Chapter 02
战略设计:把建模精力投到哪一块
上一章把领域、模型、统一语言立成地基——领域是问题,模型是为某个目的做的选择性抽象,统一语言把口语和代码绑在一起。这一章接着问一个钱的问题:一个平台有许多块业务,建模和投入的精力有限,该把最贵的精力压到哪一块。答案不是"全都好好建",而是先把业务切块、按价值分级、再分别决定投资方式。
本章你将建立的 schema
- 子域 Subdomain:领域不是铁板一块,它由若干子问题拼成
- 核心域 / 支撑域 / 通用域:三类子域的价值不是线性的,差异巨大
- 问题空间 vs 解空间:子域是被发现的,限界上下文是被设计的
- 投资拨盘:核心自研、通用买、支撑用 CRUD 或事务脚本,按价值差异化投入
- 为什么"均匀地、到处都好好用 DDD"是最贵、最亏的策略
本章用贯穿全书的「云市」电商交易平台作示例。云市要做的事很多:商品展示、下单、库存、配送、收款、退款、登录、发通知。把它们一股脑当成"一个电商系统"去建模,是工程师最常见的起手式,也是战略设计要纠正的第一个直觉。战略设计的第一刀,就是把这一坨业务切成块。
2.1子域 Subdomain:领域是由子问题拼成的
子域(Subdomain):领域里相对独立的一块子问题。一个领域不是单一问题,而是一组关注点不同的子问题的集合。
把整个云市当成一个问题来想,就只能得到一个庞大、含糊、什么都沾一点的模型。子域是一把"问题侧的解剖刀":它先承认领域内部是异质的——下单的关注点(状态机、定价、不可篡改)和发短信的关注点(投递、模板、重试)天差地别。先按子问题切开,才能后续对每一块单独判断它值多少、该投多少。
底层机制(比定义深一层):子域属于问题空间——它描述的是"业务上客观存在的一块子问题",而不是你打算用哪个系统、哪张表去实现它。识别子域靠的是业务语言的断裂:当人们换一套词汇、换一批关心的指标、换一群对接的人时,你多半跨过了一条子域边界。在云市,"加购物车、下单、订单状态"是一套语言,"安全库存、预留、仓位拣货"是另一套语言,"对账、退款、风控"又是一套。三套语言之间的人会争吵——这恰恰是边界存在的信号。
把云市初步切块,得到这样一组子域:
| 子域 | 这块在操心什么 | 它独有的语言 |
|---|---|---|
| 商品展示 | 把商品讲清楚、好看、可被搜到 | 标题、详情、图集、类目、参数、文案 |
| 下单交易 | 下单、订单状态推进、算这一单多少钱 | 订单、订单项、状态机、定价、优惠 |
| 库存履约 | 有没有货、预留、从哪个仓出 | SKU、可用量、预留、仓位 |
| 配送 | 包裹怎么送到、谁承运、签收 | 运单、重量体积、承运商、签收 |
| 收款退款 | 把钱收进来、退回去、对得上账 | 支付方式、流水、对账、退款 |
| 账号 / 通知 | 谁是谁、怎么登录、怎么发消息 | 账号、凭证、令牌、模板、渠道 |
注意这一步还没有谈"建几个微服务""设计几张表"。子域是对问题的认识,不是对方案的承诺——这条分界是 2.3 节的主题,先放着。这里只确立一点:云市不是一个问题,是六七块关注点迥异的子问题。
像给一座城市划行政区:把城市切成商业区、住宅区、工业区,是为了承认每块的功能和需求不同,好分别规划。类比失效处:城市分区是地理上互不重叠的硬边界,而子域之间会就同一个词起冲突——"商品"在展示区是胖对象、在交易区是价格冻结的快照、在库存区只是 SKU+数量。这种"同名不同物"正是 03 章限界上下文要处理的,地理类比给不了。
与下一节的关系:切出六七块只是第一步。真正决定胜负的是下一个问题——这几块里,哪一块才是云市赖以活命、对手抄不走的?这就要给子域分级。
2.2核心域 / 支撑域 / 通用域:价值是非线性的
三类子域:核心域(Core Domain)是竞争差异所在、对手抄不走;支撑域(Supporting Subdomain)是业务需要但不构成差异;通用域(Generic Subdomain)是人人都要、市面有现成方案。
如果每块子域的价值都一样,那把精力均摊就是对的。但价值不是均匀的——它高度集中在极少数子域上。给子域分级,是为了在动手前就认清:哪一块的成败直接决定企业的成败,哪几块只要"能用、别出事"就够了。投错地方的代价不是浪费一点点,而是把最稀缺的资源(你最强的几个人)压在了对手随手就能买到的东西上。
三类的判别标准(领域真名):
- 核心域 Core Domain:这块做得比对手好,企业就赢;做砸了就输。它是差异化的来源,外面买不到对等品,因为买得到的对手也都有了,就不叫差异。判据:"如果这块外包给随便一家公司做成行业平均水平,我们还有竞争力吗?"——答"没有"的,就是核心域。
- 支撑域 Supporting Subdomain:业务跑起来确实需要它,但它本身不构成差异,市面上又没有刚好贴合的现成品(业务规则比较特有)。判据:"少了它业务转不动,但它做到行业平均就够,没人会因为它选我们。"
- 通用域 Generic Subdomain:很多企业都要、且需求高度标准化,市面有成熟方案。判据:"这问题几十年前就被解决了,我们自研只是在重新发明轮子。"
场景走查:给云市的子域分级
把 2.1 切出的子域逐块过判据:
| 子域 | 分级 | 为什么这么定 |
|---|---|---|
| 智能定价 & 撮合 | 核心域 | 同一件商品、同一个买家,云市能不能给出比对手更好的价、把人货撮合得更准,直接决定成交与复购。对手抄不走这套规则与数据闭环。 |
| 订单 / 库存 / 履约 | 支撑域 | 下单、扣库存、安排发货必须有,但做到行业平均即可——没人因为"你的订单状态机更优雅"而选云市。规则又较特有,买不到完全贴合的。 |
| 支付 / 认证 / 通知 | 通用域 | 收款、登录、发短信是每个平台都要的标准问题,已有成熟的支付网关、身份服务、消息平台,自研纯属重复造轮子。 |
这里要把 2.1 的"下单交易"进一步拆开看:订单状态机是支撑性的(行业平均就够),但定价与撮合是核心。同一句"下单",里面既有支撑域也有核心域——分级看的是价值,不是模块名。这正是战略设计要逼出的精细:不要因为"它们都叫订单系统"就一视同仁。
"价值非线性"到底什么意思:把投入画成横轴、回报画成纵轴,核心域那条曲线很陡——每多投一分精力(更准的模型、更深的领域规则、更紧的反馈闭环),回报显著上扬;而通用域那条线几乎是平的,甚至向下:你自研支付,投入再多也只是逼近一个早就被商品化的能力,省下的钱比不过 SaaS,还要养一支团队填合规与风控的坑。下一节的"投资拨盘"把这条非线性曲线落成具体的投资动作。
云市的产品负责人提议:"支付对账老出问题,把咱们最强的两个工程师抽过去,自研一套对账系统做到极致。"从核心域 / 通用域的角度看,这笔人力投资是赚还是亏?
停 10 秒,再答
大概率是亏。支付对账属于通用域——它是被解决了几十年的标准问题,市面上有成熟的支付网关和对账服务。把最强的人投到通用域,等于让稀缺资源去逼近一个早已商品化的能力上限:投入再多,产出也只是"达到行业平均",省的钱比不上 SaaS 的成本,还要长期承担合规与安全的维护负担。
更深一层的损失是机会成本:这两个最强的人本该压在核心域——智能定价 & 撮合——那里每多投一分,回报陡增、且对手抄不走。把他们放到对账上,是把陡曲线上的产能挪到了平曲线上。正确动作通常是:通用域买或接 SaaS,把人留给核心域。
与下一节的关系:分级回答了"哪块值钱"。但分级本身不会自动变成行动。需要一个把"价值差异"翻译成"投资动作"的工具——投资拨盘。
2.3问题空间 vs 解空间:发现 vs 设计
子域属于问题空间(被发现);限界上下文属于解空间(被设计)。前者是业务客观长成的样子,后者是你为应对它而划的方案边界——二者只力求一一对应,但不天然相等。
工程师最容易把"业务里有几块问题"和"我要建几个系统"混为一谈,于是一看到"订单"就直接开建"订单服务"。把问题空间和解空间分开,是为了承认中间有一道翻译缝:业务长成什么样不由你决定(你只能发现),但你用什么边界、什么模型去应对它,是你的设计自由。设计得好不好,正是战略与战术能力的体现。
两个空间的精确分工:
- 问题空间 = 子域。它是被发现的:你去和业务方聊、听他们换词、看指标和争吵,把领域里客观存在的子问题识别出来。你无法发明子域——云市需不需要"履约"这件事,由生意本身决定,不由架构师决定。
- 解空间 = 限界上下文(Bounded Context,03 章详解)。它是被设计的:在确认了子域之后,你主动划出一道道模型与语言的边界,在每道边界内让一套统一语言(01 章)保持一致。边界画在哪、画几道,是工程决策。
为什么是"力求 1:1"而不是"必然 1:1":理想状态是一个子域对应一个限界上下文,干净利落。但现实里会偏离——有时遗留系统把两个子域焊在一个上下文里(一个大系统同时管订单和库存),有时一个子域因为团队或技术原因被拆成两个上下文。1:1 是设计目标,不是事实保证;偏离 1:1 不一定是错,但每一次偏离都该是有意识的取舍,而不是稀里糊涂长出来的。03 章会把这层映射关系彻底讲透,这里先把"两个空间"这根桩打牢。
与下一节的关系:分清了"业务有哪几块(问题空间)"和"我打算怎么应对(解空间)",最后一个问题是钱和精力——每一块,我到底是自研、是买、还是随便糊一个 CRUD?这是投资拨盘。
2.4投资拨盘:核心自研 / 通用买 / 支撑 CRUD
投资拨盘:按子域分级配不同的实现强度——核心域上全套 DDD 自研,通用域买或接 SaaS,支撑域用 CRUD 或事务脚本。把最贵的精力只压在核心域。
2.2 算清了价值差异,2.3 分清了发现与设计,但落到工程时,团队往往一刀切:要么"全公司统一架构、处处 DDD",要么"全都 service + dao 草草了事"。两种一刀切都亏。投资拨盘是把"价值非线性"翻译成"实现非线性"的旋钮:不同的子域,配不同档位的投入。
三个档位(与三类子域对齐):
- 核心域 → 全套 DDD 自研。这里值得上限界上下文、聚合、值对象、领域服务(05–06 章)这套重武器,因为模型的精细度直接转化成竞争力。云市的智能定价 & 撮合就该这么对待:把领域规则深深刻进模型,而不是散在一堆 if-else 里。
- 通用域 → 买或接 SaaS。支付接现成支付网关、认证接身份服务、通知接消息平台。自研只会重复造轮子并背上长期维护负担。这里的工程重点不是建模,而是集成——通过防腐层(Anticorruption Layer, ACL,03 章)把外部模型挡在核心之外。
- 支撑域 → CRUD 或事务脚本。订单状态流转、库存增减这类,业务规则不算复杂、又不构成差异,用直白的事务脚本(Transaction Script,把一个用例的逻辑写成一段过程式代码)或常规 CRUD 即可。在这里硬上全套聚合,是用屠龙刀切菜。
核心论点:"均匀地用 DDD 最贵":全套 DDD 是有成本的——更长的建模时间、更高的认知门槛、更多的间接层。把它均匀地铺到每个子域,等于在支撑域和通用域上付了核心域的价,却拿不到核心域的回报。真正划算的策略是差异化投入:把这套重武器的成本,只花在那条回报陡峭的核心域曲线上。这也呼应了一条贯穿全书的判断——限界上下文不等于微服务,模块化单体优先;不要因为"用了 DDD"就给每块子域都配一套独立部署的重型基础设施。
这就是图 2.1 里那条非线性曲线的工程落地:横轴是投入档位(CRUD → 买 → 全套自研),纵轴是回报。核心域处在曲线陡峭段,加投有显著回报;通用域处在平坦段,加投几乎不回本、自研甚至为负。投资拨盘就是让你显式地为每个子域选一个档位,而不是默认全开最大档。
场景走查:给云市三块定投资动作
把分级直接翻译成实现决策,并写成应用服务里的一句调用编排(仅示意"谁自研、谁集成",不是完整实现):
// 应用服务:只编排,不写业务规则(业务规则在各自的档位里)
public class PlaceOrderApplicationService {
private final PricingService pricing; // 核心域:全套 DDD 自研,规则刻进模型
private final OrderRepository orderRepository;// 支撑域:常规仓储 + 直白事务脚本
private final PaymentGateway paymentGateway; // 通用域:接 SaaS,ACL 挡在外面
public OrderId placeOrder(BuyerId buyerId, List<CartLine> cart) {
// ① 核心域:定价撮合,云市的差异化能力,深建模
Money total = pricing.quote(buyerId, cart);
// ② 支撑域:装配并持久化订单,事务脚本即可,不必过度建模
Order order = Order.place(buyerId, cart, total);
orderRepository.add(order);
// ③ 通用域:收款交给外部网关,本系统只发起、不重造支付
paymentGateway.charge(order.id(), total);
return order.id();
}
}
三行调用,三个档位:PricingService 是自研的核心域重头戏;OrderRepository + Order.place(...) 是支撑域的直白持久化;PaymentGateway 是通用域的外部集成。同一个用例里并存三种投入强度,这正是投资拨盘的样子——不是"整个系统用不用 DDD",而是"每一块各拨到哪一档"。
两种对称的失败模式:其一,处处全套 DDD——给发短信、收款也建聚合、划独立上下文,结果在没有差异价值的地方付了最高的建模与运维成本。其二,处处事务脚本——把核心的定价撮合也写成一坨 service 里的 if-else,结果在唯一该深建模的地方欠了债,竞争力被规则的混乱拖垮。正确姿势是按子域分级、逐块拨档,而不是给整个平台拧一个统一旋钮。
本章收口:战略设计回答的是"把精力投到哪"——先用子域把领域切块(2.1),按核心 / 支撑 / 通用给价值分级(2.2),分清问题空间与解空间(2.3),再用投资拨盘把价值差异落成投入差异(2.4)。下一章接住 2.3 埋的桩,专讲解空间里那道被设计的边界:限界上下文——它是语义边界,不是微服务边界,也正是"同一个词在不同上下文里是不同的类"的根源。
§本章 self-check
先合上教程,把答案写在纸上或编辑器里。写完再点开对照——直接点开等于把这一节当再读一遍。
- 给三句判别标准各配一类子域:「外面买得到对等品」「业务需要但做到平均就够、又没现成贴合品」「外包成行业平均我们就没竞争力了」分别指向核心域 / 支撑域 / 通用域中的哪一个?
- 子域属于问题空间还是解空间?限界上下文呢?哪一个是"被发现"的、哪一个是"被设计"的?
- "子域和限界上下文是 1:1 的"——这句话哪里对、哪里不对?用"力求 / 保证"两个词把它说准。
- 投资拨盘对核心域、通用域、支撑域分别拨到哪一档(自研全套 DDD / 买或 SaaS / CRUD 或事务脚本)?为什么说"均匀地用 DDD 最贵"?
答案(先做完再展开)
- 「外面买得到对等品」→ 通用域(买得到说明已商品化、不构成差异);「业务需要但做到平均就够、又没现成贴合品」→ 支撑域;「外包成行业平均我们就没竞争力了」→ 核心域(差异化的来源,对手抄不走)。
- 子域属于问题空间、是被发现的(业务客观长成的样子);限界上下文属于解空间、是被设计的(你划的方案边界)。
- 对:理想目标确实是一个子域对应一个限界上下文。不对:那是"力求 1:1"的设计目标,不是"保证 1:1"的事实——遗留系统、团队或技术原因都可能让映射偏离,偏离时应是有意识的取舍。
- 核心域 → 全套 DDD 自研;通用域 → 买或接 SaaS;支撑域 → CRUD 或事务脚本。"均匀地用 DDD 最贵"是因为全套 DDD 有真实成本(建模时间、认知门槛、间接层),把它平铺到不差异化的子域,等于在支撑 / 通用域上付了核心域的价却拿不到回报;省钱的做法是只在回报陡峭的核心域上全开。
给一条新业务线判核心域
云市要上一条新业务线:面向小商家的「一件代发」——商家在云市上架商品,买家下单后,由云市自动把订单转给上游供应商直接发货,云市从不碰货。请回答:这条新业务线里,(a) 至少能切出哪几个子域?(b) 哪一块最可能是核心域,理由是什么?(c) 其中"把订单路由到最合适的上游供应商"这件事,该拨到投资拨盘的哪一档?
提示(卡住再展开)
(a) 大致能切出:供应商对接 / 选品、订单路由与分单、对账结算、(复用云市既有的)展示 / 收款 / 通知等。
(b) 最可能是核心域的,是"订单路由与供应商撮合"——一件代发的差异化恰恰在于"把哪一单分给哪个上游、按什么策略(价格、时效、库存、违约率)选",这套规则与数据闭环对手抄不走,做得好直接决定履约成本和买家体验。它和主站的"智能定价 & 撮合"是同一类核心域逻辑(用判据"外包成行业平均还有竞争力吗"一问便知)。
(c) 因此"订单路由"应拨到核心域档:全套 DDD 自研,把选源规则深刻进模型;而对账结算多半是支撑域(事务脚本),收款 / 通知仍是通用域(接现成)。注意别因为"它也叫订单"就和主站订单一样按支撑域处理——分级看价值,不看名字。