1. 领域驱动设计(DDD)基础:核心概念与战略设计

领域驱动设计(Domain-Driven Design,DDD)是一套应对复杂业务系统建模的方法论,由 Eric Evans 在其经典著作中提出。它强调以业务领域为核心,通过统一的语言(Ubiquitous Language)将业务概念精确映射到软件模型中。本章作为课程的开篇,将系统梳理 DDD 的核心概念与战略设计工具,为后续的模块边界划分与审批流故障排查奠定坚实的理论基础。

学习提示:DDD 并非银弹,它最适合业务逻辑复杂、规则多变的领域。对于简单的 CRUD 系统,过度使用 DDD 反而会增加不必要的复杂度。请在学习过程中始终思考:“这个模式解决了我当前业务中的哪个痛点?”

1.1 核心概念:战术建模的基石

战术建模(Tactical Modeling)提供了构建领域模型的具体模式。理解这些模式是进行有效领域建模的第一步。

概念 定义 关键特征 典型示例
实体(Entity) 具有唯一标识,且其状态会随时间变化的对象。 有 ID、可变、通过 ID 区分 用户(userId)、订单(orderId)
值对象(Value Object) 描述事物的属性,没有唯一标识,不可变。 无 ID、不可变、通过属性值相等判断 地址(Address)、金额(Money)
聚合(Aggregate) 一组相关对象的集合,由聚合根(Aggregate Root)统一管理。 有聚合根、内部一致性、外部只能通过聚合根访问 订单聚合(Order 为根,包含 OrderItem)
领域事件(Domain Event) 领域中发生的、业务人员关心的、具有业务意义的事件。 过去时、不可变、携带上下文数据 订单已支付(OrderPaid)、库存已扣减
实战辨析:实体 vs 值对象
判断一个概念是实体还是值对象,核心标准是:你是否关心它的“身份”变化?
- 一个“人”换了地址,他仍然是同一个人 → 人是实体,地址是值对象。
- 两个“金额”都是 100 元,它们完全等价 → 金额是值对象。
- 如果系统需要追踪“地址”的历史变更记录(如物流轨迹),地址就可能升级为实体。

1.2 聚合:事务一致性的边界

聚合是 DDD 中最重要的战术模式之一,它定义了数据一致性边界。在一个聚合内部,所有对象的修改必须在一次事务中完成并保证强一致性;跨聚合的修改则通过最终一致性实现。

// 聚合根示例:Order(订单)
public class Order {
    private OrderId id;          // 实体标识
    private List<OrderItem> items; // 内部实体列表
    private Money totalAmount;   // 值对象
    private OrderStatus status;  // 值对象

    // 外部只能通过聚合根的方法修改内部状态
    public void addItem(ProductId productId, int quantity, Money price) {
        // 业务规则校验:商品是否可售、数量是否超限
        this.items.add(new OrderItem(productId, quantity, price));
        this.totalAmount = this.calculateTotal(); // 重新计算总额
    }

    public void pay() {
        // 状态机校验:只有待支付状态才能支付
        if (this.status != OrderStatus.PENDING_PAYMENT) {
            throw new IllegalStateException("订单状态不允许支付");
        }
        this.status = OrderStatus.PAID;
        // 发布领域事件
        DomainEventPublisher.publish(new OrderPaidEvent(this.id));
    }
}
常见误区:不要将聚合设计得过大。一个聚合包含的对象越多,事务冲突的概率就越高,系统并发性能越差。遵循“一次操作只修改一个聚合”的原则,优先考虑小聚合。

1.3 领域事件:解耦与异步通知

领域事件用于在聚合之间或限界上下文之间传递信息,实现松耦合。当一个聚合完成某个业务操作后,它会发布一个事件,其他感兴趣的订阅者可以异步响应。

  • 发布者:通常是聚合根,在方法执行成功后发布事件。
  • 订阅者:可以是同一个限界上下文内的其他聚合,也可以是不同限界上下文的服务。
  • 典型场景:订单支付成功后,发布 OrderPaidEvent,库存服务订阅后扣减库存,通知服务订阅后发送短信。
