38. 领域驱动设计DDD在电商物流行业的实践(一):领域识别
38. 领域驱动设计DDD在电商物流行业的实践(一):领域识别
文 / Kenyon,由于公众号推流的原因,请在关注页右上角加星标,这样才能及时收到新文章的推送。
摘要:本文以电商物流行业为背景,详细介绍如何运用领域驱动设计(DDD)来设计一款电商物流ERP的系统。从领域识别、上下文界定,到实体、值对象、聚合根、领域事件等领域对象的分析与提取,结合UML图表展示,为架构师提供一套完整的DDD实践方法论。
引言
大家好,我是Kenyon!在前面的文章中,我们探讨了架构设计的原则、方法和工具。今天,我们将聚焦于一个具体的实践场景——如何在电商物流行业中应用领域驱动设计(下文统一使用DDD)这个架构方法来构建一套电商物流ERP这样的系统。

先简单介绍一下电商物流ERP是什么,它们是一款专门为跨境电商卖家提供订单管理、仓储管理、物流管理等一体化服务的系统。这样的系统涉通常会及到很多个复杂的业务领域,所以如何做到清晰地划分领域和系统的边界、识别核心业务、设计合理的领域模型,是系统是否能成功非常关键的步骤。DDD作为一种专注于业务领域的设计方法,它能很好地帮助我们去做好这些工作。
下面,我们会按照DDD的核心设计步骤,先从领域识别开始,然后逐步深入到领域对象的分析与提取,最终通过UML图表来展示一个完整的设计系统设计方案。
一、DDD是什么?
在实践开始之前,让我们先回顾一下DDD相关的核心概念,这有助于让我们更好地理解后续的整个设计和落地的过程:
- 领域:指的是特定业务范围的知识、规则和实践的总和。比如拿电商物流行业来说,就是我们常说的订单管理、物流管理、仓储管理等这些业务功能和模块。
- 子域:指的是领域的细分,通常分为核心域、支撑域和通用域,每个子域都有自己的业务逻辑和数据模型。比如订单管理子域、仓储管理子域、物流管理子域等。
- 限界上下文:领域模型的边界,明确在边界内术语、概念和业务规则之间能保持一致,是一个语义上完整的业务单元。我感觉这个是一个比较容易混淆的地方,因为不同限界上下文之间可能存在相同术语但含义不同的情况,需要通过上下文映射来协调。例如,在"订单管理"限界上下文中,"订单"指的是客户的购买请求,包含商品、数量、价格等信息;而在"物流管理"限界上下文中,"订单"可能指的是需要配送的包裹信息,包含收件人、地址、配送方式等信息。这两个上下文虽然都有"订单"概念,但含义和处理逻辑不同,因此需要划分为不同的限界上下文。
- 实体:具有唯一标识的领域对象,其状态可以随时间变化。比如订单、客户、产品等,跟我们开发过程中常说的实体(Entity)是一个意思。
- 值对象:描述性的领域对象,没有唯一标识,通常是不可变的,比如像订单里面的地址、金额,物流运输过程中的时间间隔等。
- 聚合根:聚合的根实体,是聚合对外的唯一入口点,负责维护聚合的一致性和完整性。比如订单(Order)是订单聚合的根实体,客户(Customer)是客户聚合的根实体,产品(Product)是产品聚合的根实体等。
- 聚合:一组具有内聚关系的实体和值对象的集合,聚合内的对象只能通过聚合根来访问,聚合根负责维护聚合的一致性和完整性。比如订单聚合包含订单(Order)、订单行项(OrderItem)、收货地址(ShippingAddress)等,仓储聚合包含仓库(Warehouse)、库位(Location)、库存记录(InventoryRecord)等。
- 领域事件:领域中发生的重要事件,通常用于跨聚合或限界上下文的通信。比如订单创建事件(OrderCreatedEvent)、订单状态变更事件(OrderStatusChangedEvent)、物流状态更新事件(LogisticsStatusUpdatedEvent)等。
- 领域服务:封装不属于任何实体或值对象的业务逻辑,负责协调多个聚合之间的操作。比如订单管理领域服务(OrderDomainService)、仓储管理领域服务(WarehouseDomainService)、物流管理领域服务(LogisticsDomainService)等。
- 仓储:负责持久化聚合和提供聚合的访问方法,是领域模型与外部存储系统(如数据库、消息队列等)之间的桥梁,负责将聚合从内存中持久化到存储中,以及从存储中加载聚合到内存中。比如订单管理仓储(OrderRepository)、仓储管理仓储(WarehouseRepository)、物流管理仓储(LogisticsRepository)等。
- 用户界面:负责与用户交互,展示领域模型的状态和处理用户输入。比如订单管理用户界面(OrderController)、仓储管理用户界面(WarehouseController)等。
- CQRS模式:将命令(写操作)和查询(读操作)分离开来,分别由不同的处理逻辑和数据存储。比如订单管理命令查询分离(OrderCommandQuerySeparation)、仓储管理命令查询分离(WarehouseCommandQuerySeparation)等。
二、电商物流领域的识别与划分
2.1 业务场景分析
根据上面说举例的DDD的概念示例,我们可以把电商物流ERP这样的系统所涉及的主要业务场景按下面这样的方式来进行划分:
- 订单管理:接收来自不同电商平台的订单,处理订单状态变更、订单取消等操作
- 产品管理:管理商品信息、库存状态、SKU等
- 仓储管理:仓库规划、库位管理、库存盘点
- 物流管理:选择物流渠道、生成物流标签、跟踪物流状态
- 采购管理:根据库存水平自动或手动生成采购单
- 财务管理:订单对账、费用核算、报表生成
- 客户管理:管理买家信息、沟通记录
- 平台集成:与Amazon、eBay、Shopify等电商平台的对接
2.2 子域划分
基于上述业务场景,我们可以将电商物流领域划分为以下子域:
| 子域类型 | 子域名称 | 描述 | 重要性 |
|---|---|---|---|
| 核心域 | 订单管理 | 处理订单生命周期,是系统的核心价值 | 高 |
| 核心域 | 物流管理 | 管理物流渠道和物流状态,直接影响客户体验 | 高 |
| 支撑域 | 仓储管理 | 支持订单和物流的执行,管理库存 | 中 |
| 支撑域 | 产品管理 | 管理商品信息,为订单和仓储提供基础数据 | 中 |
| 支撑域 | 采购管理 | 保证库存充足,支持销售业务 | 中 |
| 支撑域 | 财务管理 | 处理财务核算,为决策提供数据 | 中 |
| 支撑域 | 客户管理 | 管理客户信息,提升服务质量 | 中 |
| 通用域 | 平台集成 | 与外部电商平台对接,获取订单数据 | 低 |
| 通用域 | 用户管理 | 系统用户认证和授权 | 低 |
2.3 限界上下文界定
根据子域划分,我们可以界定出以下限界上下文:

