别急着改代码:先画子域地图,再动第一行
这是「DDD 实战系列」的第 2 篇。第 1 篇我们聊了"业务规则要有唯一的家",解决了"模型内部怎么写"。这一篇往上抬一层:在写任何模型之前,业务这块大版图,到底该从哪下刀?
一、先回答一个问题
如果现在让你负责的那套系统砍掉一半功能,你先砍哪一半?
我拿这个问题问过不少团队,大多数人会愣一下。不是因为舍不得,而是脑子里没有一张地图——一张"哪些业务是企业赚钱的命根子、哪些只是配套、哪些其实谁家都一样"的地图。
这正是 DDD 战略设计要解决的第一个问题:业务这么大,从哪下手?
一个反直觉的答案是——不是从代码下手。绝大多数重构项目死掉,就死在"上来就看代码、看表结构、想怎么拆"这一步。DDD 的顺序正好反过来:先看业务,再看代码。
为什么?因为代码结构腐化是"果",业务知识混乱才是"因"。如果连"哪些业务是命根子、哪些是辅助"都没在团队里达成共识,任何拆分方案都是拍脑袋。你拆得很优雅,三个月后照样别扭,因为根上的业务认知就没对齐过。
战略设计这一上一下两篇,就是教你怎么把"业务版图"先画出来。上篇(本篇)讲三件事:子域三分法、事件风暴、通用语言。下篇讲限界上下文,那是模型真正的"户口所在地"。
二、子域三分法:把钱花在命根子上
把整个业务切成若干子域(Subdomain),然后按"投入等级"分成三类。这是战略设计最朴素、也最被低估的一步。
| 类型 | 定义 | 例子(电商) | 投入策略 |
|---|---|---|---|
| 核心域(Core Domain) | 企业赚钱的竞争力所在,必须自研做精 | 交易、推荐算法 | 最优秀的团队、最多的资源 |
| 支撑域(Supporting Subdomain) | 必不可少但不是卖点 | 库存、物流、客服 | 够用就好,适度投入,可定制开发 |
| 通用域(Generic Subdomain) | 所有公司都一样,没有差异化 | 认证、短信、支付通道对接 | 直接用开源 / 云服务 / 采购 |
这张图把三类子域和它们的特征摆在一起,一眼能分清:
三分法的核心价值不在"分类"本身,而在资源配置。一个 10 人团队如果平均用力,核心域反而做不出竞争力——好钢没用在刀刃上。我见过太多团队把最贵的资深工程师耗在"通用域"上,去手写一套认证、一套短信网关,而核心的交易链路反而没人精雕。这是本末倒置。
那怎么判断一块业务到底归哪一类?不用背定义,连续问三个问题就够了:
💡我的设计笔记 · 一
子域三分法里,最容易被误解的是"核心域"。核心域没有标准答案,它取决于公司战略,不是取决于技术难度。
同样是电商,有的公司核心竞争力在供应链,那"库存"就是它的核心域,要自研做精;有的公司核心竞争力在内容种草,那"社区"才是核心域。一个做交易起家的团队,和一家做履约起家的团队,对同一个"库存"子域的归类可以完全相反。
所以我特别警惕一种说法:"这块逻辑最复杂,所以它是核心域。"复杂不等于核心。复杂度是技术负担,核心度是商业价值。两者一定要分开看,否则你会在最不赚钱的地方投入最贵的人。
还有一点要强调:子域划分不是一次性的。业务方向变了,子域地图要跟着重审。去年还是支撑域的"营销",今年公司主打私域增长,它可能就得升格为核心域。
三、怎么划分子域:事件风暴(Event Storming)
三分法给了"判级"的尺子,但还没告诉你"边界画在哪"。划边界最实用的工具,是 Alberto Brandolini 发明的事件风暴(Event Storming)——一种工作坊方法,用"领域事件"做抓手,让业务和技术在同一张白板上对齐。
什么是领域事件
领域事件是领域内已发生的、业务人员关心的事实,命名一律用过去时:订单已创建、库存已扣减、支付已完成、退款已发起。
注意是"已发生"——它描述事实,不是命令。这一句话就能把技术和业务拉到同一频道:业务人员听得懂"订单已创建",也只对这种事实感兴趣。
操作流程
一次标准的事件风暴,我按下面六步走:
- 头脑风暴:全员(产品、运营、开发、测试)把脑子里能想到的领域事件,写橙色便利贴,用过去时,往墙上贴;
- 按时间轴排序:把便利贴按业务发生的先后顺序排成一条线,去掉重复、补齐缺口;
- 按亲疏聚簇:谁发起、谁关心、谁紧挨着谁,关系近的贴成一簇;
- 标记关键决策:用粉色便利贴标出业务规则和策略点(比如"等级不足则拒单");
- 识别参与者与命令:黄色贴角色(谁触发的),蓝色贴命令(触发事件的动作);
- 找边界:簇与簇之间的空隙,就是子域 / 限界上下文边界的候选。
最妙的是第 6 步。一次工作坊,同时完成了"事件梳理"和"边界识别"两件事——这恰恰是事件风暴最大的效率优势:你不是在写完代码后才争论"这块该归谁",而是在白板上当场就把分歧解决掉。
同样一块业务,两种开局,差别有多大?一张图说清楚:
我自己的经验是:直接写代码的团队,把最贵的争论(边界、归属、术语)挪到了"改不动"的阶段——等代码都耦合在一起了再谈拆分,返工成本是指数级的。而先风暴的团队,逻辑边界先定,物理拆分只是顺水推舟,可逆、低成本。
一个电商交易域的事件风暴,产出的"簇"长这样(节选):
关键观察:交易簇和库存簇之间的那条线,就是限界上下文的候选边界。这件事 03 篇会展开讲,但你现在要记住一个结论——簇间空隙 = 边界候选,这是事件风暴送给你的"免费边界"。
四、通用语言:让代码说"业务的话"
边界画完,进入具体的建模之前,还有一块地基必须打——通用语言(Ubiquitous Language)。这是贯穿全系列的一条主线,也是 DDD 里最容易被当成"命名规范"而轻视的一条。
一个词,三套说法
我们团队真实发生过的事:
- 产品经理的文档写的是"核销";
- 前端接口字段叫
status: 2; - 后端代码里叫
updateCouponState。
三个词,指同一件事。每次沟通都要翻译一遍:产品说核销,前端说状态 2,后端说更新状态。新人上手成本极高,需求评审永远对不齐——因为根本没有人说同一种语言。
解法:建立通用语言表
把术语、命名、代码体现、禁用说法,统一成一张表,钉在团队 Wiki 上:
| 业务术语 | 英文命名 | 代码体现 | 禁用的替代说法 |
|---|---|---|---|
| 核销优惠券 | redeem | Coupon.redeem() | updateStatus(2)、stateChange |
| 库存锁定 | lock | Stock.lock(qty) | deduct、freeze |
| 订单履约 | fulfill | Order.fulfill() | ship、deliver 混用 |
| 秒杀活动 | flashSale | FlashSaleActivity | seckill(音译)、rushBuy |
这张表的价值,远不止"统一叫法"这么简单。一旦你要求代码命名必须能对上业务语言,很多模型缺陷就藏不住了:你会发现业务方嘴里的"核销"和"使用"其实是两件事,而你的表里只有一个status字段——语言对不上的背后,往往是模型本身就漏了概念。
维护规则(三条)
- 通用语言表放在团队 Wiki,需求评审时同步更新——评审中出现新词,当场定命名,不带回技术那边再"翻译";
- 代码 Review 的检查项之一:命名与语言表一致,不一致就是 Review 不通过;
- 术语在限界上下文内必须唯一,跨上下文允许同名不同义(比如"商品"在目录上下文和库存上下文含义不同,这是允许的,03 篇细讲)。
💡我的设计笔记 · 二
我早年以为通用语言是"锦上添花"的软东西,后来踩过一次大坑才改观。
当时一个"账户冻结 / 账户止付 / 账户挂失"三个词,在业务口是三种不同风控动作,到了代码里全被我合并成一个
account.freeze(),因为我觉得"反正都是不让动钱"。结果上线后,风控要区分"客户主动挂失"和"系统自动止付"两条不同流程,我的模型根本表达不出这种差异,只能到处加if (type == ...)的补丁。教训:语言不一致,本质是概念没拆清。通用语言表不是给新人看的字典,它是模型正确性的第一道闸门。命名对不上业务那天,往往就是模型开始腐化的第一天。
五、实战:给一个中型电商画子域地图
光讲方法太虚,来一个真实的体量:日订单 10 万,团队 12 人,单体应用 40 万行。我们用上面的方法,把它的全域切成 7 个子域,分类和资源决策一张表落定:
| 子域 | 三分法归类 | 资源决策 |
|---|---|---|
| 交易(订单 / 购物车 / 促销)、推荐引擎 | 核心域 | 派 2 名资深工程师长期投入,代码规范按最高标准,慢就是快 |
| 库存、物流、营销(优惠券) | 支撑域 | 普通团队维护,允许适度定制,不追求极致 |
| 认证授权、消息通知、支付网关对接 | 通用域 | Keycloak + 云消息服务 + 聚合支付 SDK,一行不自己写 |
你看,子域划分的产出从来不是"一张漂亮的图",而是一张"钱和人该往哪砸"的清单。图只是清单的载体。
六、子域不是微服务,也不是包
写到这,必须澄清一个高频误解,否则你下一步就会走偏。
子域 ≠ 微服务。子域是业务的逻辑划分(这是哪块业务、花多少钱做),微服务是部署的物理形态(怎么上线、怎么扩容)。一个子域可以是一个微服务,也可以是单体里的一个模块——09 篇会讲渐进式落地的三种形态,核心结论先记住:先逻辑,后物理。
顺着这个逻辑,子域和限界上下文的关系也就清楚了,这是本篇最该拎清的一对概念:
- 子域属于"问题空间":回答"该不该做、花多少钱做、归谁管";
- 限界上下文属于"解空间":回答"模型住哪、术语在哪唯一生效、边界画在哪"。
一个子域通常对应一个限界上下文,但两者层次完全不同。事件风暴里我们先用"簇"把子域(问题空间)划出来,下一步才在簇的边界上立起"限界上下文"(解空间)。子域是地图,上下文是户口。下一篇,我们就走进这个"户口"里,看模型到底被圈在哪些边界内、边界与边界之间又该怎么相处(六种上下文映射模式)。
如果子域划分错了怎么办?别慌,这正是"先逻辑后物理"的好处:逻辑边界调整的成本,远低于物理拆分。识别信号(比如交易域反复找库存域要本该自己缓存的数据)→ 回到事件风暴重新聚簇 → 调整边界。代码还没拆成微服务,调整只是挪几张便利贴的事。
本篇的三个自查动作,我做成了一张卡,贴在这里方便收藏:
七、本篇小结
一句话收束:先划子域,再动代码。
- 用"外包会不会失去竞争力"这一个问句,区分核心域 / 支撑域 / 通用域,三分法的产出是一张资源清单;
- 事件风暴用"领域事件"做抓手,一次工作坊同时完成事件梳理与边界识别,把最贵的争论消灭在白板上;
- 通用语言表是所有后续建模的地基,语言不一致的本质是概念没拆清;
- 子域是问题空间的地图,限界上下文是解空间的户口,两者先逻辑后物理。
下一篇进入限界上下文——模型真正的"户口所在地",以及边界与边界之间的六种相处方式。
八、系列目录
| 篇 | 主题 | 一句话 |
|---|---|---|
| 00 | 系列导读 | 这条学习路线怎么走,三类人怎么读 |
| 01 | 为什么需要 DDD | 规则副本的代价与三条原则 |
| 02 | 战略设计(上) | 本文:先划子域再动代码;子域三分法、事件风暴、通用语言表 |
| 03 | 战略设计(下) | 限界上下文与六种上下文映射模式 |
| 04 | 战术设计(上) | 实体、值对象、聚合的建模与三条铁律 |
| 05 | 战术设计(下) | 领域服务、领域事件、仓储与工厂 |
| 06 | 架构模式 | 四层架构、六边形架构与依赖倒置 |
| 07 | 实战案例一 | 电商订单系统:聚合与事件驱动建模 |
| 08 | 实战案例二 | 银行转账:跨聚合的最终一致性 |
| 09 | 实战案例三 | 40 万行单体如何按上下文渐进拆分 |
| 10 | 落地与误区 | 六步路线图、七个高频坑、体检清单 |
下一篇:03 · 战略设计(下):限界上下文—— 模型真正的"户口所在地",以及边界与边界之间的六种相处方式。
关于作者
技术原点(YuandianIT)
- 985 计算机硕士,十年一线研发经历,先后在头部互联网公司做交易与履约,现于金融行业负责信贷核心、风控与账务系统的架构设计
- 累计面试近千人,带过 50+ 新人,长期负责技术团队的技术选型与架构评审
- 关注方向:复杂业务系统建模(DDD / 事件驱动 / 一致性设计)与AI 工程化落地
- 写作原则:只写自己动手做过的,只给能落地的结论,方案必须带代价
我信一句话:架构的优劣不在图上,在第三次需求变更时的手感上。
关于技术原点
始于技术,归于原点—— 👉https://yuandianit.com
技术原点是一个基于Spring Boot 3 + Vue 3前后端分离的自有技术平台,把「内容创作 + 付费专栏 + AI 辅助 + 资源交易」完整跑通——不是一个博客 Demo:
| 能力 | 说明 |
|---|---|
| 📝 文章系统 | Markdown 创作、分类标签、归档时间线、阅读量 / 点赞 / 收藏、关键词搜索 |
| 📚 Wiki 知识库 | 专栏化知识库、目录树导航、整册解锁 ——本 DDD 系列会整理成册放在这里 |
| 🤖 AI 助手 | 接入智谱 / OpenAI / 通义千问 / DeepSeek 多模型,支持 SSE 流式追问 |
| 💰 付费阅读 | 四级阅读门禁与付费专栏,支持单篇与整册 |
| 🛒 社区商城 | 源码与服务商品、微信 Native 扫码支付、订单与下载鉴权 |
| ⚙️ 后台管理 | 内容与用户管理、sa-token 权限、操作日志、定时任务、Knife4j 接口文档 |
为什么要到平台上读这个系列?
- 代码和图都在线渲染:架构图、代码高亮、一键复制,比看截图舒服;
- 读到不懂的地方直接问 AI:平台内置多模型 AI 助手,可以就某一篇继续追问,不必等我在评论区回;
- 成册阅读:系列在平台上按 Wiki 专栏组织,目录树跟随,适合系统学一遍;
- 平台本身就是源码:文章里讲的依赖倒置、事件驱动、支付闭环、AI 流式对话,都能在平台源码里找到真实落地——理论和实现互为注解。
👉立即访问:https://yuandianit.com