领域驱动设计DDD在电商物流行业的实践(一):领域识别

文 / Kenyon,由于公众号推流的原因,请在关注页右上角加星标,这样才能及时收到新文章的推送。

摘要:本文以电商物流行业为背景,详细介绍如何运用领域驱动设计(DDD)来设计一款电商物流ERP的系统。从领域识别、上下文界定,到实体、值对象、聚合根、领域事件等领域对象的分析与提取,结合UML图表展示,为架构师提供一套完整的DDD实践方法论。

引言

大家好,我是Kenyon!在前面的文章中,我们探讨了架构设计的原则、方法和工具。今天,我们将聚焦于一个具体的实践场景——如何在电商物流行业中应用领域驱动设计(下文统一使用DDD)这个架构方法来构建一套电商物流ERP这样的系统。

电商物流ERP示例图
先简单介绍一下电商物流ERP是什么,它们是一款专门为跨境电商卖家提供订单管理、仓储管理、物流管理等一体化服务的系统。这样的系统涉通常会及到很多个复杂的业务领域,所以如何做到清晰地划分领域和系统的边界、识别核心业务、设计合理的领域模型,是系统是否能成功非常关键的步骤。DDD作为一种专注于业务领域的设计方法,它能很好地帮助我们去做好这些工作。

下面,我们会按照DDD的核心设计步骤,先从领域识别开始,然后逐步深入到领域对象的分析与提取,最终通过UML图表来展示一个完整的设计系统设计方案。

一、DDD是什么?

在实践开始之前,让我们先回顾一下DDD相关的核心概念,这有助于让我们更好地理解后续的整个设计和落地的过程:

  1. 领域:指的是特定业务范围的知识、规则和实践的总和。比如拿电商物流行业来说,就是我们常说的订单管理、物流管理、仓储管理等这些业务功能和模块。
  2. 子域:指的是领域的细分,通常分为核心域、支撑域和通用域,每个子域都有自己的业务逻辑和数据模型。比如订单管理子域、仓储管理子域、物流管理子域等。
  3. 限界上下文:领域模型的边界,明确在边界内术语、概念和业务规则之间能保持一致,是一个语义上完整的业务单元。我感觉这个是一个比较容易混淆的地方,因为不同限界上下文之间可能存在相同术语但含义不同的情况,需要通过上下文映射来协调。例如,在"订单管理"限界上下文中,"订单"指的是客户的购买请求,包含商品、数量、价格等信息;而在"物流管理"限界上下文中,"订单"可能指的是需要配送的包裹信息,包含收件人、地址、配送方式等信息。这两个上下文虽然都有"订单"概念,但含义和处理逻辑不同,因此需要划分为不同的限界上下文。
  4. 实体:具有唯一标识的领域对象,其状态可以随时间变化。比如订单、客户、产品等,跟我们开发过程中常说的实体(Entity)是一个意思。
  5. 值对象:描述性的领域对象,没有唯一标识,通常是不可变的,比如像订单里面的地址、金额,物流运输过程中的时间间隔等。
  6. 聚合根:聚合的根实体,是聚合对外的唯一入口点,负责维护聚合的一致性和完整性。比如订单(Order)是订单聚合的根实体,客户(Customer)是客户聚合的根实体,产品(Product)是产品聚合的根实体等。
  7. 聚合:一组具有内聚关系的实体和值对象的集合,聚合内的对象只能通过聚合根来访问,聚合根负责维护聚合的一致性和完整性。比如订单聚合包含订单(Order)、订单行项(OrderItem)、收货地址(ShippingAddress)等,仓储聚合包含仓库(Warehouse)、库位(Location)、库存记录(InventoryRecord)等。
  8. 领域事件:领域中发生的重要事件,通常用于跨聚合或限界上下文的通信。比如订单创建事件(OrderCreatedEvent)、订单状态变更事件(OrderStatusChangedEvent)、物流状态更新事件(LogisticsStatusUpdatedEvent)等。
  9. 领域服务:封装不属于任何实体或值对象的业务逻辑,负责协调多个聚合之间的操作。比如订单管理领域服务(OrderDomainService)、仓储管理领域服务(WarehouseDomainService)、物流管理领域服务(LogisticsDomainService)等。
  10. 仓储:负责持久化聚合和提供聚合的访问方法,是领域模型与外部存储系统(如数据库、消息队列等)之间的桥梁,负责将聚合从内存中持久化到存储中,以及从存储中加载聚合到内存中。比如订单管理仓储(OrderRepository)、仓储管理仓储(WarehouseRepository)、物流管理仓储(LogisticsRepository)等。
  11. 用户界面:负责与用户交互,展示领域模型的状态和处理用户输入。比如订单管理用户界面(OrderController)、仓储管理用户界面(WarehouseController)等。
  12. 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 集成模式