三、领域对象分析与提取
下面我们开始分析系统中所涉及到的订单上下文的领域对象。
3.1 订单上下文
3.1.1 实体与值对象
实体:
- 订单(Order):订单的实体,具有唯一订单号,状态会随着订单处理的过程变化而更新。
- 订单行项(OrderItem):订单中的商品明细,与订单关联。
值对象:
- 订单状态(OrderStatus):表示订单的当前状态,如待处理、已发货、已完成等。
- 收货地址(ShippingAddress):描述收货位置,无唯一标识,如果是电商系统的话,这里可以设计成有唯一标识的实体。
- 付款信息(PaymentInfo):描述付款方式和状态,无唯一标识,如果是电商系统的话,这里也可以设计成有唯一标识的实体。
3.1.2 聚合根与聚合
聚合根:
- 订单(Order):作为聚合根,负责管理订单、订单项、订单状态、收货地址、付款信息等,如果用充血模型的话,这里还应包含了订单创建、更新、取消等业务操作的逻辑处理。
聚合:
- 订单聚合:包含订单、订单行项、收货地址、付款信息等。
3.1.3 领域事件
- 订单创建事件(OrderCreatedEvent):当新订单创建时触发。
- 订单状态变更事件(OrderStatusChangedEvent):当订单状态发生变化时触发。
- 订单发货事件(OrderShippedEvent):当订单发货时触发。
- 订单完成事件(OrderCompletedEvent):当订单完成时触发。
3.1.4 领域服务
- 订单处理服务(OrderProcessingService):处理订单的创建、修改、取消等操作。
- 订单同步服务(OrderSyncService):与电商平台同步订单数据。
订单上下文的示例图如下:
3.2 物流上下文
下面我们开始分析系统中所涉及到的物流上下文的领域对象。
3.2.1 实体与值对象
实体:
- 物流单(LogisticsOrder):具有唯一物流单号,状态随物流过程变化。
- 物流渠道(LogisticsChannel):物流服务提供商,如FedEx、UPS等,每个物流渠道都有自己的物流单号生成规则和费用计算方式。
值对象:
- 物流状态(LogisticsStatus):表示物流的当前状态,如已揽收、运输中、已送达等。
- 物流标签(LogisticsLabel):包含物流信息的标签,用于贴在包裹上,无唯一标识。
- 物流费用(LogisticsFee):物流服务的费用,无唯一标识。
3.2.2 聚合根与聚合
聚合根:
- 物流单(LogisticsOrder):作为聚合根,负责管理物流状态、物流标签、物流费用等。
聚合:
- 物流单聚合:包含物流单、物流状态、物流标签、物流费用等。
3.2.3 领域事件
- 物流单创建事件(LogisticsOrderCreatedEvent):当新物流单创建时触发。
- 物流状态变更事件(LogisticsStatusChangedEvent):当物流状态发生变化时触发。
- 物流标签生成事件(LogisticsLabelGeneratedEvent):当物流标签生成时触发。
- 物流完成事件(LogisticsCompletedEvent):当物流完成时触发。
3.2.4 领域服务
- 物流单处理服务(LogisticsOrderProcessingService):处理物流单的创建、修改等操作。
- 物流渠道服务(LogisticsChannelService):管理物流渠道信息,计算物流费用。
- 物流跟踪服务(LogisticsTrackingService):跟踪物流状态,更新物流信息。
物流上下文的示例图如下:
3.3 仓储上下文
下面我们来分析和提取系统中仓储上下文的相关领域对象。
3.3.1 实体与值对象
实体:
- 仓库(Warehouse):用来存放商品的场所及相关的信息,具有唯一标识。
- 库位(Location):为了方便仓库的管理而划分出来具体位置,用于存放商品及方便管理库存。
- 库存记录(InventoryRecord):记录商品在仓库中的实际的库存以及变化的情况。
值对象:
- 库存状态(InventoryStatus):用于表示库存的状态,如正常、不足、过剩等。
- 库存变动(InventoryMovement):记录库存的变动情况,如入库、出库、调拨等。
3.3.2 聚合根与聚合
聚合根:
- 仓库(Warehouse):作为聚合根,负责管理库位和库存记录。
聚合:
- 仓库聚合:包含仓库、库位、库存记录等。
3.3.3 领域事件
- 库存变动事件(InventoryMovementEvent):当库存发生变动时触发。
- 库存不足事件(InventoryShortageEvent):当库存不足时触发。
- 库存盘点事件(InventoryCountEvent):当库存盘点完成时触发。
3.3.4 领域服务
- 仓库管理服务(WarehouseManagementService):管理仓库信息,如创建、修改仓库。
- 库存管理服务(InventoryManagementService):管理库存记录,如入库、出库、调拨等。
- 库存盘点服务(InventoryCountService):执行库存盘点,调整库存数量。
仓储上下文的示例图如下:
3.4 产品上下文
下面,我们来介绍产品上下文的实体、值对象、聚合根和聚合。
3.4.1 实体与值对象
实体:
- 产品(Product):具有唯一标识的商品信息。
- SKU(StockKeepingUnit):产品的库存单位,是库存管理的最小单位。
- 产品分类(ProductCategory):对产品进行分类管理。
值对象:
- 产品属性(ProductAttribute):描述产品的特性,如颜色、尺寸等。
- 产品价格(ProductPrice):产品的价格信息,无唯一标识。
3.4.2 聚合根与聚合
聚合根:
- 产品(Product):作为聚合根,负责管理SKU和产品属性。
聚合:
- 产品聚合:包含产品、SKU、产品属性、产品价格等
3.4.3 领域事件
- 产品创建事件(ProductCreatedEvent):当新产品创建时触发。
- 产品更新事件(ProductUpdatedEvent):当产品信息更新时触发。
- SKU创建事件(SKUCreatedEvent):当新SKU创建时触发。
3.4.4 领域服务
- 产品管理服务(ProductManagementService):管理产品信息,如创建、修改产品。
- SKU管理服务(SKUManagementService):管理SKU信息,如创建、修改SKU。
- 产品分类服务(ProductCategoryService):管理产品分类,如创建、修改分类。
以下是产品上下文的类图:
四、限界上下文集成
在DDD中,限界上下文之间的集成是一个重要的环节。我们需要设计合理的集成方式,确保各个上下文之间能够顺畅地通信和协作。
4.1 上下文映射
上下文映射描述了限界上下文之间的关系和集成方式。对于我们的电商物流系统,主要的上下文映射关系如下:
| 源上下文 | 目标上下文 | 关系类型 | 集成方式 |
|---|---|---|---|
| 订单上下文 | 物流上下文 | 上游/下游 | 事件发布/订阅模式 |
| 订单上下文 | 仓储上下文 | 上游/下游 | 事件发布/订阅模式 |
| 订单上下文 | 产品上下文 | 上游/下游 | 同步调用模式 |
| 仓储上下文 | 采购上下文 | 上游/下游 | 事件发布/订阅模式 |
| 物流上下文 | 财务上下文 | 上游/下游 | 事件发布/订阅模式 |
| 订单上下文 | 财务上下文 | 上游/下游 | 事件发布/订阅模式 |
| 平台集成上下文 | 订单上下文 | 上游/下游 | 同步调用模式 |
| 平台集成上下文 | 产品上下文 | 上游/下游 | 同步调用模式 |
4.2 集成模式
根据上下文映射关系,我们可以采用以下集成模式:
- 事件发布/订阅模式:适用于事件驱动的集成,如订单状态变更事件触发物流单的创建。
- 同步调用模式:适用于需要立即获取结果的场景,如订单创建时获取产品信息。
- 共享数据库模式:适用于关系紧密的上下文,但需要注意数据一致性,如通过本地事务+数据库约束来确保数据的幂等性和完整性。
- 防腐层模式:适用于与外部系统集成,如与电商平台的对接。
上下文集成示例图如下:
五、领域模型到代码的转换
5.1 架构分层
在将领域模型转换为代码时,我们可以采用经典的DDD分层架构:
- 接口层(Interface Layer):负责处理用户请求和响应
- 应用层(Application Layer):协调领域对象完成业务操作
- 领域层(Domain Layer):包含领域模型和业务逻辑
- 基础设施层(Infrastructure Layer):提供技术支持,如持久化、消息传递等
如下图所示:
5.2 代码结构示例
以下是一个简化的代码结构示例,展示了如何组织我们的领域模型代码:
src/
├── application/ # 应用层
│ ├── command/ # 命令处理
│ ├── query/ # 查询处理
│ └── service/ # 应用服务
├── domain/ # 领域层
│ ├── order/ # 订单子域
│ │ ├── aggregate/ # 聚合
│ │ ├── entity/ # 实体
│ │ ├── event/ # 领域事件
│ │ ├── repository/ # 仓储接口
│ │ ├── service/ # 领域服务
│ │ └── valueobject/ # 值对象
│ ├── logistics/ # 物流子域
│ ├── warehouse/ # 仓储子域
│ └── product/ # 产品子域
├── infrastructure/ # 基础设施层
│ ├── persistence/ # 持久化
│ ├── messaging/ # 消息传递
│ └── external/ # 外部系统集成
└── interface/ # 接口层
├── controller/ # 控制器
├── dto/ # 数据传输对象
└── validator/ # 验证器六、实践建议与注意事项
6.1 实践建议
- 采用事件风暴(Event Storming):通过结构化的工作坊形式,与业务专家和开发团队共同参与,使用便签等可视化工具,识别领域事件、命令、聚合根、政策等核心领域元素,梳理业务流程和规则,从而构建出一个共识度高、贴近业务本质的领域模型。
- 从小规模开始:先选择一个核心子域进行DDD实践,积累经验后再扩展到其他子域,切莫一开始就尝试对整个系统进行DDD设计。
- 业务操作放到聚合根里面:聚合根是业务操作的入口,将业务逻辑放到聚合根中可以确保数据的一致性和完整性,而且修改起来也比较方便。
- 持续迭代:领域模型不是一成不变的,需要根据业务变化持续调整和优化,保持与业务需求的同步。
- 注重团队协作:DDD需要架构师、开发者和业务专家的紧密协作,确保对业务需求的理解和准确实现。
- 使用领域术语:在代码和文档中使用统一的领域术语,避免技术术语与业务术语混用,确保所有团队成员对领域的理解是一致的。
6.2 注意事项
- 避免过度设计:根据系统规模和复杂度,合理应用DDD概念,不要生搬硬套,否则只会适得其反。
- 关注性能:DDD虽然对架构的扩展和演进有帮助,但是其带来的复杂性也是不少的,所以在设计领域模型时,需要考虑系统性能,避免过度复杂的对象关系,导致性能问题。
- 保持限界上下文的独立性:避免上下文之间的耦合,确保每个上下文都能独立演进,互不干扰。
- 注意数据一致性:在分布式环境中,需要设计合理的机制确保数据一致性,避免数据不一致问题。
- 平衡业务价值与技术实现:在追求领域模型完美的同时,也要考虑技术实现的可行性和成本。
七、总结
本文以电商物流行业为背景,详细介绍了如何运用领域驱动设计(DDD)来设计一款电商物流ERP的系统。从领域识别、子域划分、限界上下文界定,到实体、值对象、聚合根、领域事件等领域对象的分析与提取,我们构建了一个完整的领域模型。
同时,我们通过一系列的UML图表来辅助整个系统的设计后,我们可以清晰地看到系统的整体结构和各个组件之间的关系。这种可视化的方式不仅有助于团队成员理解系统设计,也为后续的开发和维护提供了重要的参考。
DDD是一种强大的设计方法,它能够帮助我们更好地理解业务需求,设计出更加符合业务本质的系统。在实践中,我们需要结合具体的业务场景,灵活运用DDD的核心概念和方法,不断优化和完善领域模型。
本文是作者通过个人的实践经验得出来的,希望能够通过抛砖引玉,为大家在日常工作中应用DDD的时候提供一些参考和启发。如果你有任何问题或建议,欢迎在评论区留言讨论。
互动话题:您有实践过DDD吗?在实践DDD的时候有遇到过哪些挑战呢?当时是如何解决的?欢迎在评论区分享你的经验!
工具附录:
关于作者
Kenyon,资深软件架构师,15年的软件开发和技术管理经验,从程序员做到企业技术高管。多年企业数字化转型和软件架构设计经验,善于帮助企业构建高质量、可维护的软件系统,目前专注技术管理、架构设计、AI技术应用和落地;全网统一名称"六边形架构",欢迎关注交流。
原创不易,转载请联系授权,如果觉得有帮助,请点赞、收藏、转发三连支持!

