题记与说明
- 日期:2026-11-06
- 性质:主题精读(选读 Evans 原典战略设计部分 + 4 个二手来源交叉验证)
- 七维提取:见下文第三节
- 本篇要回答的问题:DDD 出版于 2003 年,语境是大团队、大系统。剥掉微服务叙事和组织政治成分后,战略设计(Bounded Context / Ubiquitous Language / Context Map / Distillation)有哪些在单人项目和单体服务里依然直接可用?
一、精读来源清单(5 个,均已通读)
| # | 来源 | 角色 | 链接 |
|---|---|---|---|
| 1 | Eric Evans, *Domain-Driven Design Reference* (2015-03, 官方模式与定义摘要, CC BY 4.0),8 页 PDF 已下载并全文转文本精读 | 原典级:书中全部模式定义的逐字压缩版 | 页面 / PDF 直链 |
| 2 | Martin Fowler, "Bounded Context" (bliki, 2014-01-15) | 权威二手:解读 Evans 原书结论与适用条件 | https://martinfowler.com/bliki/BoundedContext.html |
| 3 | Petter Holmström, "DDD Part 1: Strategic Domain-Driven Design" (Vaadin blog, 2023) | 现代结构化讲解:子域三分类、问题/方案空间、小团队警告 | https://vaadin.com/blog/ddd-part-1-strategic-domain-driven-design |
| 4 | Robert Laszczak (Three Dots Labs), "Microservices or monolith? It's detail" | 单体内的限界上下文实操:Clean Monolith、通信规则、CI 强制工具 go-cleanarch | https://threedots.tech/post/microservices-or-monolith-its-detail/ |
| 5 | Jon P Smith, "My experience of using modular monolith and DDD architectures" (2021) | 单人项目第一手经验报告:22 个项目的模块化单体 + DDD,时间压力下的真实得失 | https://www.thereformedprogrammer.net/my-experience-of-using-modular-monolith-and-ddd-architectures/ |
辅助检索:WebSearch("DDD bounded context monolith small project overkill modular monolith")确认了社区共识:限界上下文作为思维工具几乎处处适用;全套战术机器与微服务拆分只在多团队/复杂域/有拆分计划时才划算(代表性观点见 Milan Jovanović 与 kgrzybek/modular-monolith-with-ddd)。
二、原典逐字定义与关键引述(来源 #1,DDD Reference PDF,p.2–41)
以下引文为 PDF 转文本的逐字摘录(去除了原 PDF 的连字符排版差异)。该文档为 CC BY 4.0 许可,允许署名转载。
Bounded Context(p.2)——问题陈述:
"Multiple models are in play on any large project. … Multiple models are inevitable, yet when code based on distinct models is combined, software becomes buggy, unreliable, and difficult to understand."
"Model expressions, like any other phrase, only have meaning in context."
Therefore(模式的核心处方):
"Explicitly define the context within which a model applies. Explicitly set boundaries in terms of team organization, usage within specific parts of the application, and physical manifestations such as code bases and database schemas. Apply Continuous Integration to keep model concepts and terms strictly consistent within these bounds, but don't be distracted or confused by issues outside."
Ubiquitous Language(p.3–4):
"Translation blunts communication and makes knowledge crunching anemic."
"Use the model as the backbone of a language. Commit the team to exercising that language relentlessly in all communication within the team and in the code. Within a bounded context, use the same language in diagrams, writing, and especially speech."
"Recognize that a change in the language is a change to the model."
Continuous Integration(p.5)——注意它不是指 CI/CD 工具,而是指模型/代码的持续合并:
"When a number of people are working in the same bounded context, there is a strong tendency for the model to fragment. The bigger the team, the bigger the problem, but as few as three or four people can encounter serious problems."
Context Map(p.28–29):
"Identify each model in play on the project and define its bounded context. … Name each bounded context, and make the names part of the ubiquitous language. Describe the points of contact between the models, outlining explicit translation for any communication… Map the existing terrain. Take up transformations later."
七种上下文关系模式(p.31–37),各自 Therefore 的要点逐字摘录:
- Shared Kernel:"Designate with an explicit boundary some subset of the domain model that the teams agree to share. Keep this kernel small."
- Customer/Supplier:"Establish a clear customer/supplier relationship between the two teams, meaning downstream priorities factor into upstream planning."
- Conformist:"Eliminate the complexity of translation between bounded contexts by slavishly adhering to the model of the upstream team."
- Anticorruption Layer:"As a downstream client, create an isolating layer to provide your system with functionality of the upstream system in terms of your own domain model."
- Open Host Service:"Define a protocol that gives access to your subsystem as a set of services. … use a one-off translator to augment the protocol for that special case so that the shared protocol can stay simple and coherent."
- Published Language:"Use a well-documented shared language that can express the necessary domain information as a common medium of communication."
- Separate Ways:"Integration is always expensive, and sometimes the benefit is small. … Declare a bounded context to have no connection to the others at all."
Distillation(p.39–41):
Core Domain: "Make the core small. Apply top talent to the core domain… Justify investment in any other part by how it supports the distilled core."
Generic Subdomains: "Identify cohesive subdomains that are not the motivation for your project. … consider off-the-shelf solutions or published models for these generic subdomains."
Big Ball of Mud(p.38):
"Draw a boundary around the entire mess and designate it a big ball of mud. Do not try to apply sophisticated modeling within this context. Be alert to the tendency for such systems to sprawl into other contexts."
三、七维提取
1. 核心问题
软件最贵的复杂度往往不在技术而在领域本身。Evans 在战略设计部分回答三个问题:
- 模型放不下:大系统中多个模型必然并存(不同团队、不同用户群、不同工具链),强行合并成一个大一统模型行不通——Fowler 转述 Evans 的结论:"total unification of the domain model for a large system will not be feasible or cost-effective"。多义术语在口语里能糊弄过去,在计算机里不行(Fowler 的电力公司例子:
meter对不同部门分别指"电网到位置的连接""电网到用户的连接""可更换的物理电表")。 - 语言分裂:领域专家、开发者、代码各自一套词汇,翻译消耗认知:"Translation blunts communication and makes knowledge crunching anemic."
- 精力错配:稀缺的高级开发者天然流向技术基建或"没有领域知识也能啃"的整洁问题,真正的业务资产(核心域)反而被 neglected。
2. 主要主张
- 模型只在上下文中有意义 → 显式定义边界(Bounded Context),边界体现在团队组织、应用的特定部分、代码库与数据库 schema 等物理形态上。
- 通用语言即模型:语言变了模型就变了,代码是语言的最终载体;同一上下文内,图、文字、口语、代码必须用同一套词。
- 上下文之间要画地图(Context Map):显式命名每个上下文、显式写出翻译点;先用现有地形,以后再改造。
- 蒸馏:区分核心域/支撑子域/通用子域,"Make the core small",通用域考虑现成方案;其他一切投入都要"justify by how it supports the distilled core"。
- 集成是有价的决策,不是默认动作:七种关系模式给出了从"共享内核"到"彻底不集成"(Separate Ways)的谱系;烂系统也要画边界(Big Ball of Mud),只是别在里面做精细建模。
3. 方法
- 模式语言:不是可执行流程,而是 2003 年从真实企业项目提炼的模式集(2015 官方 Reference 收录全书定义 + 3 个未入书模式)。
- 操作顺序(综合 #3、#4):Event Storming 等工作坊定位上下文 → 区分子域(core/supporting/generic,判断是相对的:身份管理对身份公司是核心域,对 CRM 厂商是通用域)→ 子域在问题空间、上下文在方案空间,二者不要求一对一;一个上下文横跨多个子域是边界错误的信号 → 画 Context Map,为每对相邻上下文选关系模式。
- 落地形态:上下文可以是微服务,也完全可以是"单体里的一个子系统/模块/包"(#3 原话:"a separate subsystem inside a single monolithic system")。
4. 数据与证据
证据类型是模式文献 + 从业者经验报告,不是受控实验,没有量化收益数据。具体支撑:
- 原典定义(#1)来自 Evans 全书文本压缩,CC BY 4.0 许可,本人已下载 PDF 并转文本逐段核对(
pdftotext转出 1754 行,各引文均逐字核对)。 - Fowler(#2)给出可复现的多义术语案例(电力公司
meter),并指出旧式建议(全企业统一模型)已被 Evans 结论替代。 - Three Dots Labs(#4)给出可执行的机械强制手段:开源工具 go-cleanarch 在 CI 里校验"上下文之间只经接口/基础设施层通信、禁止 import 对方 domain 包";并给出单体与微服务下同一套 domain/application 代码、只换注入实现的对照写法(同步调用 REST vs 函数调用,异步 RabbitMQ vs Go channel)。
- Jon P Smith(#5)是单人开发的第一手报告:ASP.NET Core 图书电商,22 个 C# 项目(传统分层约 4 个),在截稿压力下完成并从 700 本压测到 50 万本;结论 "Modular Monolith = Brilliant… I will use it again! DDD = Love it"。同时报告了真实代价:命名最难、DDD 实体多写很多代码、时间压力下自己也会违规(6 处展示查询直接引用了第一个项目而非抽公共项目,事后才补
BookApp.ServiceLayer.DisplayCommon.Books)。
是否充分:对"概念有效、可落地"是充分的;对"收益多大、何时过度"只有定性经验,不充分——这部分靠多个独立来源收敛一致来补强。
5. 结论
战略设计的思想内核 20 年后依然成立,且对个人项目/单体尤其有效的是它的"边界思维",而不是它的"服务拆分"表象:
- 通用语言照常适用——单人项目里语言分裂发生在"现在写代码的我"与"三个月后读代码的我"之间;术语表 + 命名一致性是最低成本的防碎片手段。
- 单体内部照样可以(也往往应该)有多个限界上下文:"whether the application is a microservice or a monolith is an implementation detail"(#4)。模块间只走接口 + DTO(transport types),不 import 对方 domain 层;单体免费赠送编译期契约检查,微服务反而要靠人工维护 API 契约。
- 数据重复 ≠ 行为重复(#4):跨上下文复制一份订单快照字段不是坏味道,翻译/映射才是保持模型独立所需。
- 蒸馏对个人项目是放大器:单人时间最稀缺,"核心域优先 + 通用域买现成"直接就是排期原则;"不想清楚要不要集成"时默认 Separate Ways。
- 拆分路径是先单体后服务:#4 引 Fowler:"Almost all the successful microservice stories have started with a monolith that got too big." 模块边界画得好,将来抽取服务只动接口层和基础设施层。
6. 局限
- 作者/社区承认的:Evans 原书难读(Fowler:"the canonical source… amply repays a substantial investment",但承认门槛);Context Map 的组织维度(Customer/Supplier 谈判、Conformist 的政治现实)对单人项目大部分失效。
- 证据层面的:全部是案例与模式文献,无对照实验、无量化收益;"边界由语言/文化决定"意味着判定高度情境化,无法机械化,同一案例不同人画的线可以不同(Fowler 引 Verraes/Wirfs-Brock:上下文分裂常常出于历史和人际关系,和领域概念一样多)。
- 时代层面的:原书写于 2003,云原生、事件溯源、微服务为其后社区补充;Continuous Integration 模式在本意上指"模型+代码频繁合并",与今日 CI/CD 工具含义已经漂移。
- 适用边界的:对单团队 + 单上下文的小 CRUD 应用,全套战术 DDD 与"每模块四项目"结构是 overkill(WebSearch 共识,#5 也自报 DDD 代码量代价);#3 给出的小团队纪律是"从一个核心域 + 一个限界上下文开始,让更多的上下文自己显形",为分而分是主要失败模式。
- 我的推断:Reference PDF 只是定义压缩版,训练与反例需要回到原书或 Vernon;本文对原书 2003 正文未直接通读,原书长文语境以 Fowler 2014 转述与官方 Reference 为准——这是本次精读的明确边界。
7. 对我的启发(个人项目与单体服务的落地清单)
直接照做:
- 每个项目维护一页术语表(glossary),代码命名、数据库字段、笔记/文档用同一套词;改词=改模型,改词要连代码一起改("Recognize that a change in the language is a change to the model")。单人项目审查者就是未来的自己,这条防的是"三个月后自己读不懂"。
- 单体按限界上下文分包/分模块;跨模块只调用对方暴露的接口并使用独立 DTO,禁止 import 对方 domain 层;能用编译器/lint/CI 规则强制的就不要靠自觉(go-cleanarch 思路,任何语言都可以抄这条规则)。
- 启动期只承认一个核心域 + 一个上下文;新上下文只在语言确实变了时才切出来("you need a different model when the language changes",Fowler)。
- 每次想加功能/加集成时先问:这是核心域吗?不是 → 现成方案(Generic Subdomains:"consider off-the-shelf solutions");集成收益小 → Separate Ways。
- 包第三方 API/LLM 服务时套一层 ACL:让上游的模型(字段名、报错结构、分页语义)死在翻译层里,不渗进自己的 domain。
降级使用:
- Context Map:单人项目画一张"模块 + 箭头"图即可,七种关系模式里只常用三种——ACL(包外部/遗留系统)、Conformist(用别人的 SaaS 就照人家的模型来)、Separate Ways;Shared Kernel/Customer-Supplier 等多人政治模式基本用不上。
- Continuous Integration(模型含义):单人版 = 高频合并到主干 + 测试快速报警碎片化,不追求原书多人仪式。
明确不做:
- 不为"将来可能拆微服务"提前拆服务;先做模块边界清晰的 clean monolith,拆分是"touching only interfaces/infrastructure layers"的后置动作。
- 不为小 CRUD 项目上全套战术 DDD(聚合/仓储/领域事件的成本要由领域复杂度支付)。
四、来源链接汇总
- Eric Evans, *Domain-Driven Design Reference* (2015-03):<https://www.domainlanguage.com/ddd/reference/>(PDF:<https://www.domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf>,CC BY 4.0)
- Martin Fowler, "Bounded Context":<https://martinfowler.com/bliki/BoundedContext.html>
- Petter Holmström, "DDD Part 1: Strategic Domain-Driven Design":<https://vaadin.com/blog/ddd-part-1-strategic-domain-driven-design>
- Robert Laszczak, "Microservices or monolith? It's detail":<https://threedots.tech/post/microservices-or-monolith-its-detail/>
- Jon P Smith, "My experience of using modular monolith and DDD architectures":<https://www.thereformedprogrammer.net/my-experience-of-using-modular-monolith-and-ddd-architectures/>
- 辅助:Milan Jovanović, "Build a Modular Monolith in .NET":<https://milanjovanovic.tech/blog/build-modular-monolith-dotnet-step-by-step>;kgrzybek/modular-monolith-with-ddd:<https://github.com/kgrzybek/modular-monolith-with-ddd/discussions/213>