根据上下文映射关系,我们可以采用以下集成模式:

  1. 事件发布/订阅模式:适用于事件驱动的集成,如订单状态变更事件触发物流单的创建。
  2. 同步调用模式:适用于需要立即获取结果的场景,如订单创建时获取产品信息。
  3. 共享数据库模式:适用于关系紧密的上下文,但需要注意数据一致性,如通过本地事务+数据库约束来确保数据的幂等性和完整性。
  4. 防腐层模式:适用于与外部系统集成,如与电商平台的对接。

上下文集成示例图如下:
集成上下文的示例图

五、领域模型到代码的转换

5.1 架构分层

在将领域模型转换为代码时,我们可以采用经典的DDD分层架构:

  1. 接口层(Interface Layer):负责处理用户请求和响应
  2. 应用层(Application Layer):协调领域对象完成业务操作
  3. 领域层(Domain Layer):包含领域模型和业务逻辑
  4. 基础设施层(Infrastructure Layer):提供技术支持,如持久化、消息传递等

如下图所示:
DDD分层架构

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 实践建议

  1. 采用事件风暴(Event Storming):通过结构化的工作坊形式,与业务专家和开发团队共同参与,使用便签等可视化工具,识别领域事件、命令、聚合根、政策等核心领域元素,梳理业务流程和规则,从而构建出一个共识度高、贴近业务本质的领域模型。
  2. 从小规模开始:先选择一个核心子域进行DDD实践,积累经验后再扩展到其他子域,切莫一开始就尝试对整个系统进行DDD设计。
  3. 业务操作放到聚合根里面:聚合根是业务操作的入口,将业务逻辑放到聚合根中可以确保数据的一致性和完整性,而且修改起来也比较方便。
  4. 持续迭代:领域模型不是一成不变的,需要根据业务变化持续调整和优化,保持与业务需求的同步。
  5. 注重团队协作:DDD需要架构师、开发者和业务专家的紧密协作,确保对业务需求的理解和准确实现。
  6. 使用领域术语:在代码和文档中使用统一的领域术语,避免技术术语与业务术语混用,确保所有团队成员对领域的理解是一致的。

6.2 注意事项

  1. 避免过度设计:根据系统规模和复杂度,合理应用DDD概念,不要生搬硬套,否则只会适得其反。
  2. 关注性能:DDD虽然对架构的扩展和演进有帮助,但是其带来的复杂性也是不少的,所以在设计领域模型时,需要考虑系统性能,避免过度复杂的对象关系,导致性能问题。
  3. 保持限界上下文的独立性:避免上下文之间的耦合,确保每个上下文都能独立演进,互不干扰。
  4. 注意数据一致性:在分布式环境中,需要设计合理的机制确保数据一致性,避免数据不一致问题。
  5. 平衡业务价值与技术实现:在追求领域模型完美的同时,也要考虑技术实现的可行性和成本。

七、总结

本文以电商物流行业为背景,详细介绍了如何运用领域驱动设计(DDD)来设计一款电商物流ERP的系统。从领域识别、子域划分、限界上下文界定,到实体、值对象、聚合根、领域事件等领域对象的分析与提取,我们构建了一个完整的领域模型。

同时,我们通过一系列的UML图表来辅助整个系统的设计后,我们可以清晰地看到系统的整体结构和各个组件之间的关系。这种可视化的方式不仅有助于团队成员理解系统设计,也为后续的开发和维护提供了重要的参考。

DDD是一种强大的设计方法,它能够帮助我们更好地理解业务需求,设计出更加符合业务本质的系统。在实践中,我们需要结合具体的业务场景,灵活运用DDD的核心概念和方法,不断优化和完善领域模型。

本文是作者通过个人的实践经验得出来的,希望能够通过抛砖引玉,为大家在日常工作中应用DDD的时候提供一些参考和启发。如果你有任何问题或建议,欢迎在评论区留言讨论。


互动话题:您有实践过DDD吗?在实践DDD的时候有遇到过哪些挑战呢?当时是如何解决的?欢迎在评论区分享你的经验!

工具附录

关于作者

Kenyon,资深软件架构师,15年的软件开发和技术管理经验,从程序员做到企业技术高管。多年企业数字化转型和软件架构设计经验,善于帮助企业构建高质量、可维护的软件系统,目前专注技术管理、架构设计、AI技术应用和落地;全网统一名称"六边形架构",欢迎关注交流。

原创不易,转载请联系授权,如果觉得有帮助,请点赞、收藏、转发三连支持!

快来关注我吧!

posted @ 2026-01-27 09:20  六边形架构  阅读(2)  评论(0)    收藏  举报