3. 领域驱动设计基础:实体与值对象、聚合与聚合根、领域事件、领域服务

领域驱动设计(Domain-Driven Design,DDD)为《OpenAPI 领域边界》课程提供了核心的建模方法论。在将业务需求转化为 API 契约之前,必须理解 DDD 的四个基本构造块:实体与值对象、聚合与聚合根、领域事件以及领域服务。这些概念直接决定了 API 的资源粒度、状态管理方式和边界划分。

3.1 实体与值对象

实体(Entity)和值对象(Value Object)是领域模型中最基础的两种对象类型,它们的区别在于身份标识和可变性。

特性 实体(Entity) 值对象(Value Object)
身份标识 拥有唯一标识(如 ID、UUID),标识贯穿生命周期不变 没有身份标识,由属性值定义相等性
可变性 可变(Mutable),状态可随时间变化 不可变(Immutable),创建后属性不可修改
相等性判断 通过标识判断(如 userId 相同即同一实体) 通过所有属性值判断(如金额+币种相同即相等)
生命周期 有独立生命周期,可被创建、修改、删除 无独立生命周期,可被整体替换或丢弃
API 映射 通常映射为 REST 资源,拥有独立 URI 通常作为请求体/响应体中的嵌套对象
API 设计提示:在 OpenAPI 规范中,实体通常对应 object 类型且包含 id 属性;值对象也对应 object 类型,但不应包含独立标识字段,且建议标记为 readOnly: true 以强调不可变性。
实体与值对象对比 实体(Entity) User(用户) • userId: UUID(标识) • name: string(可变) • email: string(可变) • status: enum(可变) → 映射为 /users/{userId} 值对象(Value Object) Money(金额) • amount: decimal(不可变) • currency: string(不可变) • 无独立标识 • 整体替换而非修改 → 嵌套在 Order 中 vs

3.2 聚合与聚合根

聚合(Aggregate)是一组相关对象的集合,被视为数据修改的一致性边界。聚合根(Aggregate Root)是聚合中唯一允许被外部引用的实体,负责维护聚合内部的不变量。

核心原则:外部对象只能通过聚合根访问聚合内的其他对象;聚合之间的关联应使用聚合根的全局标识,而非对象引用。

聚合设计的三条关键规则:

  1. 一致性边界:一个事务只修改一个聚合,聚合内部保证最终一致性。
  2. 聚合根唯一入口:所有对聚合内对象的操作必须通过聚合根的方法进行。
  3. 引用通过标识:聚合之间通过 ID 引用,避免直接对象引用导致的级联遍历。
概念 职责 API 设计影响
聚合根 全局标识、生命周期管理、一致性守卫 映射为顶级资源端点(如 /orders)
聚合内部实体 局部标识、聚合内部行为 通常作为子资源或嵌套对象(如 /orders/{id}/items)
聚合内部值对象 属性封装、无标识、不可变 嵌套在请求/响应体中,无独立端点
聚合与聚合根结构 聚合边界(事务一致性) Order(聚合根) orderId: UUID OrderItem(内部实体) itemId: 局部标识 ShippingAddress(值对象) 无标识,不可变 Customer(外部实体) 通过 customerId 引用 外部只能通过 Order(聚合根)访问 OrderItem 和 ShippingAddress

3.3 领域事件

领域事件(Domain Event)是领域模型中已经发生的、对业务有意义的事实记录。它用于在聚合之间或限界上下文之间传递状态变更通知,实现最终一致性。

常见误区:领域事件不是 API 请求日志,也不是系统内部的技术事件(如“数据库连接超时”)。它必须是业务人员能够理解的事实,例如“订单已支付”、“库存已不足”。

领域事件在 OpenAPI 设计中的两种典型模式:

  • 事件通知(Event Notification):API 端点直接发布事件消息(如 Webhook 回调),接收方自行获取最新状态。
  • 事件溯源(Event Sourcing):API 将事件作为一等资源暴露,支持查询和重放历史事件流。
领域事件流转 Order 聚合 订单已支付 领域事件总线 OrderPaid 事件 Inventory 聚合 扣减库存 Notification 服务 发送确认邮件 事件发布 → 多个订阅者独立处理,实现最终一致性

3.4 领域服务

领域服务(Domain Service)用于承载那些不属于任何实体或值对象的领域逻辑。当某个业务操作涉及多个聚合或多个外部系统,且无法自然地归属于某个实体时,应将其建模为领域服务。

判断标准:如果业务逻辑需要操作多个聚合根,或者需要调用外部基础设施(如支付网关、物流 API),且该逻辑在实体中会导致“方法膨胀”或“职责不清”,则考虑提取为领域服务。

领域服务在 OpenAPI 中的映射方式:

  • 无状态服务端点:映射为 POST 操作,请求体包含所有必要参数,响应体返回计算结果。
  • 资源化服务:将领域服务建模为“计算资源”或“任务资源”,支持 GET 查询和 POST 提交。
# OpenAPI 3.1 领域服务示例:订单价格计算服务
paths:
  /domain-services/order-pricing:
    post:
      summary: 计算订单最终价格(含折扣、税费、运费)
      operationId: calculateOrderPrice
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                items:
                  type: array
                  items:
                    $ref: '#/components/schemas/OrderItem'
                customerId:
                  type: string
                couponCode:
                  type: string
      responses:
        '200':
          description: 价格计算结果
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PriceCalculationResult'
      x-domain-service: true
      x-aggregate-root: Order
OpenAPI 扩展标记:建议使用 x-domain-service 和 x-aggregate-root 等自定义扩展属性,在 API 契约中显式标注领域驱动设计的构造块类型,便于代码生成和文档导航。

3.5 四种构造块在 API 边界设计中的协同

在实际的 OpenAPI 设计中,这四种构造块并非孤立存在,而是共同构成领域边界:

DDD 构造块 API 资源/操作 边界职责
聚合根 顶级 REST 资源(如 /orders) 定义资源生命周期和一致性边界
实体 子资源或嵌套对象(如 /orders/{id}/items) 维护局部状态和标识
值对象 请求/响应体中的嵌套 schema 封装不可变属性,减少冗余端点
领域事件 Webhook 回调或事件资源端点 跨聚合/跨上下文异步通知
领域服务 无状态 POST 端点或计算资源 承载跨聚合的业务逻辑