DDD 系列:领域、子域和限界上下文

📅 2026/7/12 7:48:19
DDD 系列:领域、子域和限界上下文
上一篇文章中我们学习了贫血模型和充血模型解决了“业务规则应该放在哪里”的问题。但一个系统里还有订单、商品、库存、支付等很多业务。哪些对象应该放在一起边界在哪里光靠充血模型回答不了。这一篇我们继续学习 DDD 战略设计中三个基础概念领域、子域和限界上下文。一、为什么要划分业务边界我们平时最熟悉的是技术分层controller service mapper entity这种分法解决的是代码放在哪里却没有告诉我们业务应该怎么拆。以电商系统为例商品、订单、库存和物流都可能用到“商品”但关注点完全不同商品中心关心标题、类目和上下架状态库存关心 SKU、可用库存和仓库订单关心下单时的名称、成交价格和数量物流关心重量、体积和配送要求。如果所有模块共用一个Product它很快就会变成谁都依赖、谁都不敢改的万能对象。DDD 会先划分业务问题和模型的适用范围再考虑代码、数据库和服务怎么落地。领域、子域和限界上下文就是用来说明这些边界的。二、领域与子域领域对应Domain简单来说就是我们需要理解和解决的一块业务范围。例如“电商”是一个领域其中包含商品、订单、库存、支付和物流等业务问题。领域描述的是业务本身不是 Java 包、数据库或技术框架。领域的大小是相对的。从电商平台看支付可能只是其中一部分但对支付公司来说支付本身又是一个大领域还能继续拆成收单、退款、对账和清算。一个大领域拆出的业务部分叫作子域。以电商领域为例可以粗略拆成商品子域订单子域库存子域支付子域物流子域。每个子域解决一类相对完整的问题。订单负责交易生命周期库存负责增加、锁定、扣减和释放物流负责发货和运输跟踪。子域也可以继续拆但不要为了套 DDD 概念无限拆分。拆分的目标是让职责更清楚如果拆完每块只剩一两个增删改查方法反而会增加协作成本。三、核心域、支撑域和通用域不同子域对业务的价值不同。DDD 通常把它们分为三类用来判断有限的人力应该投入在哪里。类型说明示例核心域体现主要业务竞争力规则通常复杂且持续变化交易平台的订单撮合支撑域支撑核心业务并带有企业自己的规则商家结算、售后审核通用域多数系统都会遇到通常有成熟方案认证、短信、文件存储核心域不是固定的模块名单。同样是电商平台零售平台可能重视交易和营销物流平台可能重视履约调度二手平台可能重视验货和担保交易。判断时可以问三个问题它是不是主要价值来源规则是否复杂且经常变化市面上是否已有成熟通用方案。这里说“通用”不代表它不重要。认证和权限同样重要只是它们通常不能体现当前业务与竞争对手的差异可以优先使用成熟产品或公共能力。分类也不是常量。随着公司战略变化支撑域可能变成核心域自研能力也可能被公共平台替代。四、限界上下文与通用语言子域划分了业务问题但还需要给模型规定明确的适用范围这就是限界上下文Bounded Context。在一个限界上下文内业务术语有明确含义模型和规则保持一致并由明确的模块或团队维护。“限界”限制的是模型生效的范围不是限制业务沟通。一个上下文中的模型出了边界不应该被默认复用其他上下文要按照自己的业务重新表达。还是以“商品”为例商品上下文维护标题和上下架状态库存上下文使用库存项表达 SKU 和库存数量订单上下文保存下单时的商品快照物流上下文只关心发货属性。这意味着我们不需要一个万能Product。订单上下文可以定义自己的OrderItempublicclassOrderItem{/** 商品 SKU 标识用于记录本次下单选择的具体规格 */privateLongskuId;/** 下单时的商品名称商品改名不会影响历史订单 */privateStringproductName;/** 下单时的成交单价用于保证订单金额可追溯 */privateBigDecimaldealPrice;/** 本次购买数量 */privateIntegerquantity;}它只保留订单需要的信息不必带上商品详情、搜索关键词等字段。不同上下文需要协作时通过接口或消息传递必要数据而不是直接修改对方的表和对象。这里还要注意限界上下文不等于微服务。限界上下文是业务语义和模型边界微服务是部署和运行边界。单体项目也能通过模块和接口建立多个限界上下文两个服务如果共用表、互改数据业务边界仍然是混乱的。限界上下文内部还需要通用语言Ubiquitous Language。它要求业务、产品、开发和测试对关键术语有一致理解。例如“取消订单”和“超时关闭”可能是两个动作前置条件和后续处理也不同代码就应该直接表达order.cancelByUser();order.closeByTimeout();通用语言最终要落实到需求、类名、方法名、状态枚举、接口、事件和测试用例中而不只是停留在会议里。五、子域和限界上下文的关系这两个概念很容易混淆可以先记住子域是对业务问题空间的划分回答“有哪些不同的业务问题”限界上下文是模型和解决方案生效的边界回答“用哪套模型解决这些问题”。理想情况下一个子域通常对应一个限界上下文。但历史系统里订单和库存可能还挤在同一个上下文复杂的订单子域也可能拆成多个上下文。这种对应关系还会受业务复杂度、团队职责和系统现状影响。划分的目标是得到清楚的业务边界不是追求形式上的一一对应。所以不能机械套用“一个子域等于一个限界上下文更等于一个微服务”。真正需要检查的是模型语义是否一致规则是否有明确归属边界之间是否通过清楚的契约协作。六、从电商案例看边界如何落地假设用户下单后系统要锁定库存、处理支付并通知仓库发货。可以先按业务能力得到下面几个上下文限界上下文主要模型主要职责商品上下文商品、类目、SKU维护展示和销售属性订单上下文订单、订单项创建交易并维护订单生命周期库存上下文库存项、锁定记录管理可用、锁定、扣减和释放支付上下文支付单、退款单处理支付和退款状态物流上下文发货单、物流轨迹管理发货和运输过程下单时订单上下文获取必要的商品信息库存上下文判断并锁定库存订单保存成交快照支付上下文处理支付支付结果再推动订单和履约流程。这里的重点不是一定要使用接口还是消息而是每个上下文只维护自己的规则和数据。对于已经运行多年的 MVC 项目可以先做四件事查找同名但含义不同的业务词例如各种status和“完成”核实哪些表被多个模块直接修改并结合调用链、SQL 和日志确认真实用途找出经常一起变化的代码判断它们是否属于同一业务边界先明确模块职责和跨边界接口再决定是否拆库、拆服务。老系统里可能存在补偿任务和历史兼容逻辑不能看到跨模块更新就直接删除。先核实现状再逐步收紧边界更稳妥。业务边界也不能只由开发人员根据代码目录决定。产品、业务、开发和测试需要一起确认流程和术语否则技术上看着整齐模型却可能与真实业务不符。七、常见误区和验证方法划分边界时常见误区主要有这些按数据库表划分子域把一个实体当成一个限界上下文用微服务数量证明业务边界合理为了减少字段重复让所有模块共用一套 Entity只让开发人员根据代码目录划分业务边界。边界划完后可以检查每个子域解决的问题能否用一句话说清楚同一上下文内的关键术语是否只有一种含义每条核心规则是否有明确归属不同上下文是否直接修改对方数据跨边界传递的是明确契约还是内部对象一个上下文变化时是否经常要求其他上下文同步修改。如果职责、术语和规则仍然说不清边界就需要继续调整。领域知识本来就是逐步积累的不必追求一次划到完美。最后简单总结一下领域是一块需要理解和解决的业务范围子域是对它的继续拆分核心域、支撑域和通用域体现不同的业务价值和投入优先级限界上下文规定业务语言和模型生效的范围子域属于问题空间限界上下文属于模型和解决方案边界划分边界要看真实业务、数据归属和调用关系不能直接套数据库表或微服务。到这里我们已经从“业务规则放在哪里”走到了“哪些业务应该放在一起”。下一篇继续学习战略设计看看这些边界怎么进一步指导系统拆分。