一个电商供应链系统的DDD实战

  • 2021-10-30
  • Admin

951be28684114d8b1313371b03d94f68.png

作者 | 武清明

来源 | 网络

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

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

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

ee4818120c33c48294704120d5e01221.png

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

  • 系统边界不清晰

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

    • 越来越模糊 usecase,导致代码边界和事务不清晰;

    • 分层后各层职责和接口职责不清晰,导致接口依赖混乱,甚至出现循环依赖。

19c539c268d3c7597917669d6c44bcdf.png

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

    • 库存不准,超卖甚至少卖情况频繁

    • 库存变更日志不规范,上下文不清晰,出现库存问题时,查找原因困难重重

    • 库存与库存变更日志无法自证正确

    • 如果您正在学习Spring Boot,推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/

  • 业务新要求

    • 店铺库存分配自动化

    • 智能采购

    • 库存准确率保障

    • 履约率保障

    • 提升运营效率

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

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

  • 梳理库存业务场景

ea72fd051b3847f783ac69cb59338f3f.png

  • 梳理限界上下文

93bef8ba4ebc7a9192de3a019fd02469.png

  • 梳理库存模型

    • 占用库存:已售卖未出库库存数

    • 可用库存:仓库实物库存 - 占用库存

    • 实物库存:仓库中的实际库存数

    • 在途库存:已采购未入库库存数

    • 冻结库存:因秒杀等促销活动或仓间调拨等预占的库存数

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

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

c40c9331f984e6eb304742d809b7e212.png

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

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

829ff46e84a7ba8dfb70e22228d0a35c.png

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

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

b15e2095c836ba23b7e9531e8f24e38e.png

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

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

7c481b4b13e42849624ed2646019ab2e.png

  • 架构最终落地 -COLA

646e6b2ae582f01d1b103588dd66292e.png

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

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

另外,如果您正在学习Spring Cloud,推荐一个连载多年还在继续更新的免费教程:https://blog.didispace.com/spring-cloud-learning/

    • 方案选择

e3681700500193ea00254d73d4bd1380.png

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

7fa712933d583f26c05b0fed4fe6b7c5.png

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

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

a60d355f851127bddc8da3a57b180700.png

bfb40bc4ce0ed436b7cd8e6a80d302d1.png

1f8741d7fee429343a8d465f98c9e253.png

  • 订阅领域事件

    • 注册订阅组

745488f3ce83e09df19e0d14d9ccb736.png

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

c8c731c2bc732c44457a1db265ee4915.png

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

    • 核心领域模型添加单元测试,对应 Domain 测试

    • 核心业务接口场景添加单元测试,对应 CmdExe 测试

    • 引入 Mockito 库,mock 相关接口和数据,验证流程环节是否正确

    • 在单测代码中造单测相关数据,保证单测数据可靠性

    • 单测采用 H2 数据库,避免测试过后留痕,影响后续单测,同时提升单测执行效率

    • 减少或不依赖其他中间件,如 Dubbo、Kafka 等,如依赖可考虑直接 Mock

    • git push 后 CI 开启自动单元测试

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

  • 实际库存准确,彻底解决仓库库存不准问题,同时为校准销售库存提供基准参考;

  • 功能扩展方便,如后续快速对接财务系统;

  • 快速定位问题(代码结构清晰,库存变更有据可查且上下文清晰);

  • 沉淀出较通用的事件组件 EventStore,后续在 Keep 电商内部快速推广复用;

  • 沉淀出一套比较成熟的 DDD 最佳实践,后续快速推广至 Keep 电商库存系统重构、售后重构。

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

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

作者介绍

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

往期推荐

JWT 应该保存在哪里?

一个月薪 12000 的北京程序员的真实生活

一文带你入门 JMeter 性能测试!

程序员的“鱿鱼游戏”,你能活到第几关?

大名鼎鼎的 OceanBase 居然在买Star !?

084dd1b804aa91fe01eb6d1871c1c46b.png技术交流群f84d65ed293f7d63ebecc805c4bcdfe6.png

最近有很多人问,有没有读者交流群,想知道怎么加入。加入方式很简单,有兴趣的同学,只需要点击下方卡片,回复“加群“,即可免费加入我们的高质量技术交流群!

点击阅读原文,送你免费Spring Boot教程!

原文:https://blog.csdn.net/j3T9Z7H/article/details/121045131

联系站长

QQ:769220720