跳到内容

20.3 上下文集成:领域事件、ACL 与模型演进

限界上下文之间必须交换信息,但不应共享同一套内部模型。集成边界负责翻译语言、隔离变化,并把本地事务产生的事实可靠地传播出去。

领域事件与集成事件分开

领域事件表达上下文内部已经发生的业务事实:

java
public record RegistrationConfirmed(
    TournamentId tournamentId,
    PlayerId playerId,
    int confirmedCount
) implements DomainEvent {}

它可以驱动同一上下文内的规则,但不一定适合作为公开消息。集成事件是面向外部消费者的稳定契约,可能隐藏内部字段、改变命名并增加版本元数据:

json
{
  "eventType": "TournamentEntryConfirmed",
  "schemaVersion": 1,
  "entryId": "entry-92",
  "tournamentId": "tournament-7",
  "participantId": "participant-42",
  "confirmedAt": "2026-08-03T10:15:30Z"
}

把内部类直接序列化上 broker,会让重构字段变成跨团队破坏性变更。

事件何时发布

聚合可以在执行业务行为时记录领域事件,但外部发布应在事务成功之后,并通过 Outbox 保留可靠发布意图:

text
聚合产生领域事件
  → 应用服务保存聚合
  → 同一事务写 Outbox 集成事件
  → 提交后 relay 发布

如果事务回滚,不能对外发布“报名已确认”。如果 relay 重复发布,消费者必须幂等。

Anti-Corruption Layer 保护本地语言

假设遗留计费系统用 ORDCUS_NO 和状态码 7 表示已授权订单。报名上下文不应让这些概念扩散进领域模型:

java
final class LegacyBillingAdapter implements PaymentAuthorization {
    private final LegacyBillingClient client;

    public Authorization authorize(PaymentRequest request) {
        LegacyOrderResponse response = client.createOrder(toLegacyOrder(request));
        return switch (response.statusCode()) {
            case 7 -> Authorization.approved(response.orderNo());
            case 9 -> Authorization.declined(response.reasonCode());
            default -> throw new BillingUnavailable();
        };
    }
}

ACL 不只是 DTO Mapper。它还翻译错误、身份、时间、单位、幂等和一致性语义。若上游无法区分“拒绝”和“暂时不可用”,ACL 也不能凭空制造可靠语义,只能显式暴露不确定性。

Shared Kernel 要极小且共同治理

两个上下文共享少量模型或代码时,任何变化都需要共同协调。Shared Kernel 只适合真正稳定、双方共同拥有的部分,例如经过治理的标识类型或协议 schema。

把全部 common-domain 放进共享包会重新形成分布式单体。复制一个简单值类型,有时比永久共享演进成本更低。

模型会随认知演进

领域模型不是一次工作坊定稿。生产反馈可能暴露:

  • 同一个词实际包含两个生命周期;
  • 一个聚合因并发冲突过多需要拆分;
  • 原以为最终一致的规则其实要求预留;
  • 上下文之间的调用方向与团队责任不匹配;
  • 支持人员需要模型中不存在的中间状态。

演进步骤可以是:

  1. 用实例和事故描述当前模型的失败;
  2. 与领域专家更新语言和不变量;
  3. 调整代码边界与契约;
  4. 用 expand–migrate–contract 迁移数据;
  5. 更新事件、监控、手册和团队责任;
  6. 删除旧语言和兼容层。

什么时候不需要完整 DDD

数据录入、简单 CRUD、短寿命内部工具或成熟通用领域,可能只需要清晰分层和模块边界。DDD 的建模投入适合业务规则复杂、语言歧义高、变化长期且错误代价大的核心领域。

不要用战术模式把简单问题复杂化,也不要因简单外围功能不需要 DDD,就否定核心领域进行深度建模的价值。

第 6 卷专业链路回顾

text
构造与契约
→ 需求、架构、测试、重构和交付
→ 设计原则与对象模式
→ 分层、端口和依赖规则
→ 模块化单体与服务提取
→ 韧性、一致性、部署和事件
→ AI 系统、API 契约与领域边界

这条链路的共同目标是:把变化、失败和责任限制在可理解的边界内,并用测试、契约和运行反馈证明边界有效。

参考资料

Built with VitePress | Software Systems Atlas