《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 网络 「武清明」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

作者 | 武清明

编辑 | 王一鹏

任何一套业务架构都可能存在一定的历史问题,这是业务在不同阶段做技术选型必然出现的状况,如何用新的、合适的架构思想做恰到好处地改造,则是架构师们的必备能力。本文是 Keep 利用 DDD 改造电商供应链系统的一次精彩实战,InfoQ 架构头条独家分享,以供大家参考交流。

文章作者:武清明,目前他在 Keep 负责商业化业务中台研发和规划工作,擅长电商业务系统架构设计,采用 DDD 合理简单化设计复杂电商系统,提升系统功能模块的复用性和扩展性。

今天的主角是供应链系统,又被称为进销存系统。这个系统主要是针对采购(进)—>入库(存)—>销售(销)动态链条的管理系统,核心能力是管理仓库货物库存,在电商体系中起到承上启下的作用,下图中的 Skynet 系统和 ERP 系统分别扮演着供应链系统的核心角色,负责订单发货、售后退货、采购补货、仓间调拨以及特殊出入库等核心流程。

Skynet 系统和 ERP 系统作为元老级系统,自 Keep 开启电商赛道时开始建设,经过多年需求快速迭代,期间系统包袱越来越重,运营过程中的问题也越来越多。供应链系统相对于 Keep 电商业务发展明显滞后,甚至有可能进一步阻碍 Keep 电商业务发展,而当时的供应链系统因缺乏系统性规划、代码缺少规范,导致这个元老级系统积重难返。当时面临的主要问题如下:

  • 系统边界不清晰

  • 架构混乱,系统内部分层不清晰

    • 越来越模糊 usecase,导致代码边界和事务不清晰;
    • 分层后各层职责和接口职责不清晰,导致接口依赖混乱,甚至出现循环依赖。

  • 库存不准,库存变更上下文不清晰

    • 库存不准,超卖甚至少卖情况频繁
    • 库存变更日志不规范,上下文不清晰,出现库存问题时,查找原因困难重重
    • 库存与库存变更日志无法自证正确
  • 业务新要求

    • 库存准确率保障

    • 履约率保障

    • 提升运营效率

      • 店铺库存分配自动化
      • 智能采购

种种问题重压,在老系统上修改已无法根除系统问题,且无法满足未来业务发展需求,导致供应链系统正式提上日程。

重构思路主要包括三大类梳理,分别是:

  • 梳理库存业务场景

  • 梳理限界上下文

  • 梳理库存模型

    • 占用库存:已售卖未出库库存数
    • 可用库存:仓库实物库存 - 占用库存
    • 实物库存:仓库中的实际库存数
    • 在途库存:已采购未入库库存数
    • 冻结库存:因秒杀等促销活动或仓间调拨等预占的库存数

梳理清楚之后,关于 DDD 架构选型也是要重点考虑的内容:

  • 梳理领域模型与非领域模型之间关系 - 六边形架构

  • 保证核心领域模型的稳定性

    • 分层设计采用依赖倒置原则,保证核心领域模型的稳定性,领域层不依赖任何其他层,底层服务可以依赖高层服务所提供的接口。

  • 防止定制化查询腐化领域模型

我们通过引入 CQRS 模式,隔离命令与查询领域模型。

    • 防止与其他限界上下文交互导致领域模型腐化

如下图所示采购上下文通过防腐层 (ACL) 将仓储库存核心上下文中的仓库信息映射为自身上下文中的仓库值对象,防止仓库信息依赖腐化。

  • 架构最终落地 -COLA

  • 库存变更场景相关单据状态一致性保障

从库存变更场景中,可以看到围绕库存变更在不同的业务层存在不同的业务单据,上层业务层单据状态变更依赖底层仓储核心单据状态变更,如采购入库单入库状态变更为入库完成则采购单状态也会变更为已完成,如销售出库单状态变更为出库完成则销售发货单状态会变更为已发货。

    • 方案选择

最终我们采用 EventStore 方案,使用 EventStore 数据流程如下:

上图中黄色部分为领域事件异常处理。

  • 发布领域事件代码如下:

  • 订阅领域事件

    • 注册订阅组

    • 在订阅组中声明订阅事件

  • 在持续集成开发过程中如何同时保障效率和质量 - 单元测试保驾护航

    • 核心领域模型添加单元测试,对应 Domain 测试
    • 核心业务接口场景添加单元测试,对应 CmdExe 测试
    • 引入 Mockito 库,mock 相关接口和数据,验证流程环节是否正确
    • 在单测代码中造单测相关数据,保证单测数据可靠性
    • 单测采用 H2 数据库,避免测试过后留痕,影响后续单测,同时提升单测执行效率
    • 减少或不依赖其他中间件,如 Dubbo、Kafka 等,如依赖可考虑直接 Mock
    • git push 后 CI 开启自动单元测试

最终,回顾这次改造工作,我认为收益可以分为五点:

  • 实际库存准确,彻底解决仓库库存不准问题,同时为校准销售库存提供基准参考;
  • 功能扩展方便,如后续快速对接财务系统;
  • 快速定位问题(代码结构清晰,库存变更有据可查且上下文清晰);
  • 沉淀出较通用的事件组件 EventStore,后续在 Keep 电商内部快速推广复用;
  • 沉淀出一套比较成熟的 DDD 最佳实践,后续快速推广至 Keep 电商库存系统重构、售后重构。

可以看出,收益还是非常喜人的。大部分同学关注 DDD 是因为微服务,没错,DDD 可以说是与微服务天生互补的,DDD 领域面向划分业务模型边界,微服务面向将单体架构拆分为多个微服务,至于如何拆微服务,DDD 领域拆分则是一个非常好的微服务拆分方式。

欢迎关于 DDD,如果你想进一步交流探讨,也可以在本文下留言,期待大家的分享能够带来更多的启发。

作者介绍

武清明,从业 12 年,近 8 年一直在互联网电商行业一线从事系统研发,之前在京东和万达电商负责过仓储系统、订单系统、促销系统等研发工作。目前在 Keep 负责商业化业务中台研发和规划工作。擅长电商业务系统架构设计,采用 DDD 合理简单化设计复杂电商系统,提升系统功能模块的复用性和扩展性。

文章目录