主题
DDD 生产检查清单
适用 DDD 战略设计与战术落地。每条都可勾选,括号内是判断依据;这类方法论不产生配置默认值,清单只落到可验证的工程动作。 只列「不做会出事」的动作,机制解释见各篇正文。
上线前
- [ ] 上下文图存在且数据归属清楚:每个限界上下文画出数据归属,库隔离级别按一致性、团队所有权、事务成本写明;目标状态可独立库,过渡期允许同实例不同 Schema,但写入口必须唯一,其他上下文只能读视图或调 API;两个服务同时写同一张表就是边界没划开;DDD 只用于核心域,纯 CRUD/报表模块保持简单分层。
- [ ] 聚合边界写明不变量、事务与引用规则:每个聚合根列出「它守护哪些不变量」,一个聚合一个事务,代码里跨聚合的
@Transactional数量应为 0;跨聚合只按 ID 引用,不持有其他聚合的对象,外部只能通过聚合根方法修改内部对象。 - [ ] 业务规则在模型里,Service 只做编排:状态流转、金额计算、不变量校验必须能在实体/值对象/聚合方法中找到;评审标准是
order.pay()而不是orderService.pay(order)。 - [ ] 领域事件与状态同事务:选定 Outbox / 事务消息 / CDC 之一,确认「改状态」和「写事件」是一个本地事务;消费端按业务键做幂等(唯一索引或状态机),事件结构带版本号。
- [ ] 依赖方向正确:领域层不出现 Spring/MyBatis 注解与 SQL,仓储接口定义在领域层、实现在基础设施层;仓储方法只服务聚合生命周期,报表与列表查询走独立读模型。
- [ ] 外部系统走防腐层:对接遗留系统、第三方、MCP/Skill 时统一做 Schema 翻译、字段校验和异常兜底;核心域模型里不出现外部系统的字段命名。
运行中
- [ ] 跨上下文调用链长度:统计一次业务请求经过的服务数与 RPC 跳数;一次业务串 5 个以上服务,或两个上下文直接读写同一张表,按分布式单体处理。
- [ ] 事件可靠性:Outbox 未发送条数与最老一条的年龄、MQ 消费延迟、重复消费率、死信数量;最老事件年龄持续增长说明投递链路已断。
- [ ] 聚合争用:热点聚合的行锁等待、乐观锁失败率、单事务持有时长;同一聚合根高频冲突说明聚合过大或读写没分离。
- [ ] 模型漂移:定期扫描领域层的框架依赖与 DTO 命名,检查仓储方法是否出现报表/统计语义,检查同一业务概念是否出现多种叫法;任一出现即视为边界或分层失守。
故障时
- [ ] 状态已提交但下游没收到事件 → 查 Outbox 表中该聚合的未发送记录与创建时间、MQ 发送日志 → 判断事件是否与状态同事务写入;不是就补事务边界,不要手工补发后就算完。
- [ ] 下游重复处理或数据翻倍 → 查消费端幂等键、唯一索引、状态机校验和重试记录 → 确认重复投递被业务键挡住;没有幂等键就补,不要靠「MQ 只投一次」。
- [ ] 热点聚合锁等待、接口超时 → 看聚合大小、单事务跨表数与并发冲突率 → 拆分聚合或把非强一致逻辑改为事件异步,不要用调大锁等待时间掩盖模型问题。
- [ ] 一个需求要改 3 个以上服务,或两个服务同时写一张表 → 画当前依赖图与数据归属 → 判定为边界划错,重新划上下文;继续加 RPC 或共享表只会把分布式单体做得更牢。
- [ ] 规则又回到 Service、领域对象只剩 getter/setter → 抽查状态流转与金额计算方法的位置 → 判定贫血模型复活,按「规则归属」评审打回,不要只改类名。