无为与 DDD:让模型顺着业务的纹理生长
“道常无为而无不为。”——《老子》第三十七章
引言
青岛崂山太清宫相传始建于西汉,已有两千余年历史。太清宫后山的老君峰下,有一尊高达 50 米的老子铜像。铜像中的老子一手指天、一手指地,仿佛在提示天地之间的幽微秩序。老子被尊为道家的奠基者;以其名义流传的《老子》一书,又被广泛称为《道德经》,是道家的重要经典。“无为”,正是其中最有代表性的思想之一。
把“无为”和 DDD 放在一起谈,乍看像一种跨界类比:一个来自两千多年前的思想传统,一个来自现代软件设计实践。前者谈治身、治国、处世,后者谈领域模型、限界上下文、统一语言。
但如果把表层词汇先放下,会发现它们都会触到一个相通的问题:面对复杂系统时,人应该如何理解事物,并据此行动。
“无为”不是不做事,也不是消极退让。它要求把行动限制在必要而自然的范围内:少一点任意,少一点过度,先看清事物自身的运行方式,再顺势而动,并且知道在哪里停手。老子说“以辅万物之自然,而不敢为”,这里的“辅”很关键:不是替万物安排一套外来的秩序,而是帮助它按照自身规律展开。
DDD 也不是一组架构术语或模式清单。Eric Evans 在 2003 年出版的 Domain-Driven Design: Tackling Complexity in the Heart of Software 中,把重点放在“领域”本身:复杂软件的设计,应该围绕业务领域的理解、模型和语言展开。技术结构要服务于业务理解,而不是反过来让业务去适配技术结构。
所以,这篇文章不是要说老子预言了 DDD,也不是要给 DDD 披上一层玄学外衣。更朴素的说法是:当我们面对复杂业务时,好的软件设计常常表现为一种工程上的无为:先理解,再行动;尊重边界,聚焦核心;少一点强行塑造,多一点准确呈现。
一、无为不是不作为,而是不妄为
对“无为”最常见的误解,是把它理解成消极怠工。但在《老子》的语境里,无为并不是“什么都不做”。它要减少的是妄为、强为、过度之为,以及违背自然规律的任意之为。
这个理解也可以从冯友兰的《中国哲学简史》中得到佐证。他谈到“无为”时强调,它并非完全无所作为,而是要“为得少一些”,避免过度,也避免任意地违背自然。
老子说:“道常无为而无不为。”这句话首先说的是道:道不以意志强作,却使万物各得其生。引申到人的行动上,它提示我们:当行动顺应了事物本身的规律,就不需要额外的强制、装饰和控制,也能产生效果。还有一个更具体的比喻:“治大国,若烹小鲜。”小鱼不能反复翻动,翻得越多越容易碎。复杂系统也类似。干预不是越多越好,规则不是越细越好,架构也不是越复杂越好。很多时候,问题不是因为做得太少,而是因为没看清系统的自然结构,却急于给它加控制、加抽象、加流程。
从软件设计的角度看,无为至少包含三个层次。
第一层:先观。
先观察事物如何运转,理解它的稳定结构、变化节奏和关键约束。老子说“不知常,妄作凶”。不了解规律就乱做,结果往往不是更快,而是更乱。
第二层:后行。
理解之后再行动。不是不设计,而是不在理解之前设计;不是不抽象,而是不在概念清楚之前抽象。顺势而为的行动看起来不费力,是因为它没有和事物本身对抗。
第三层:知止。
知道边界在哪里,知道什么不该管,知道哪里不该提前设计。老子说“知足不辱,知止不殆,可以长久”。在复杂系统里,知道什么时候停手,比知道还能加什么更难。
“妄为”正好相反。老子说“为者败之,执者失之”。软件里的妄为并不罕见:需求一来就建表,概念没搞清就拆服务,业务边界没看明白就上微服务,最后系统看起来做了很多,却越来越难改。
无为不是拒绝行动,而是把行动限制在必要、自然和适度的范围内。它拒绝的是在无知中行动,也拒绝在已经足够的时候继续加码。
二、DDD 的本质:不是模式,而是认知业务
DDD 很容易被学成一组术语:实体、值对象、聚合根、仓储、领域服务、领域事件、限界上下文。术语当然重要,但如果只学术语,就会错过 DDD 的核心。
DDD 的起点不是模式,而是领域。
Eric Evans 在 2003 年出版 Domain-Driven Design: Tackling Complexity in the Heart of Software,书名里最重要的词其实不是 design,而是 domain。DDD 要处理的是软件核心复杂性,而这些复杂性通常来自业务领域本身:业务概念不清、规则不断变化、角色理解不一致、边界互相污染。
因此,DDD 的基本顺序应该是:
- 理解业务:核心流程是什么?关键规则在哪里?哪些变化最频繁?
- 建立模型:哪些概念真正属于业务?它们之间有什么关系?
- 形成语言:业务人员和技术人员是否能用同一套词汇讨论问题?
- 划定边界:同一个词在不同场景下是否其实代表不同模型?
- 聚焦核心:哪些部分承载真正的业务差异和竞争力?
- 落到代码:代码结构是否表达了领域模型,而不是把业务淹没在技术框架里?
这不是瀑布式的固定流程,而是一种优先级:理解要牵引设计,设计再约束编码;编码过程中得到的新发现,也要反过来校正模型。
很多失败的系统恰恰反过来:先决定技术栈,再设计数据库,再按表生成接口,最后才让业务来解释这些结构为什么合理。这样做出来的系统,技术上可能整齐,业务上却很别扭。代码看似有层次,概念却没有边界;类名看似规范,业务人员却无法讨论。
极端情况下,这会演变成所谓的“简历驱动开发”(Resume-Driven Development):技术选型的动力不是业务需要,而是它能不能写进简历。微服务、Event Sourcing、六边形架构,一个都不少;架构图画得壮观,业务却淹没其中。
DDD 要求开发者放下“我来设计一个系统”的冲动,先进入业务现场,理解业务专家脑子里的模型、组织中的协作方式、真实流程中的例外和约束。它不是让技术消失,而是让技术服从于正确的业务认知。
这正是它与无为相通的地方:先观,然后再行。
三、道与德:战略设计与战术设计的总框架
老子说:“道生之,德畜之,物形之,势成之。是以万物莫不尊道而贵德。”
在《老子》中,“道”很难被当成普通概念来定义。道是“无名”的,它不是万物中的某一物,而是万物之所从生者;“德”则是一物从道那里获得的力与本性,一物自然地是什么,就是它的德。
所以,“道生之,德畜之”不是说有一个人格化的创造者在制造万物,而是在说:万物有其所从来的整体依据,也有各自得以成其为自身的内在本性。道更接近普遍的、整体性的根据;德则落在具体事物身上,表现为各自的特性。比如一棵树向上生长,一条河顺势下流,一只鹰翱翔高空,它们都可以被看作顺着自身之德而展开。
接下来做一个不那么严格的类比,借这个框架来理解 DDD。
道,接近领域的整体秩序。
一个业务系统不是一堆功能点的集合,而是有自己的全局结构:核心域在哪里,子域如何分布,不同上下文如何协作,哪些关系是上游和下游,哪些地方需要防腐层,哪些地方可以分道而行。这些问题属于 DDD 的战略设计。
德,接近每个上下文内部的自洽模型。
同样一个业务概念,落到不同上下文里,会呈现出不同的形态。订单上下文有订单的模型,库存上下文有库存的模型,结算上下文有结算的模型。实体、值对象、聚合、领域事件这些战术模式,只有放在具体上下文里才有意义。
换成 DDD 的语言:
| 道 | DDD 战略设计 |
|---|---|
| 领域的整体秩序 | 核心域、子域、限界上下文、上下文映射 |
| 决定系统如何分块 | 决定模型在哪里成立、上下文如何协作 |
| 不直接写代码,却决定代码边界 | 不一定落在某个类里,却影响所有类的位置 |
| 德 | DDD 战术设计 |
|---|---|
| 整体秩序在具体事物中的呈现 | 每个上下文内部的领域模型 |
| 各有其性 | 实体、值对象、聚合、领域事件各有职责 |
| 必须自洽 | 模型内部的规则、状态和行为必须一致 |
没有战略设计,战术模式会失去位置。你不知道一个聚合根“活”在哪个上下文里,也不知道一个领域事件应该被谁发布、谁消费、谁翻译。
没有战术设计,战略设计会停留在墙上的图。上下文地图画得再漂亮,如果每个上下文内部依然是贫血模型、过程脚本和混乱命名,战略边界也撑不住。
放在这个比喻里,DDD 的实践可以概括为一句话:
尊道,才能划清边界;贵德,才能让模型在边界内自洽。
四、尊道:边界、核心域与上下文
DDD 战略设计里最难的问题,往往不是“怎么实现”,而是“边界在哪里”。
这个功能应该属于哪个上下文?这个概念是不是全局统一的?两个团队说的“商品”“客户”“账户”“订单”,是不是同一件事?上下文之间是共享模型,还是通过防腐层翻译?这些问题如果答错,后面的代码越努力,系统越别扭。
限界上下文(Bounded Context)的核心,不是拆服务,而是承认模型有边界。一个概念只在某个上下文中具有明确含义;跨出这个边界,同一个词可能需要另一个模型。
以“商品”为例:
- 在商品目录上下文中,它关心标题、图片、类目、上下架状态。
- 在库存上下文中,它关心 SKU、仓库、可用数量、锁定数量。
- 在订单上下文中,它关心下单时的价格、规格、优惠和履约约定。
- 在采购上下文中,它关心供应商、采购价、入库批次和交付周期。
它们都可能被日常语言叫作“商品”,但它们并不是同一个模型。
如果强行设计一个全局统一的 Product,把所有属性、规则和状态都塞进去,短期看像是复用,长期看往往是边界污染。每个上下文都要为别人的规则让步,模型越来越臃肿,最终变成谁都不敢改的上帝对象。
《庄子·骈拇》里有一句很适合说明这个问题:“凫胫虽短,续之则忧;鹤胫虽长,断之则悲。”野鸭腿短,鹤腿长,本来各有其性。把短的接长,把长的截短,就是用外部标准扭曲事物本身。
限界上下文也一样。不要为了统一而统一。好的边界不是技术上看起来整齐,而是业务上自然、语义上清楚、协作上可控。
放到 DDD 里,尊道还意味着识别核心域。
资源永远有限,复杂度也不能平均分配。核心域(Core Domain)是系统中最能形成业务差异、最值得精细建模的部分。支撑域可以用相对简单的设计,通用域甚至可以采购或复用成熟产品。
一家初创保险公司,早期核心可能是新单承保,因为没有客户就没有一切;等新单系统稳定后,理赔体验可能变成新的关键能力;再往后,灵活的产品定义能力又可能成为竞争焦点。核心域不是永恒标签,它会随着业务阶段变化。
所以,战略设计不是画一张永不修改的图,而是持续回答三个问题:
- 业务真正的自然边界在哪里?
- 哪些上下文承载核心价值?
- 哪些地方应该翻译、隔离或保持距离?
这也可以理解为 DDD 中的一种“知止”:知道模型在哪里停止,知道复杂度应该集中在哪里,也知道哪里不该强行统一。
五、贵德:统一语言与领域模型
如果说在这个类比中,“尊道”解决边界问题,那么“贵德”解决的是边界内部的自洽问题。
在一个上下文内部,模型必须说同一种语言。这就是 DDD 中的统一语言(Ubiquitous Language)。
统一语言经常被误解成术语表。事实上,它远不止是把词汇列出来。它要求业务专家、开发者、测试人员、产品人员,在同一个上下文里使用同一套围绕领域模型组织起来的语言。更进一步,代码也应该使用这套语言。
这件事看起来简单,实践中极难。
比如一个电商系统里,业务方说“商品”,搜索团队叫 Item,库存团队叫 SKU,营销团队叫“货品”,数据库表叫 product_info,代码里还有个类叫 TradeRecord。这些词有时指同一件事,有时又不是。同义词和近义词混在一起,团队很快会失去共同理解。
统一语言的价值,不只是减少沟通成本。它更重要的作用,是逼迫团队回答:
- 这个概念到底是什么?
- 它在哪个上下文里成立?
- 它和相邻概念有什么区别?
- 业务人员是否真的会用这个词讨论规则?
- 代码里的行为是否能用这个词解释?
命名困难,通常不是词汇量问题,而是理解问题。如果你不知道一个类该叫 Policy、Contract 还是 InsuranceOrder,真正需要做的可能不是查词典,而是回到业务里问清楚:这个对象到底代表承保前的申请、承保后的保单,还是交易过程中的订单?
领域模型里不应该到处都是 DTO、DAO、Manager、ServiceImpl。这些词属于技术实现,不属于业务语言。它们可以出现在技术层,但如果领域层充满这些名字,说明模型还没有真正回到业务。
好的领域模型应该让业务专家能参与讨论。业务专家不一定懂代码细节,但他应该能指出:“这里不该叫续保,这是重新投保”“这个状态不是已支付,而是待清算”“订单取消和订单关闭不是一回事”。
这就是贵德:让每个上下文内部的模型按自己的业务本性生长,而不是被技术模板压成同一个形状。
六、发现而非发明:事件风暴与模型浮现
当然,严格说领域模型不是像矿石一样完整埋在那里,等开发者挖出来就行。建模一定包含选择、抽象和取舍。不同团队对同一业务也可能建立不同模型。
但好的建模更像“发现业务中的稳定结构”,而不是凭空发明一个技术上自洽、业务上陌生的世界。
事件风暴(EventStorming)体现了这种态度。它让业务专家和技术人员站在同一块建模空间前,从领域事件开始讨论:
- 业务中发生了什么?
- 这个事件由什么命令触发?
- 谁发起命令?
- 哪些规则决定命令能否成功?
- 事件发生后会影响哪些流程?
- 哪些地方存在分歧、异常和外部系统依赖?
领域事件通常用过去式命名,比如“订单已提交”“支付已完成”“库存已锁定”“保单已承保”。过去式很重要,因为它把讨论锚定在业务事实上,而不是抽象的数据结构上。
这和传统的技术驱动建模很不一样。
技术驱动的方式常常从表开始:用户表、订单表、商品表、状态字段、扩展字段。业务规则随后被塞进 service、if-else 和配置开关里。久而久之,代码里有数据,却没有业务语言;有流程,却没有模型;有实现,却没有理解。
事件风暴则反过来。它先让团队看见业务如何发生,再逐步识别命令、规则、聚合、上下文和协作关系。模型不是架构师在房间里单独想出来的,而是在业务专家和技术人员的共同探索中浮现出来的。
设计者不是造物主,而是观察者、整理者和表达者。你当然要设计,但设计的方向应该来自业务本身,而不是来自你偏爱的框架、分层或数据库结构。
七、有为的代价:过度设计与大泥球
如果说无为要求少一点任意和过度,那么软件里的“有为”常常表现为两种极端:一种是过度设计,一种是无结构堆砌。它们看似相反,根子却一样:都没有真正理解业务。
先看过度设计。
有些团队刚学 DDD,就想把所有战术模式一次性套上:聚合根、值对象、领域事件、仓储、CQRS、Event Sourcing、六边形架构,一个都不少。结果一个本来只是录入、查询、报表为主的后台系统,被设计成团队自己都难以维护的复杂系统。
这不是 DDD,这是仪式感。
DDD 是用来处理复杂业务的,不是用来给简单问题增加复杂度的。一个 CRUD 应用就用 CRUD,一个简单流程就用简单分层。只有当业务规则复杂、概念相互牵连、变化频繁、语言混乱时,DDD 的建模成本才更容易得到回报。
再看无结构堆砌。
很多系统最后会长成大泥球(Big Ball of Mud)。它通常不是某个人一开始就想写烂系统,而是许多看似合理的小妄为叠加出来的:
- 需求来了,没澄清业务概念,先建表。
- 流程没看清,先按页面写接口。
- 边界没划清,先把所有逻辑放进一个 service。
- 名称不统一,靠注释和口头约定维持理解。
- 变更来了,不敢重构,只能继续加字段、加分支、加开关。
每一步看起来都像是在节省时间,合起来却让系统越来越慢。慢在理解成本,慢在修改成本,慢在新人上手,慢在每次改动都怕牵一发动全身。
过度设计和大泥球,一个是做了太多不必要的事,一个是该做的理解和建模没有做。但它们共同违背了无为的精神:没有顺着业务纹理行动。
DDD 的价值,不是保证系统永不腐化,而是提供一组抵抗腐化的实践:用统一语言让概念可讨论,用限界上下文控制模型边界,用核心域分配注意力,用领域模型承载业务规则,用事件和上下文映射显式表达协作。
前期看起来慢,实际上是在减少后期无穷无尽的补丁和误解。
八、无为而无不为:好的模型让变化变自然
“道常无为而无不为。”放到软件设计里,可以理解为一种很朴素的状态:当模型足够贴近业务,很多事情会变得自然。
需求变化不再像地震。
因为新需求往往不是从天而降的异物,而是领域变化的体现,它能在模型中找到落点。你知道该改哪个聚合、哪个规则、哪个上下文,而不是在全局搜索里碰运气。
模块之间不再频繁拉扯。
因为上下文边界根据业务协作来划分,各自可以独立演进。需要协作的地方通过事件、API、防腐层或明确的映射来翻译,而不是共享一堆模糊对象。
团队沟通变得顺畅。
统一语言让业务专家和开发者可以在同一个语义空间里讨论问题。会议上说的词,代码里也能找到对应表达。
新人上手更快。
因为代码结构和业务结构相互映照。理解了业务流程,就能大致理解代码组织;读代码时,也能反过来加深对业务的理解。
复杂度集中在值得的地方。
核心域得到精细建模,非核心部分保持简单。团队不会把所有地方都做成同样重,也不会把真正关键的业务规则淹没在技术细节里。
这就是工程上的“无为而无不为”:不是没有设计,而是设计没有和业务对抗;不是没有抽象,而是抽象来自真实概念;不是没有边界,而是边界顺着业务逐渐浮现出来。
好的领域模型有点像水。它不强行改变地形,而是顺着地形流动,填满该填的地方,绕过该绕的障碍。坏的架构则像水泥,浇下去很整齐,凝固后再也动不了。业务一变,只能拿锤子凿。
结语:不是玄学,是工程上的克制
这篇文章,不是要把老子封为软件架构的祖师爷。两千多年前的人不需要为微服务背书,DDD 也不需要靠古人来增加权威。
真正值得讨论的是:好的方法论背后,常常有相通的认知态度。
“无为”提醒我们,不要在没有理解规律时强行干预。DDD 提醒我们,不要在没有理解业务时急着设计系统。二者来自不同语境,却指向相近的警惕:面对复杂系统,最危险的往往不是做得太少,而是在理解不够时做得太多。
好的设计不是把架构师的意志强加给业务,而是让业务中已有的概念、规则和边界得到清晰表达。该深入的地方深入,该停止的地方停止;该建模的地方认真建模,该简单的地方保持简单。
如果要用一句话概括无为与 DDD 的共鸣,我会选这句:
先理解,再行动;尊重边界,聚焦核心;让模型顺着业务的纹理生长。
这不是玄学,而是工程上的清醒。