架构师学习-DDD

架构师学习 第2篇

示例

构建一个在线电子商务系统(E-Shop)的设计示例。这个示例将从宏观的战略设计(如何划分系统边界)到微观的战术设计(代码层面的核心元素),全面展示 DDD 的核心思想。

1. 战略设计:界定的上下文 (Bounded Contexts)

DDD 的首要原则是软件必须植根于领域,并且模型需要有清晰的边界。

场景: 假设我们要构建一个大型在线商店,不仅涉及用户下单,还需要处理库存发货和销售报表。

传统问题: 许多团队会试图创建一个包含所有属性(如用户、订单、商品、库存、报表)的单一庞大模型。这会导致模型臃肿,不同职能的团队互相干扰。

DDD 解决方案:
我们将系统划分为两个独立的界定的上下文(Bounded Contexts),:

  1. 在线交易上下文(E-Shop Context): 关注客户下单、购物车、结账。这里的“商品”关注价格和描述。
  2. 报表上下文(Reporting Context): 关注销售趋势、库存周转。这里的“商品”可能只关注销售数量和成本,不需要描述信息。

核心思想:

  • 消除歧义: 同一个词(如“商品”)在不同上下文中可能有不同的含义和属性。通过划分上下文,我们可以保证模型在各自边界内的纯洁性和一致性。
  • 上下文映射(Context Map): 我们定义这两个上下文的关系。例如,报表系统需要从交易系统获取数据,它们可能通过客户-供应商(Customer-Supplier)模式交互,或者通过防崩溃层(Anticorruption Layer)来转换数据,确保报表系统的模型不受交易系统模型变更的直接破坏,。

2. 战术设计:领域模型的核心要素

在“在线交易上下文”内部,我们使用战术模式来构建领域模型。

A. 通用语言 (Ubiquitous Language)

开发人员与业务专家(如销售经理)共同制定一套语言。

  • 示例: 大家不再说“插入一条记录到订单表”,而是统一说“提交订单(Submit Order)”。
  • 价值: 这消除了沟通障碍,代码中的类名和方法名将直接反映业务意图,。

B. 分层架构 (Layered Architecture)

为了隔离关注点,我们将系统分为四层,:

  1. 用户界面层: 展示商品页面,接收用户点击。
  2. 应用层: 协调任务(如“协调结账流程”),但不包含业务逻辑。
  3. 领域层(核心): 包含 OrderCustomer 等业务对象和规则。这是软件的心脏
  4. 基础设施层: 处理数据库持久化、发送邮件等技术实现。

C. 实体 (Entities) 与 值对象 (Value Objects)

这是领域模型的基本构建块。

  • 实体(Entity):

    • 示例: Order(订单)。
    • 设计理由: 订单有生命周期(从创建到支付到发货),并且需要被追踪。即使两个订单的内容完全一样,只要 ID 不同,它们就是不同的对象。因此,Order 是一个实体,必须有唯一的标识符(Identity),。
  • 值对象(Value Object):

    • 示例: Address(送货地址)。
    • 设计理由: 我们只关心地址的属性(街道、城市),而不关心它的唯一标识。如果两个客户住在同一地址,这在业务上是等价的。Address 应该是不可变的(Immutable),如果客户搬家了,我们是用一个新的 Address 对象替换旧的,而不是修改旧对象,。

D. 聚合 (Aggregates)

为了保证数据一致性,我们需要划定修改数据的边界。

  • 示例: 一个 Order(订单)可能包含多个 OrderItem(订单项)。
  • 设计: Order 是这个聚合的根(Aggregate Root)
  • 规则: 外部对象只能引用根(Order),不能直接引用内部的 OrderItem。如果想修改某个订单项的数量,必须通过根的方法(如 order.updateItemQuantity())来进行。这确保了订单总价等不变量(Invariants)在修改过程中始终保持一致,。

E. 服务 (Services)

有些动作不属于特定的对象。

  • 示例: CheckoutService(结账服务)或 FundTransferService(转账服务)。
  • 设计理由: 结账可能涉及订单状态更新、库存扣减、支付网关调用等。这些行为放入 OrderCustomer 都不合适,因此我们创建一个无状态的领域服务来封装这些操作,。

F. 资源库 (Repositories)

为了解耦领域模型与数据库。

  • 示例: OrderRepository
  • 设计: 领域层只定义接口 findOrder(id),不关心底层是 SQL Server 还是 Oracle。资源库负责从数据库中检索数据并将其重建为领域对象(如 Order 聚合)。这让开发人员可以像从内存集合中获取对象一样获取领域对象,而无需在业务逻辑中编写 SQL,。

3. 系统运作流程示例

结合上述概念,一个“用户修改收货地址”的业务场景在代码设计中如下流转:

  1. 应用层接收请求,调用资源库CustomerRepository)。
  2. 资源库利用基础设施层从数据库检索数据,重建 Customer 聚合(实体)。
  3. 应用层调用 Customer 实体的业务方法(如 customer.moveTo(newAddress))。
  4. 在方法内部,Customer 实体将旧的 Address 值对象替换为新的 Address 值对象
  5. 资源库将更新后的 Customer 聚合保存回数据库。

总结与比喻

这个设计示例体现了 DDD 的核心:通过将软件实现与业务领域模型紧密绑定,来应对复杂性。

为了巩固理解,我们可以用书中提到的汽车制造来类比这个系统设计:

  • 领域模型就像是汽车的设计蓝图。工人在造车前必须先有精准的图纸,同样,开发软件前必须先理解并建模业务领域。
  • 实体与聚合就像是汽车的发动机和底盘。它们是核心部件,有独立的标识和生命周期,必须作为一个整体来组装和维护。
  • 分层架构就像是汽车的不同系统(传动系统、电子系统、内饰)。内饰(UI)的变化不应直接影响发动机(领域逻辑)的运作。
  • 通用语言就像是工程师团队之间的技术术语。如果有人把“方向盘”叫成“转弯器”,制造过程就会混乱;同样,代码必须精确使用业务术语。

学习资料

https://github.com/Sairyss/domain-driven-hexagon?tab=readme-ov-file