1. 第二章在整本书里的位置战略设计为什么排在战术前面1.1 你可能也在犯“先建表再谈模型”的毛病我早年做系统设计基本流程是先打开数据库工具设计两张核心表再根据表去反推服务怎么拆。这种习惯在一个独立小项目里看着挺顺可等系统长出订单、库存、营销、支付这些相互咬合的子系统后问题就开始集中爆发订单状态改成已完成库存那边到底该不该扣两个团队挂在嘴边的“客户”到底是不是同一个群体谁对用户余额负有最终解释权这些问题全部不在代码层面也不在聚合和实体这些“战术设计”层面而是发生在语言和边界层面。也就是说光把某个聚合设计得多漂亮根本救不了系统级的混乱。这本书在头两章就讲“战略设计”其实是在帮我们这些习惯了“单模块视角”的人把视野往上拉一拉先看清地图再讨论城市里的路怎么修。1.2 战略设计和战术设计到底差在哪很多人听到“领域驱动设计”第一反应是实体、值对象、聚合、领域事件、仓库、工厂这些词汇。这些内容在书里被统称为战术设计它们解决的是某一个模型内部怎么做的问题——这个聚合适不适合处理订单状态流转那个值对象是不是不可变的领域事件该在哪个原子操作里发布。而战略设计回答的是完全不同的另一组问题系统里到底应该存在哪些业务模型每个模型应该由哪个团队或模块来负责模型与模型之间用什么关系协作一句话概括战略设计负责划地盘和定语言战术设计负责在划定好的地盘里盖房子。有意思的是很多人反向操作先花大力气研究聚合怎么做再回头模糊地拍脑袋定“要不要用微服务”。这样做事系统边界和团队边界经常互相冲突代码里到处是跨上下文跳来跳去的调用。第2章把“限界上下文”和“通用语言”这两个战略设计的基础工具摆出来就是想在动手写代码之前先把地界划清楚。1.3 为什么第二章能撑起整个战略设计的地基老话说如果两拨人连说的事都不是同一件那做什么架构评审都是鸡同鸭讲。限界上下文要解决的就是每个业务模型该从哪个边界切开通用语言则确保在这个边界内部所有人都用同一套词汇与规则去理解模型。这两者一个管“墙”一个管“墙内的话”是战略设计里几乎所有后续动作的前提。一旦你搞定了限界上下文和通用语言后面再谈上下文映射、防腐层、开放主机服务就有了实实在在的落脚点。这本书把这一章放在如此靠前的位置我个人认为不是偶然。它想提醒我们建模的真正起点不是UML图不是类图而是业务语言和行为边界的识别。2. 通用语言——战略设计的第一张图纸2.1 一次对齐会议上的窘境我做过的一个模拟项目里业务方口中的“下单”和研发口中的“下单”本质上并不是一回事。业务方理解的“下单”是一个用户把商品放进购物车后点击提交的完整动作研发理解的“下单”是OrderService里创建的订单记录仅覆盖订单头信息和明细落库。于是每次评审产品说“下单要校验库存”研发说“下单只管生成记录库存是另一个服务的事”。双方都认为自己没有错但代码交付后产品一顿操作发现超卖的现象偶尔出现又反过来说研发漏了需求。这就是典型的不存在通用语言的情况。同一个词汇在业务侧、产品侧、开发侧代表了不同的事实。不夸张地讲项目里很多所谓的“需求理解偏差”追到底都是词汇定义偏差。第2章反复强调通用语言本质上就是在说必须在同一个限界上下文内建立一套精确、无歧义、共享的术语体系。2.2 通用语言的三个核心特征根据我的实践体会判断一种语言能不能算“通用”主要看三点。第一点是精确性。一个术语只能对应一个明确的业务含义不能今天表示创建动作明天又表示状态。比如“已支付”和“支付成功”如果同时出现在代码注释、接口文档和测试用例里迟早会有人混着用。第二点是共享性。通用语言不是架构师写在PPT里的精美词汇而是业务人员、产品经理、开发、测试在对话中自然使用的语言。如果业务方说“客单”研发说“GMV”那对话成本就高得离谱。术语必须由所有角色共同认可并且愿意长期使用。第三点是动态性。通用语言不是开工第一天开完会就冻结的文档。随着业务深化你会不断发现旧术语无法覆盖新场景需要补充、拆分甚至废弃某个词汇。这个过程要持续发生否则语言会腐烂。2.3 建立和维护通用语言的实操方法很多团队问通用语言到底怎么建我给一些实际可执行的建议。第一步找一个业务规则相对集中的角落把所有角色组织起来做一个术语收集。不需要搞太长周期半天到一个工作日就够了。方法很简单让每个人都写出一批自己工作中最常用的业务词汇贴到白板上然后逐个讨论把同一含义不同叫法的词合并把含义有冲突的词单独拉出来重点对齐。第二步把最终确认的词汇表沉淀成一个团队内部可搜索的词条库。这个工具不需要多高级团队用自己的云文档就能做。关键是每条术语要写明定义、适用范围、不适用范围、相关术语。哪怕只维护三十到五十个词条对对齐需求的帮助都是巨大的。第三步在代码和文档里持续贯彻。类名、方法名、字段名、数据库表名全都优先使用通用语言里的术语。当代码里的命名跟通用语言冲突时不要先改代码而是先回到团队里确认通用语言是否需要修订。语言变了代码再跟着变顺序不能反。我自己踩过坑因为嫌某个类名太啰嗦擅自改了个“简洁”的写法结果后来业务方说“你说的这个操作我们根本不认识”。吃了亏才明白命名的服务对象不是“代码洁癖”而是沟通效率。3. 限界上下文——语言要在墙内住3.1 模型不是全局共享的有些团队把领域驱动设计理解成“建一套完美的大模型所有服务共享一份领域对象”。我在项目现场看到过太多类似的结构一个系统里放了个公共领域模型项目里面放了User、Order、Product等各种根实体所有服务一起引用。这个做法听起来很美好——天下模型大一统不重复、不浪费。实际情况却是灾难。不同模块对“用户”的关注点完全不同销售团队关注用户的联系方式、购买偏好账务团队关注用户的账户余额、账单记录风控团队关注用户的历史行为、风险评分。把这些属性塞进同一个User类里最终这个类会变成几百个字段的巨型对象每次有人改动都会影响所有使用方发布时谁都不敢动它。限界上下文就是在给模型砌墙。每个上下文内部只保留与该业务场景相关的概念和行为。同一个“用户”在销售上下文里叫“顾客”在账务上下文里叫“客户”看似词汇不同但恰恰因为被边界隔开才能各自对模型做出最贴合本场景的表达不用互相迁就。3.2 识别限界上下文的几个信号对刚开始实践的人来说最头疼的是“边界到底画在哪儿”。我的经验是可以靠几个信号辅助判断。第一个信号是术语变化。当你发现同一个词汇在系统不同区域的含义、规则或属性发生了本质变化那就要警惕这里可能存在两个不同的限界上下文。比如“订单”在前台下单时包含金额试算和优惠明细在仓储履约时关注货品锁定和包装配置这两种场景下的“订单”很可能该被拆成两个模型。第二个信号是业务规则的变化频率。不同区域的业务规则如果变化节奏差异很大把它们强行放在同一个上下文里会让每次改动互相干扰。比如商品信息的录入规则相对稳定而促销规则可能每个季度都在变。两者混合在一起一次促销规则调整就可能影响商品上下架代码。第三个信号是团队归属。当一个限界上下文被两个团队同时修改时边界大概率划错了。团队是人的边界模型如果没有跟人的组织结构形成呼应维护起来会非常痛苦。3.3 一个识别边界的实战演示我拿一个虚拟的电商项目来演示怎么识别。假设系统里的核心域是订单履约支撑子域包括商品目录、库存管理、促销计算、支付对接、物流追踪。初步看可以圈出以下几个候选限界上下文订单上下文、商品上下文、库存上下文、促销上下文、支付上下文、物流上下文。但注意这不是说“几个子域就对应几个上下文”还需要看团队和实际业务系统的边界。比如如果这个电商平台体量不大促销计算和订单创建通常在一个系统内完成那就可以把促销逻辑作为订单上下文里的一个模块而不是单独拆出去。真正拆分的冲动应该来自团队规模、部署独立性和规则复杂度。哪怕从“子域”视角来看促销确实是独立支撑域但当前阶段没有独立发布的必要那就让它先待在订单上下文里。反过来如果这家公司很大促销团队和订单团队各管一套系统促销上下文就完全有必要独立出来并明确促销模型的归属和对外接口。这就是战略设计的本质不是找一条“最标准”的分割线而是找一条最适合当前组织结构、发布节奏和演进需求的线。3.4 限界上下文与微服务并不总是一对一很多微服务文章把限界上下文等同于微服务边界然后尝试让一个上下文对应一个服务。实际上限界上下文首先是一种逻辑边界它决定模型的划分和语言的一致性微服务是一种部署边界它决定进程的独立性与故障隔离。在团队规模不大时一个限界上下文内部可能包含多个模块并打包成一个可独立部署的单体应用。这完全合法。真正重要的是不要让一个微服务内部塞进多个限界上下文也不要让一个限界上下文被迫拆到多个进程里除非你有明确的性能扩展或团队自治需求。我见过的最拧巴设计是某团队把订单上下文拆成订单读服务、订单写服务、订单对账服务三个微服务。结果代码里出现了共享领域对象改了订单状态枚举要对所有相关服务做一遍兼容测试发布顺序还得严格编排。这就是典型的被“微服务必须拆小”的理念绑架。遵守限界上下文比盲目追求服务数量重要得多。4. 上下文映射——让模型之间的关系显式可见4.1 合作关系与共享内核限界上下文之间不是孤岛战略设计必须考虑它们如何协作。书中提供的上下文映射模式本质上是在描述不同类型的关系每种关系的取舍背后都藏着对团队协作成本和模型演进自由度的一次权衡。两个上下文紧密合作时可以选择合作关系或共享内核。合作关系最典型的表现是团队商定一个联动的迭代计划每次修改都要协调、验证保持上下游的集成一致性。这种关系成本很高适合双方必须频繁调整且谁也离不开谁的情况。共享内核则是指两个上下文共同维护一块小而精的模型代码比如说一个共享的领域事件包或一个共享的领域对象定义。听起来很美但实践中要严格控制范围只放那些“改了会影响双方核心行为”的最小集合。我参与过一个项目团队一开始把支付单状态枚举放进了共享内核后来发现各种五花八门的状态都往里加几个月后共享内核变成了一个大杂烩。后来我们把共享内核缩减到只剩支付成功的领域事件定义其余全部移到各自上下文中重新回归清爽。4.2 客户-供应商与顺承关系当一个上下文依赖另一个上下文提供能力时典型的关系是客户-供应商。下游是客户上游是供应商双方通过计划和合同约定接口。供应商需要给下游可预期的发布计划客户则需要在自己的节奏里处理上游的变化。这种关系里最重要的是集成方式要稳定不能上游每次调整内部模型都把下游打穿。顺承关系则是更“现实”的一种选择。当两个团队没有足够精力维护复杂的协调机制尤其是下游团队人员少、时间紧没法对上游提出严格要求时下游可以主动放弃自己的模型设计直接遵循上游的模型来写代码。这个过程叫做“顺承”听着有点无奈但在很多系统里真实存在接入外部能力时你很难说服对方为你改数据结构。顺承关系的关键是这种“被迫统一”不能被伪装成优雅设计。要用代码隔离自己的核心逻辑尽量把顺承部分压缩在一个薄薄的适配层里别让外部模型的概念渗透到整个系统中。4.3 防腐层、开放主机服务与发布语言防腐层是战略设计里最出名的模式也是被用得最多的一个。当你要集成的外部系统模型跟你自己的模型不一致且你无法控制它时在集成处加一层翻译器让外部概念在你的边界内被转换成自己的领域模型。这就像给房子装一道防风门室内保持自己的空气环境。防腐层的实操要点在于“翻译”要彻底。我见过很多团队在Controller层调用外部接口后直接把外部DTO传给自己的Service最后领域模型里到处都是外部系统的字段名。防腐层失效的原因通常是懒写完外部调用后顺手在内部方法里用外部对象做参数传递结果屋内屋外逐渐打通。要守住防线就必须在防腐层内部完成外部对象到内部对象的一一映射并且禁止外部对象越境。开放主机服务和发布语言则是一对好搭档。如果上游团队希望多个下游团队都能轻松集成可以把自己系统的能力包装成一组稳定的接口协议这组协议就是一个开放主机服务同时用一套共享的、跨上下文可理解的格式作为发布语言比如统一的事件模型、标准API规范。这样下游不必关心上游内部模型双方通过公开协议协作。我建议每个对外提供能力的系统都认真考虑这三种模式的组合内部模型内部用对外能力放到防腐层后面用发布语言暴露下游按开放协议接入。这套打法能极大降低跨系统集成的痛苦。4.4 分离方式也是一种合法选择上下文映射九种关系里分离方式最容易被忽视。它指两个上下文之间不需要任何协作模型完全独立各走各路。很多开发人总觉得“不集成一下好像系统就不完整”于是把明明不需要关联的业务逻辑强行打通。实际上明确地说“这俩没关系”比设计一套复杂的异步消息要好得多。判断要不要分离就看业务上是否真的存在必须保障的信息流。没有就大胆隔离。分离方式不是逃避而是战略上的一种清醒。4.5 画上下文映射图时的记录要点上下文映射图是战略设计的重要产出。画图的要点是简而且显把限界上下文画成圆角矩形里面的模型画成简单条目上下文之间用连线表示关系在线旁边标注模式名称和方向。我通常会补三样信息第一是每个上下文当前所处状态比如“演进中”还是“稳定”第二是每个上下文对应的核心团队第三是关键集成点的协议类型比如“REST同步”还是“事件异步”。这张图不需要画得很复杂关键是所有人开会时都看着同一张图讨论问题而不是每个人在脑子里各画各的。5. 战略设计从哪里开始——我验证过的落地步骤5.1 用事件风暴画出业务全貌如果你接手一个陌生的老系统我建议的第一步不是对着代码库看类关系而是组织一次事件风暴工作坊。方法形式上很简单准备一面足够长的墙和一批橙黄色便签让业务、产品、开发坐在一起按时间顺序贴出领域事件。比如一个订单履约流程从“订单已提交”贴到“订单已取消”“订单已发货”“订单已签收”。事件贴完再给每个事件补上触发它的业务命令、产生数据的读模型、依赖的外部系统以及每个环节里的业务规则。这个过程能让团队在一天内对系统的业务全貌形成共同认知。事件风暴最大的价值是提供一个话题的安全空间。大家围着墙排序事件时很容易就辨识出哪些概念只在某个局部场景有意义哪些词汇在多个场景中含义不一致。这些信号就是后续划分限界上下文的第一手线索。5.2 按子域圈定上下文候选完成事件风暴后接下来是给整套业务圈定核心域、支撑子域和通用子域。核心域是决定产品竞争力的地方比如一家电商平台的核心可能是履约体验一家风控公司的核心是反欺诈决策。找核心域的方法是问一个简单问题整个产品如果砍掉哪块业务会彻底失去竞争力。然后把子域跟候选限界上下文做匹配。需要明确的是子域是分析业务时用的“问题空间”限界上下文是设计解决方案时用的“解决空间”。它们通常高度相关但不一定一对一。小团队可以把多个子域合并到一个限界上下文里大团队也可能把一个复杂子域拆成多个上下文。我的一般原则是至少给每个核心子域一个独立的限界上下文支撑子域和通用子域则按团队规模、发布频率去适度合并。5.3 花半小时维护一次上下文映射图上下文映射不是一次性的规划产出需要随业务演化持续更新。我建议把它当作一个迭代任务每次迭代评审时留出半小时确认三件事有没有新的上下文出现已有上下文之间的关系有没有变化某条集成协议是否已经换成更合适的方式为了降低维护成本一定要让上下文映射图跟代码形态对齐。理想情况下代码目录的第一层结构就应该跟限界上下文保持一致。比如订单上下文对应代码里的order目录商品上下文对应catalog目录。这样新同事看代码结构就能反推出地图地图里的更新也能快速落实到代码组织里。如果地图和代码结构长期不一致团队就会慢慢遗弃地图。那些“画画只是为了给架构师过审”的项目图纸挂在墙上落灰代码却在另外一条逻辑线上野蛮生长。要让图活下来最好的办法就是让它成为代码审阅、迭代规划里必须出现的一部分。5.4 把通用语言写进代码和文档很多团队把通用语言当成日常口头沟通的约束却忘了让代码本身讲这套语言。代码里的命名、注释、接口文档都应该服务于同一个目标让读者把代码和真实业务概念轻松对应起来。我做的模拟项目里曾经把一个领域服务命名为CustomerRelationAssessor理由是它负责判断客户关系状态。后来业务方把这个概念叫“客户归属判定”我立刻把类名改成CustomerOwnershipChecker同步修改了所有相关注释和接口字段。动作不大但业务方后来看代码评审时能一眼看出那段代码在做什么沟通成本降了一大截。这里有个给新手的提示不要为了让代码“显得高级”使用晦涩的英文转译。通用语言说的是业务方听得懂的词代码命名优先用业务词汇的直译跟业务词汇保持一一对应远比漂亮的缩写重要。6. 常见问题与排查技巧实录6.1 把限界上下文当成模块边界画了两层皮新手最常见的错误是把限界上下文理解成代码里的“模块分包”随便画个内部包名就算建了上下文。实际上限界上下文必须拥有模型的定义权和发布权。如果两个模块在代码里共用同一套实体类还口口声声说它们是两个上下文那骗的只有自己。判断边界的有效性可以做一个测试看修改一个模型时是不是必须检查另一个“上下文”的所有调用。如果是说明边界破裂。这不是说隔离要绝对物理化而是说在逻辑层面“改了那个上下文我根本不用关心这边”才是限界上下文存在的意义。6.2 试图让所有团队说同一套“全局语言”反向的错误则是试图建立一个全局统一的语言。有些领导喜欢强调“统一中台词汇大家都要用同一套业务定义”这种想法在跨域系统里不仅不现实而且有害。因为不同领域对同一术语的定义天然不同强行统一只会把词汇抽象得无比空洞。通用语言的前提是限界上下文先划界再定义语言顺序不能反。你在一个上下文里说“客户”在另一个上下文里也允许对“客户”有不同定义关键是双方都清楚自己当前处在哪个上下文、说的是哪套语言。这种“各说各话但不互相干扰”的状态才是战略设计真正想要的。6.3 防腐层泛化到所有集成点防腐层确实好用但它不是万能药。对于自己可以控制的内部上下文之间更适合直接采用开放主机服务或客户-供应商模式而不是每个接口都包一层翻译器那样会造成大量重复转换代码。我有一个判断口诀当两个上下文遵循同一个发布语言时就不需要防腐层当外部模型和内部模型无法达成一致时才需要防腐层把语言差异吃掉。一味兜底翻译只会让系统里到处是语义转换的样板代码维护成本直线上升。6.4 战略设计常见问题速查表问题现象可能原因排查方向两个团队频繁互相改代码边界跟团队归属不一致重新划定限界上下文或调整团队分工公共模型越来越大没人敢改不同模型被强行塞进一个公共类按上下文拆分公共类缩窄共享内核范围业务词汇和代码命名对不上没有维护通用语言词条库建词汇表把代码命名拉回业务词汇改一个外部接口影响一大片防腐层失效外部对象到处漂移检查外部DTO是否越过了防腐层边界上下文映射图跟代码结构完全脱节图没随迭代更新或代码结构未对齐让地图成为迭代评审的一部分6.5 我的一些独家心得最后分享几个自己在多次尝试后沉淀下来的体会。第一个战略设计要循序渐进不要试图一次画全所有上下文。先画出你最有信心、业务价值最高的核心域边界把上下文映射跑顺再逐步覆盖边缘子域。先小后大才能持续获得反馈。第二个用事件风暴解决“谁跟谁有关系”的分歧特别有效。大家如果判断不了两个上下文是否需要集成就模拟走一遍业务事件流程看信息是否需要穿过边界。没有信息流转就直接用“分离方式”。第三个通用语言词条库更新频率要高于代码评审频率。只要业务方说“这个叫法我们已经改了”最迟一两天内就要把词条和代码命名一起更新。拖得越久旧语言固化进潜意识再改就越难。此书第2章读起来理论味浓但只要你带着真实项目去实践会发现它说的每一句都来自现实踩坑。限界上下文和通用语言这两把尺子值得每个想做好企业系统的开发者和架构师反复拿着去量自己的项目。