领域、限界上下文与统一语言
领域驱动设计先确定一套业务模型在哪些范围内成立,再让代码、接口和团队沟通使用同一套业务语言。
1. 领域模型来自业务规则
领域是软件需要解决的业务范围,例如电商、信贷或物流。一个大领域通常还会分成多个子域:电商可以包含商品、交易、库存、履约和售后。
领域模型是对其中一部分业务规则的表达。它不等于数据库表,也不要求每个业务名词都对应一个 Java 类。模型需要回答的是:
- 哪些对象有稳定身份;
- 哪些状态变化受到业务规则约束;
- 一次操作由谁发起,又由谁保证结果有效;
- 这一部分业务与外部系统如何协作。
当业务规则很简单时,CRUD 已经足够。DDD 更适合规则复杂、概念容易混淆、业务仍在持续变化的部分。
2. 限界上下文规定模型的适用范围
同一个词在不同业务环节可能有不同含义。以“客户”为例:
- 在会员上下文中,客户关心等级、积分和权益;
- 在订单上下文中,客户可能只保留购买人标识和下单资格;
- 在结算上下文中,客户还可能代表签约主体和账期配置。
强行共用一个包含全部字段的 Customer,会让不同团队围绕同一个模型不断增加条件分支。限界上下文明确模型的边界:上下文内部保持一致;跨上下文只交换双方约定的数据和语义。
限界上下文是逻辑边界,不自动等于一个微服务。它可以先落在一个模块里,也可以由多个部署单元共同实现。是否独立部署,要继续评估变更频率、扩缩容、故障隔离和团队责任。
3. 统一语言连接需求与实现
统一语言是领域专家和开发者共同使用、并且能够落到模型中的词汇。它需要进入以下位置:
- 需求、验收条件和业务规则;
- 类名、方法名和领域事件;
- API 字段与错误码;
- 监控指标、告警和运行手册。
如果业务说“确认订单”,代码却使用 updateStatus(3),真正的规则只能藏在注释和人的记忆里。更明确的接口可以是 confirm(),由方法内部检查当前状态、支付条件和版本。
统一语言允许在不同限界上下文中出现差异。同一个词一旦语义不同,就应通过上下文名称、转换模型或显式契约区分,避免追求全公司的单一大模型。
4. 从业务变化识别边界
识别边界时,可以沿一条完整业务流程观察以下信号:
- 规则归属:哪些规则必须由同一批领域专家解释?
- 共同变化:哪些代码和数据经常因同一个需求一起修改?
- 一致性要求:哪些状态必须在一次事务内同时成立?
- 语言差异:同一个词在两个环节是否有不同定义?
- 责任归属:哪个团队可以独立决定模型和接口的演进?
边界需要通过真实需求验证。初次建模可以保守一些,把关系紧密的能力放在一起;当变化模式清楚后再拆分。过早切出服务,通常只会把尚未理解的业务关系变成网络调用。
5. 上下文之间通过契约协作
上下文图记录限界上下文之间的依赖方向和集成方式。常见做法包括:
- 上游发布稳定 API 或事件,下游按契约消费;
- 下游使用防腐层,把外部模型转换为自己的模型;
- 两个上下文共享一小段明确维护责任的内核;
- 对遗留系统先建立适配层,避免旧模型继续渗入新代码。
契约要包含字段语义、版本、幂等要求、错误处理和兼容期限。只画一张模块关系图,却允许双方直接读取彼此数据库,边界仍然无法独立演进。
6. 一个订单场景
假设交易系统收到“取消订单”请求:
- 订单上下文判断订单状态是否允许取消,并记录取消结果;
- 库存上下文根据订单事件释放预占库存;
- 支付上下文判断是否需要退款并推进退款流程;
- 会员上下文根据最终结果调整积分。
“订单取消”在订单上下文中是一条受状态机约束的命令,在库存和支付上下文中则是触发后续工作的事实。各上下文保留自己的模型,通过事件标识、版本和幂等规则完成协作,不共享一个可被所有服务修改的 Order 对象。
7. 常见问题
7.1 DDD 是否要求使用充血模型
DDD 要求关键业务规则有清楚的归属。复杂规则可以放在实体、值对象、聚合和领域服务中;简单查询或 CRUD 没有必要包装出大量空壳对象。判断标准是规则是否容易找到、能否独立测试,以及状态是否只能通过合法操作改变。
7.2 限界上下文是否应该独占数据库
它应该拥有自己的数据定义和写入权。物理上可以暂时共用一个数据库实例,但表的所有权、写入入口和迁移责任要明确。跨上下文直接更新表,会重新建立隐式耦合。
8. 面试题
8.1 如何理解 DDD,项目中怎样识别限界上下文
出现公司:阿里巴巴
考察重点
- 领域、子域、限界上下文和统一语言之间的关系。
- 业务边界与服务部署边界的区别。
- 如何用规则、变化、一致性和团队责任验证边界。
相关内容:第 1 节“领域模型来自业务规则”至第 5 节“上下文之间通过契约协作”。
参考回答
我会先选一条核心业务流程,与领域专家梳理业务词汇、状态变化和必须成立的规则。语义一致、经常共同变化并且需要同一事务维护的内容,先放在同一个模型中;同一个词出现不同含义,或者规则、数据所有权和团队责任已经分开时,建立不同的限界上下文。
上下文内部让需求、代码和接口使用统一语言;上下文之间通过明确 API、事件或防腐层转换模型。限界上下文先解决模型边界,是否拆成微服务还要根据独立部署、扩缩容、故障隔离和运维能力决定。最后用后续需求的变更范围、跨边界调用和一致性成本持续校验边界,而不是一次建模后永久固定。