架构即上下文路由
架构是上下文获取的路由图。本章说明各种架构风格如何与修改模式相匹配,放置规则为何是复用的前提,以及修改后的反思如何维持路由的可信度。
本文为中文版,根据英文原版改写,而非逐句直译。
人们谈论软件架构时,通常从看得见的结构说起:分层、模块、服务、组件、依赖方向、接口、适配器、边界。这些描述有用,但很容易让架构看起来只是一组静态摆放的方框。
从 CMP 的视角看,架构首先关心的不是系统长什么样,而是当系统需要变化时,修改者应该往哪里去。
当开发者或 AI 编程智能体开始一次修改时,架构问题一点也不抽象,而是眼前的事:
- 这个决策应该放在哪里?
- 要获取所需的上下文,应该沿着哪条路径走?
- 系统的哪些部分根本不需要搜索?
- 哪个边界足够可信,让我可以停止阅读?
- 哪个所有者、模块、层、上下文或扩展点负责这次修改?
这正是架构的实际价值所在。
架构是一套上下文路由(context routing)系统。
它的成本是否值得,取决于它能否让决策的放置位置变得可预测。它告诉未来的修改者如何获取充分上下文,而不必把每次修改都变成一次全代码库搜索。
好的架构并不消除上下文,而是为上下文提供路由。它告诉我们从哪里开始、哪些路径是正当的、相关决策应该在哪里找到,以及搜索可以在哪里停下。
坏的架构也许仍然具备架构的一切外在形式:分层、目录、模块、接口、服务和架构图。但如果这些结构已经无法预测决策实际所在的位置,它们就不再承担路由功能。地图还挂在墙上,却已经不能用了。
架构的存在是为了减少无边界搜索
CMP 的出发点很简单:软件设计之所以存在,是因为软件必须被修改。正确的修改需要充分上下文。对于现实中的修改,所需的充分上下文越容易获取,设计就越好。
在局部层面,边界的契约若能让修改者停止阅读,就能降低遍历深度;相关的修改面若集中放置或有索引可查,局部性就能降低广度。架构把同样的逻辑应用到整个系统的尺度上。
架构降低的是无边界搜索的成本。
没有架构时,修改者常常不得不面对一些开放式的问题:
- 这条规则在 controller、service、model、数据库触发器,还是前端?
- 这项校验是不是在 API、UI、导入任务和报表流水线里各写了一份?
- 别处是否已有可复用的实现,还是我该自己写一个?
- 这个概念是全公司共享的,还是在每个领域里含义各不相同?
- 这个行为归核心系统、插件、工作流,还是某个集成所有?
这些不只是理解上的难题,更是修改风险。如果修改者无法可靠地知道某个决策属于哪里,就无法知道自己的上下文获取何时算是完整。
架构通过施加放置规则来回答这些问题。
放置规则说的是:这类决策属于这里,而不是随处都有。领域规则归领域所有者;基础设施细节藏在适配器后面;跨上下文通信经由公开的契约;特性专属行为放在特性切片内部;插件专属行为放在扩展点后面;团队负责的业务能力放在所属的模块或服务里。
放置规则在两个方向上起路由作用。正向看,它告诉修改者一个新决策该放在哪里,让补丁落在未来修改者预期的位置。反向看,它告诉修改者一项既有能力本该在哪里,让他们在另起炉灶之前先去找可复用的实现。回答”这个该放哪儿”的同一条规则,也回答了”这个是不是已经有了”。
复用依赖的正是这个反向方向。当一项能力有可预测的归属地时,确认它是否已存在只是一次有边界的查找;没有归属地时,“有没有人已经做过这个”就成了又一次无边界搜索,而在上下文成本的约束下,修改者理性的选择是停止搜索、重新写一份。重复代码是缺失放置规则在反向搜索上的失败,而不是纪律上的疏忽。
放置规则成立时,修改者可以为搜索规划路线:从一个有边界的区域出发,忽略无关区域,在可信的边界处停下。系统之所以更便于修改,不是因为代码更少,而是因为所需的上下文更容易找到。
放置规则失效时,修改者就无法信任架构。他们先去架构所说的位置找,然后照样把其他地方都搜一遍。到了这一步,架构就沦为纯粹的承载深度:系统依旧为它的结构付出成本,却失去了减少搜索的收益。
架构选型就是修改模式匹配
架构风格常被当作彼此竞争的理想来讲授。分层架构、整洁架构(Clean Architecture)、六边形架构、DDD 的限界上下文(Bounded Context)与上下文映射(Context Map)、垂直切片架构(Vertical Slice Architecture)、微服务、模块化单体、插件架构、实体-组件-系统(Entity-Component-System),各有一套自己的词汇和结构。
CMP 换了一个角度来看待它们。
每一种架构风格都内嵌了一个对未来修改的预测。
一种架构风格实际上在说:未来的变化通常是这种形状,因此上下文应该沿着这种拓扑来路由。
预测正确时,架构降低上下文成本;预测错误时,架构就变成了间接层、繁文缛节或碎片化。
这意味着架构不该凭品味、资历、潮流或架构图是否优雅来选择,而应当让路由拓扑去匹配系统中占主导地位的修改模式。
要问的不是:哪种架构最干净?
要问的是:这个系统在现实中必须支持哪些类型的修改?对这些修改而言,哪种架构让所需上下文的获取最便宜?
举几个例子:
- 一个产品团队反复给后台页面加小字段:加一列,在 API 中暴露,做校验,存进库,在表格里显示,再更新一个测试。变化总是纵向贯穿整个技术栈。
- 一个定价团队频繁调整折扣、资格、税费或计费规则,而数据库、Web 框架和 UI 的变化出于别的原因。策略和机制各自演化。
- 一个交易平台在商品目录、库存、搜索、履约和会计中对
Product一词的用法各不相同。同一个词到处出现,但每个领域在不同的业务压力下改变它的含义。 - 一个不断壮大的组织发现,大多数变更的延误不是因为代码复杂,而是因为要弄清哪个团队拥有这条规则、哪个服务拥有这份数据、其他团队可以依赖哪份契约。
- 一个平台不断接入新的支付渠道、导入格式、通知渠道、AI 工具或编辑器扩展。每项新能力都沿着同一条扩展路径加入,核心不应该每次都被重新打开。
- 一款游戏或仿真系统不断加入横跨对象类型的行为:可移动的东西、可受伤的东西、持有物品栏的东西、临时状态效果、对 AI 可见的对象。这些变化并不沿着一棵清晰的继承树展开。
- 一个报表或分析功能反复修改读取形态、过滤条件、投影和仪表盘,而核心交易规则保持稳定。大部分工作是表示层的变化,而非领域策略的变化。
- 一个合规要求很重的工作流,只要漏掉一个校验器、一个审批步骤、一条审计记录或一条通知就会出错。难点不只是做出修改,而是可靠地获取整个闭包。
这些情形就是修改模式。它们描述未来工作反复出现的形状:变化从哪里开始,哪些构件倾向于一起变,哪些决策需要稳定的所有者,以及哪些遗漏会让修改出错。
有了这个视角,各种架构风格就更容易比较了。它们不是万能方案,而是针对不同修改流优化的路由拓扑。
分层、整洁与六边形架构:把策略与机制分开路由
分层架构、整洁架构和六边形架构做出了一个很强的预测:策略与机制在不同的压力下变化。
说得简单些,策略关乎系统应该做什么,机制关乎系统怎么做到。策略包括业务规则、用例、领域行为、不变量和应用特有的意图;机制包括执行这些决策的工具和交付细节:数据库、框架、用户界面、传输协议、队列、文件系统、支付服务商和基础设施 API。
它的路由承诺很清楚:
- 策略的变化应当向内路由;
- 机制的变化应当向外路由;
- 修改领域行为不应需要加载框架、数据库、UI 或传输层的上下文;
- 修改基础设施不应改写领域策略。
这就是这些架构把端口、适配器、接口、仓储、controller、网关和用例围绕策略核心来放置的原因。价值不在于图的形状,而在于修改者可以先问一句:我改的是策略,还是机制?
如果是策略,搜索就该朝领域层或用例层推进;如果是机制,就该朝适配器和基础设施推进。只要这种区分是稳定的,这套路由拓扑就能发挥作用。
例如,假设一条定价规则变了。在路由良好的系统里,修改者不需要查看 HTTP controller、ORM 映射细节、框架注解、数据库驱动或前端状态管理。这些东西也许参与执行,但不应拥有定价决策。架构让修改者在获取无关的机制上下文之前就停下来。
但在一个简单的 CRUD 系统里,同样的结构可能是一笔糟糕的交易。如果大多数修改都是字段级的垂直切片——在表单里加个字段、校验、保存、在表格里展示——那么严格的策略/机制分离就可能迫使每次普通修改都穿过多个层、DTO、端口、映射器、仓储和适配器。架构预测策略与机制会分开演化,实际的修改模式却总是把两者一起贯穿。这时架构并没有路由上下文,反而把修改闭包切碎了。
当策略复杂度足够高、机制的易变性又足够独立,以至于分开两者的获取路径所减少的上下文成本超过了边界本身带来的成本时,整洁架构或六边形架构才是合理的。
如果日常修改流大多是简单的纵向 CRUD,增加的边界又不能让修改者更早停下,那就是过度架构。
DDD 限界上下文:为语义上下文提供路由
领域驱动设计(DDD)的范围比架构更广。它包含一整套领域建模的方法:通用语言、聚合、实体、值对象、仓储、领域服务等等。本文关心的是其中属于架构的部分,即 DDD 的战略设计,尤其是限界上下文和上下文映射。
DDD 的这一部分做出了一个具体的架构预测:语义不会在全局范围内保持一致,每个领域都需要独立演化的空间。
在规模足够大的业务中,同一个词在不同地方可能意味着不同的东西。销售、履约、发货和会计中的”订单”,可能带有不同的规则、生命周期状态和不变量。更重要的是,这些含义并不在同一时间、出于同一原因而变化。商品目录可能细化商品分类方式,库存可能调整库存预留方式,计费可能改变商品费用的确认方式,推荐可能改变商品的排序方式。单一的共享模型会迫使所有这些变化都挤过同一个语义面。
限界上下文为语义上下文提供路由。
它的承诺不只是模块化,而是:在某个领域内工作的修改者,不必加载整个企业模型。他们可以依赖本地上下文中的语言、模型、不变量和规则。当需要进入另一个上下文时,要经过显式的翻译边界:API、事件、公开语言、防腐层或契约。
它的路由承诺是:
- 语义变化留在所属的上下文内部;
- 跨上下文的理解经由显式契约来路由;
- 任何修改者都不必为了一次本地领域修改而加载一个通用模型;
- 任何外部调用者都不应依赖另一个上下文的内部模型。
只有当系统大到这些语义面需要独立演化时,这种风格才有价值。如果业务规模不大,或者一个团队仍能在无需反复协商的情况下维护单一共享模型,那么限界上下文可能在产生真正的路由价值之前,就先带来了翻译成本。
这里有一个措辞上的陷阱需要注意:DDD 也使用 context 这个词。本节中的限界上下文指的是领域特定的语义边界,与 CMP 中更宽泛的”上下文”——即修改所需的信息——不是一回事。这本身也展示了 DDD 中典型的语义问题:context 一词在 DDD 的限界上下文和 CMP 的限界上下文里含义不同。
当语义上的修改闭包天然是局部的、否则就会被全局模型污染时,DDD 的限界上下文架构就是合理的。
如果系统没有足够的语义断裂来抵消翻译成本,那就是过度架构。
垂直切片架构:按特性级修改面路由
垂直切片架构做出的预测几乎与严格分层设计相反。
它预测大多数修改以特性级或用例级切片的形式到来。一次变化通常不会只影响 controller 层,或只影响 service 层,或只影响持久化层,而是影响一条完整的路径:请求处理、校验、命令或查询逻辑、持久化交互、响应形态以及测试。
它的路由承诺是:
- 一个特性的变化应该在一个特性切片内就能找到;
- 用例特有的行为不应散落在各个横向分层中;
- 修改者不必为了理解一个产品行为而在整个技术栈中来回跳转;
- 特性级修改的闭包应当局限在切片之内。
在产品本身就是一组相互独立的工作流、页面、命令或用户操作的系统中,这种风格很有效。许多内部业务应用、SaaS 后台系统、管理面板、工作流工具、审批系统、内容管理系统和报表门户都是这种形态。大多数变化并不深入共享的领域模型。每次变化都有一个可辨识的产品入口、有限的规则面,以及一个基本属于该工作流的修改闭包。架构把上下文路由到这个切片上,而不是逼着修改者穿过全局的 controller、service、repository 和持久化层。
如果表面上的特性只是更深层共享策略的外壳,垂直切片就不再合适。比如修改优惠券可能牵涉定价、资格、税费、计费和促销规则。在这样的系统中,真正的所有者不是优惠券页面或请求处理器,而是定价或促销策略。把这些逻辑放进彼此隔离的切片里,会让决策被复制多份,未来修改规则时更难获取上下文。
它的优势在于局部性。一个切片可以把请求模型、处理器、校验、查询、命令、测试以及特性专属的小决策放在一起。对很多应用系统来说,这比横向分层更符合变化的真实形状。
当特性级修改闭包占主导、共享的领域策略规模小、表达明确或另有所属时,垂直切片架构是合理的。如果共享的领域决策被复制到各个切片里,又没有可靠的索引把它们连起来,它就成了架构不足。
这个对比很重要。整洁架构和垂直切片架构并不是简单的对手,它们针对的是不同的修改模式。整洁架构按策略与机制来路由,垂直切片架构按用例来路由。哪个更好,取决于系统更常需要哪条上下文获取路径。
模块化单体与微服务:为所有权上下文提供路由
本节与 DDD 的限界上下文架构很接近,但处在更低一层。
限界上下文是语义边界:它规定一个模型、一套语言、一组规则和一种业务含义可以在哪里独立演化。模块或服务边界则是实现与所有权的边界:它规定哪些代码、数据、运行时、契约和团队拥有这块变化区域。
在对齐良好的系统中,实现边界往往与语义边界一一对应。一个拥有自己语言和规则的领域,在模块化单体中成为一个模块,在微服务架构中成为一个服务。这就是两种想法常常让人觉得相似的原因:一个从领域含义上描述边界,另一个在软件和组织上落实边界。
但这种映射并非天然一对一。小系统可能在一个可部署的单体里容纳多个语义上下文;大系统也可能出于扩展、团队所有权、运行隔离或独立发布的考虑,把一个宽泛的领域拆成多个服务。重要的不是每个限界上下文是否恰好变成一个服务,而是实现边界是否保留了领域边界所隐含的修改路径。
模块化单体和微服务都通过所有权边界来路由上下文,区别在于边界的强度。
模块化单体保持统一部署,但强制执行内部模块边界。微服务架构则通过独立部署、运行时隔离、网络契约、独立数据库,往往还有团队所有权,来强化这些边界。
它的路由承诺是:
- 每项业务能力都有所有者;
- 大多数修改应当在所属模块或服务内部完成;
- 跨边界的变化应当经由契约、API、事件或显式协调;
- 修改者在修改本地能力时,不需要加载无关的业务能力。
当所有权成本主导修改成本时,这种风格就变得有价值。
在一个团队构建的小系统中,一个模块清晰的单一代码库可能就够了。跨进程边界、网络调用、部署流水线、分布式追踪、最终一致性和契约版本管理的成本,可能超过它带来的收益。
然而在更大的组织里,上下文成本常常变成社会-技术问题。修改者需要的不只是读代码,还要知道谁拥有某条规则、哪个团队能批准变更、哪个服务可以发布某个事件、哪份契约是稳定的、哪个数据库是私有的,以及跨边界时允许哪些假设。
当硬性的技术边界所减少的组织上下文成本超过它带来的分布式系统成本时,微服务就是合理的。它们迫使修改路径尊重团队和能力的所有权。
但如果实际的修改模式让普通变更也要横跨许多服务,微服务就是一笔坏交易。这时每个特性都变成了分布式修改。修改者必须跨越网络边界、数据所有权边界、部署边界、监控系统和团队职责去获取上下文。架构不是在降低上下文成本,而是在成倍放大它。
模块化单体往往是一种中间形态的路由拓扑。它可以让所有权变得可见,又不必把每个边界都变成分布式系统的边界。
CMP 的关键判断不是单体与微服务之争,而是实现边界是否保留了系统真实的修改路径。保留了,模块和服务边界就能降低上下文成本;没保留,它们就把本地修改变成分布式搜索。
插件架构:通过扩展点路由重复出现的变化
插件架构做出一个非常具体的预测:未来的修改会沿着稳定的扩展轴反复到来。
例子包括编辑器插件、支付渠道、导入导出格式、认证策略、通知渠道、工作流动作、AI 工具、语言集成以及自定义业务规则。
它的路由承诺是:
- 新能力应通过扩展点加入;
- 核心不需要为每个新变体而修改;
- 插件元数据、清单、注册表或契约应当为所有参与者建立索引;
- 修改者应当确切知道新扩展该放在哪里。
这是一种很强的上下文路由模式。架构把反复出现的广度变成了有索引的扩展。
没有插件架构时,每接入一个新的服务商或新能力,可能都要修改核心分发逻辑、配置、UI 选项、权限检查、文档、测试和部署逻辑。修改闭包很宽。
有了插件架构,修改者沿着一条可预测的路线走:实现契约、声明元数据、注册插件、补上测试,然后让核心通过扩展机制发现它。闭包也许仍包含好几个构件,但架构让它们都可以到达。
它的失败模式是形状错误的可扩展性。
如果未来的变化轴尚不明确,过早设计插件系统可能会把错误的边界固化下来:扩展点暴露了错误的契约,要求了错误的生命周期,隐藏了错误的假设,或支持了错误类型的变化。之后的修改者只能与插件架构对抗,而不是利用它。
当变化轴稳定且反复出现时,插件架构才是合理的。
实体-组件-系统:通过组合路由状态与行为
实体-组件-系统(ECS)常在游戏开发和仿真系统中被讨论,有时还与性能联系在一起。但从 CMP 的视角看,它的架构价值不只在性能,它还是一种针对高度组合式变化的路由拓扑。
传统的面向对象层次结构预测未来修改沿着分类体系展开。要找某个行为,就沿着类层次往下走:Entity、Character、Player、Enemy、Vehicle、Projectile,等等。
ECS 的预测不同:未来的行为变化会横切分类体系。
一个游戏特性可能影响所有可移动的东西、所有可受伤的东西、所有有生命值的实体、所有受重力影响的实体、所有带物品栏的对象、所有临时状态效果,或所有对 AI 感知可见的对象。这些修改模式无法干净地映射到继承树上。
ECS 把状态路由到组件,把行为路由到系统。
它的路由承诺是:
- 数据存放在组件中;
- 行为存放在系统中;
- 特性修改沿着组件与系统的组合展开,而不是遍历类层次;
- 增加一项能力,就是用相关的组件和系统来组合实体。
当领域具有组合性时,这能降低上下文成本。修改者不再问”哪个子类拥有这个行为?“,而是问”哪个组件表示这个状态,哪个系统处理它?”
当未来变化难以沿单一分类体系预测、却反复组合彼此正交的能力时,这种架构是合理的。
如果领域小、稳定且天然具有层次结构,ECS 就不合适。这时它会增加概念面,却带不来足够的路由收益。
说到底,这仍是修改模式匹配。只有当组合确实是变化的真实形状时,ECS 才奏效。
架构何时有效
上面这些架构风格结构各异,但可以用同一个问题来评判:这种架构是否沿着系统真实的修改模式来路由上下文?
如果大多数变化能把策略与机制分开,整洁架构或六边形架构可以把领域决策路由到远离框架和基础设施细节的地方。如果难点在于各业务领域之间的语义独立,DDD 的限界上下文架构可以把每个领域的语言和模型路由进各自的语义边界。如果大部分工作以独立的工作流或操作页面的形式到来,垂直切片架构可以按用例路由上下文。如果修改成本主要来自所有权、团队协调或业务能力边界,模块化单体或微服务可以经由模块或服务来路由上下文。如果系统反复沿着稳定的扩展轴增长,插件架构可以通过扩展点路由新能力。如果行为高度组合、横切对象分类体系,ECS 可以通过组件和系统来路由状态与行为。
这些选择并非在每个尺度上都互斥。一个系统可以在领域层面使用限界上下文,以模块化单体作为实现边界,在模块内部使用垂直切片,并为反复出现的扩展设置插件点。关键的判断在于:每个边界是否回应了一个真实的修改模式,而不只是多加了一种架构形状。
因此,有效的架构让上下文变得有边界、可发现、可信。
有边界,是指上下文路径有边界线。修改者知道从哪里开始、在哪里停下。定价规则属于定价领域,支付渠道属于支付集成端口之后,插件属于扩展点之后,发货概念属于发货领域之内。
可发现,是指相关的修改面可以从两个方向到达。正向看,修改者可以通过名称、类型、测试、注册表、schema、契约、所有权记录、依赖规则或架构测试,找到闭包的其余部分,即必须一起修改的那些构件。反向看,修改者可以在新建之前先找到已有的东西,弄清某条规则、某项能力或某个辅助函数是否已经实现、实现在哪里。构件之间的关系记录在系统里,而不只是记在某位资深工程师的脑子里。
可信,是指这些路径可以依赖。这是最重要的一条性质。如果修改者不信任架构,就会绕开它走。他们会到处搜索,逐层通读,不相信任何边界,并且为了局部方便而复制决策。
只有当架构让修改者可以停止搜索时,它才真正起作用。
这种”让人停下”的能力才是真正的回报。不能终止遍历的边界只是间接层;不能预测决策位置的分层只是文件夹;与所有权不一致的服务边界只是分布式开销;与真实变化轴不匹配的插件契约只是繁文缛节。
检验架构的标准不是架构图是否整洁,而是未来的修改者能否沿着架构声称提供的路径获取充分上下文。
可复用能力需要一个归属地
“可发现”这一性质有一个容易被忽视、却很尖锐的推论:只有当既有能力能被找到时,复用才可能发生;而能否找到,取决于放置规则和所有权。
上面大多数风格路由的是领域决策,而领域决策天然有所有者:定价逻辑在定价领域,发货逻辑在发货领域。横切性的能力——日期格式化、重试包装、解析辅助函数、校验原语——却没有领域可以依附。解决办法很平常:一个有明确所有者的 tools 或 utility 模块,通常就能让它们保持可被找到。关键在于,这条放置规则及其所有权本身就是架构的一部分。当架构规定了这类能力放在哪里、由谁负责时,修改者在自己动手之前会先去一个可预测的地方查看。当架构对此不作规定时,就没有可以首先查看的地方,重复便成了结构上的默认结果,而非纪律上的失败。这在 AI 出现之前就存在:人类维护的代码库会出于完全相同的原因一再重新发明同一个辅助函数。
这对架构构成了一个具体的要求。一项可复用能力必须有可预测的放置规则和明确的所有者,即使它不属于任何领域——可以是共享内核、有指定所有者的工具模块、注册表,或是让它保持可达的索引。可发现性是复用的前提,而放置和所有权又是可发现性的前提。一个架构即便把每个领域决策都路由得无可挑剔,只要让共享能力无家可归,仍然会不断积累重复,因为对每个修改者来说,看起来正确又最便宜的做法就是自己写一份。
智能体让这一要求变得容易验证。每当一次修改去寻找某项既有能力、没能找到权威实现、于是新建了一份时,执行轨迹就记录下了一项无家可归的职责:一项没有可发现归属地的能力。每次出现,都标出了一条架构尚未定义的放置规则。
架构腐蚀即路由失效
架构腐化常被描述为”不纯”:领域规则写进了 controller,基础设施关注点渗进了领域,跨越边界的直接依赖,不该共享的共享辅助函数,绕过端口的捷径,被削弱的架构测试。
CMP 给出了一个更尖锐的解释。
架构腐蚀就是路由失效。
眼前的捷径往往看起来无害:当前的 diff 更小,任务完成得更快,也没有发生什么看得见的灾难。但放置规则已经被削弱了。
放在 controller 里的领域规则,会让未来的修改者以为规则可能存在于 controller 中。领域模型里的持久化假设,会让他们以为机制上下文可能向内泄漏。通过共享表绕过上下文的捷径,会让他们以为语义边界不可信。复制了命令侧不变量的查询投影,会让他们以为读模型可能暗中承担着正确性。
每一次违规都做了两件事。
第一,它制造了局部的隐藏广度。决策如今落在一个未来修改者可能不会去看的地方。
第二,它削弱了全局的路由拓扑。未来的修改者再也不能相信同类决策就在架构所说的位置。
这就是小小的架构违规为何代价不成比例。损害不只是那一行局部代码,而是路由信任的流失。
这与依赖信任的行业颇为相似。银行、保险公司、交易所或认证机构,并不仅仅靠完成一笔笔交易而存续,它们的价值取决于人们相信其承诺可以依赖。一次小小的失信,人们不会只按这次失信本身的大小来评估,而会追问一个更大的问题:整个机构是否还值得信任?架构也有同样的性质。一旦修改者在某个 controller 里发现了一条藏起来的领域规则,问题就不再只是这一条规则,而变成了:架构还在哪些地方说了谎?
信任一旦丧失,修改者就要付两次代价。他们依旧要承担架构的承载深度:分层、服务、接口、契约、适配器、测试、部署单元;却再也得不到有边界搜索的好处。他们必须在官方路径之内和之外都搜一遍。
每次修改都在穿越架构
架构并非只在设计评审时才被使用,每一次修改都在使用它。
修改者从某个入口出发:一个失败的测试、一份缺陷报告、一个特性需求、一个 API 端点、一条领域规则、一个 schema 字段、一个 UI 行为、一条日志或一个事件载荷。
然后他们获取上下文。他们沿着名称、类型、测试、调用链、依赖边、schema、所有权说明、文档、注册表和运行时行为前进。他们遇到边界,决定是否信任它;跨过一些边界,在另一些边界前停下;找出决策的所有者;最后把补丁放到某个位置。
这条路径并非偶然,它是架构路由行为的一个经验样本。
一次修改能揭示架构是否真的有效:
- 自然的入口是否把修改者引向了正确的所有者?
- 边界契约是否让修改者得以停下?
- 所需的闭包是否变得可发现?
- 修改者是否需要全局搜索?
- 最终的补丁是否落在未来修改者会预期的位置?
- 这次变更是加强了还是削弱了放置规则?
每一次修改,要么强化架构,要么腐蚀架构。
这在 AI 编程智能体身上尤其明显。智能体不像资深工程师那样拥有团队的默会记忆,它们更依赖显性的构件:文件名、测试、类型、文档、依赖结构和工具反馈。当架构清晰地路由上下文时,智能体可以顺着走;当路由是隐性的或已被腐蚀时,智能体就退回到大范围搜索和局部打补丁。
AI 并没有消除对架构的需要,反而让架构的质量变得更加可观察。
修改后反思:维护路由
评估架构的最佳时机,是一次修改刚刚完成之后。
这时修改者手里有新鲜的证据:他们知道打开了哪些文件、哪些边界没能终止遍历、哪些概念难以定位、哪些关系可以被发现、哪些决策的所有权不清楚,以及最终的补丁落在了哪里。
这让修改后反思成为一种实用的架构维护手段。
反思不需要重新设计系统,只需要问:这次修改是否遵守了架构的路由承诺?
局部性检查问的是:修改闭包是否仍可通过架构预期的路径到达:
- 从修改面中任何一个有意义的入口出发,能否沿着系统的常规路由路径——模块所有权、领域边界、特性切片、注册表、schema、测试结构或依赖规则——到达闭包的其余部分?
- 修改者是否不得不抛开架构,转而依靠全局搜索、记忆或猜测?
- 这次变更是否暴露出缺失的架构索引,比如所有权说明、契约测试、注册表条目、schema 关联、架构规则或文档路径?
边界检查问的是:被触及的边界是否仍然起着路由终点的作用:
- 这次任务触及了哪些架构边界:层边界、领域边界、模块边界、服务边界、适配器边界、扩展点,还是公共接口?
- 边界契约是否提供了足够的上下文让修改者停下,还是他们必须越过边界去找回被隐藏的细节?
- 调用方特有的假设是否重塑了某个共享边界,使未来的路由变得更难预测?
所有权检查问的是:
- 发生变化的决策归谁所有?
- 补丁是否落在了该所有者的上下文中?
- 是否有某个辅助函数、抽象或共享模块承担了不清晰的职责?
- 这次修改是否因为没有所有者或归属地让原有能力可被发现,而重建了一项能力?
- 是否有某种局部便利让一个决策离开了它应有的归属地?
路由完整性检查问的是:
- 未来的修改者是否仍能信任原有路径?
- 这次变更是否引入了捷径式依赖?
- 它是否削弱了某个架构测试、契约测试或依赖规则?
- 它是否在没有为闭包建立索引的情况下,跨边界复制了某个决策?
这种反思规模很小,却直指架构腐化的真实机制。架构通常不会因为一次重大失误而崩塌,而是在许多局部看来合理的捷径中慢慢腐化。修改后反思能在这些捷径还小的时候就把它们抓住。
这种反思对人类开发者有用,但特别适合 AI 编程智能体。人的记忆是选择性的,也会丢失细节;任务完成后,开发者也许记得某次修改感觉很乱,却记不清究竟是哪些搜索失败了、哪个边界最先变得可疑。智能体的上下文窗口里往往还保留着大部分上下文获取的历史:查看过的文件、尝试过的搜索、遍历过的路径、测试反馈、修正过的假设以及跨越过的边界。这让修改后反思变得格外精确。它可以把智能体的工作轨迹变成架构反馈:哪些路径有效,哪些边界失效,哪些所有权不清,以及应该补上哪个缺失的索引。
架构在修改的尺度上得到维护
架构常被看作预先设计、画成图、偶尔评审一下的东西。但在一个活的系统里,架构是通过日常修改得到维护或遭到腐蚀的。
每次变更都会把一个决策放到某处;每次变更要么保持、要么削弱某条放置规则;每次变更要么让未来的上下文获取更便宜,要么更昂贵。
这就是为什么不能只凭静态结构来评判架构。一个系统可能看起来架构整洁,但如果它的路由规则不被信任,修改起来照样昂贵。另一个系统也许看起来没那么正规,却因为决策放置可预测、所有权清晰、修改闭包易于获取,而始终高度可修改。
CMP 为架构给出了一个具体的标准:
当架构的路由拓扑与系统现实的修改模式相匹配,并且它的放置规则始终足够可信、能让充分上下文更容易获取时,这个架构就是好的。
这个标准同时解释了各种架构风格的价值与代价。
当策略与机制的变化需要各自的路径时,整洁架构和六边形架构有效。当语义变化局限于不同领域时,DDD 的限界上下文架构有效。当特性级变化占主导时,垂直切片架构有效。当所有权边界主导修改成本时,模块化单体和微服务有效。当变化反复沿着稳定的扩展点到来时,插件架构有效。当组合是变化的真实形状时,ECS 有效。
每一种风格都是对未来修改的一个预测。
预测正确时,架构为上下文提供路由。
预测错误时,架构就成了一笔糟糕的投资。
架构不是系统的示意图。架构是上下文路由。