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

14 阅读18分钟

文 / 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技术应用和落地;全网统一名称"六边形架构",欢迎关注交流。

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

快来关注我吧!