DDD 核心概念关系图 实体 (Entity) 有ID,可变,可追踪 值对象 (VO) 无ID,不可变,属性相等 聚合 (Aggregate) 聚合根 + 内部实体/VO 领域事件 (Domain Event) 过去时,不可变,携带数据 发布 说明 聚合是事务一致性边界

1.4 战略设计:划定系统边界

当系统规模变大,单一模型无法覆盖所有业务时,就需要引入战略设计(Strategic Design)。战略设计的核心是限界上下文(Bounded Context)和上下文映射(Context Map)。

1.5 限界上下文:模型的边界

限界上下文是 DDD 战略设计的核心。它定义了一个显式的边界,在这个边界内部,领域模型、通用语言和代码实现保持高度一致。不同限界上下文之间通过明确的接口进行通信。

  • 每个限界上下文拥有自己的通用语言:同一个术语在不同上下文中可能有不同含义。例如,“客户”在“销售上下文”中可能指购买者,在“售后上下文”中可能指报修人。
  • 每个限界上下文拥有自己的模型和代码库:团队可以独立演进,技术栈也可以不同。
  • 典型划分依据:业务能力(如订单、库存、支付)、子域类型(核心域、支撑域、通用域)。

1.6 上下文映射:协作模式

上下文映射描述了不同限界上下文之间的协作关系。常见的映射模式包括:

模式 关系描述 适用场景
合作关系(Partnership) 两个上下文紧密协作,共同演进。 两个团队需要同步开发,共享同一个目标。
共享内核(Shared Kernel) 共享一部分模型和代码,保持一致性。 两个上下文有高度重叠的核心概念。
客户-供应商(Customer-Supplier) 上游(供应商)决定模型,下游(客户)适配。 下游依赖上游的接口,上游优先满足下游需求。
防腐层(Anti-Corruption Layer) 下游上下文通过一层转换,隔离上游模型的污染。 集成遗留系统或第三方服务,保护自身模型。
开放主机服务(Open Host Service) 上游提供明确的 API 供下游调用。 上游希望被多个下游以标准方式集成。
发布语言(Published Language) 使用标准的数据格式(如 XML、JSON Schema)进行通信。 跨系统集成,需要统一的数据契约。
大泥球(Big Ball of Mud) 没有清晰边界,模型混乱耦合。 需要避免的反模式。
战略设计核心原则:
1. 按业务能力划分:每个限界上下文应该对应一个独立的业务能力或子域。
2. 保持模型纯净:在限界上下文内部,模型只反映该上下文的业务逻辑,不掺杂其他上下文的规则。
3. 明确通信方式:根据上下文之间的关系,选择合适的映射模式(如防腐层、开放主机服务)。
限界上下文与上下文映射示例 订单上下文 Order, OrderItem OrderStatus 库存上下文 Product, Warehouse StockLevel 支付上下文 Payment, Transaction Account 客户-供应商 合作关系 防腐层 (ACL) 隔离遗留系统污染 遗留系统 大泥球反模式 调用 关键原则 每个上下文有独立模型 通过映射模式协作

1.7 本章小结

本章介绍了 DDD 的两大支柱:战术建模(实体、值对象、聚合、领域事件)和战略设计(限界上下文、上下文映射)。这些概念是后续进行模块边界划分和审批流故障排查的基础。请务必理解:

  • 聚合是保证数据一致性的最小单元,也是微服务拆分的候选边界。
  • 限界上下文是微服务划分的逻辑依据,上下文映射决定了服务间的通信模式。
  • 领域事件是实现服务间异步解耦的关键手段。
课后思考:在你的当前项目中,是否存在“大泥球”式的模块?尝试用限界上下文重新划分边界,并思考每个上下文应该使用哪种映射模式进行协作。