深入复杂系统的五个层级

当你第一次打开一个新项目,或者几个月后回到自己写的代码时,如何理清一个系统的结构?

这两种情况下,开发者都必须构建同一张内部地图:弄清责任在哪里划分、存在哪些能力,以及它们如何连接到具体实现。经验能帮你更快找到主要的锚点,但当这些锚点没有在架构本身中被表达出来——或者在项目与项目、模块与模块之间不断变化时——每一次都得重新摸索入口。

AOA 把这条入口从资深开发者的头脑中移出,放进系统本身的结构里。从一个项目到另一个项目,它始终保持同样的五根支柱:Domain、Action、Contract、Pipeline 和 Code。每一根支柱都成为一个独立的深入层级,它们共同铺出一条从整体图景通向具体行为的顺序路径。

开发者穿过这些层级,就像在地图上一步步放大:先从整体上理解系统,再逐步添加细节。每一层都对自己的问题给出完整的回答,而无需预先知道更深处有什么。

文档 ↗

当所有层级的上下文混在一起时

当所有细节同时摆在眼前时,是什么妨碍你看清整体图景?

普通的代码仓库不会按尺度区分信息:业务领域、操作、数据模型、调用顺序、基础设施以及一行行具体代码,全都同时呈现出来。工作记忆必须同时容纳来自不同层级的事实,并自行重建它们之间的关系。注意力不断在系统结构与局部细节之间来回切换,因此中断之后,理解很快就会瓦解。

缺少全局图景会如何影响工作

下面,同一组信息以两种状态呈现:左侧是混在一起的样子,右侧则被整理成五个依次递进的问题。让我们从第一个问题开始:系统中的责任究竟在哪里?

扁平代码库 · 新事实缺少依托
orders.pyBillingServiceapi.pycreate_orderreserve_stockOrderModelsend_mailutils.pydb.py
五个层级 · 上下文依次建立
01Domain责任所在
02Action系统可以做什么
03Contract操作要求和承诺
04Pipeline场景如何推进
05Code具体行为存在于何处
完整文档 ↗

先建立责任地图

责任在哪里?

第一根支柱是一张责任地图。它把控制器、服务、数据模型、基础设施适配器和调用链,围绕系统的主要区域聚拢起来——形成一个骨架,操作、数据和代码之后都可以附着其上。

领域不是按技术层,而是按稳定的责任范围划分系统:订单、支付、通信和分析。数百个看似同等重要的文件,由此变成第一张可理解的系统拓扑。

领域地图带来了什么

图中的四个领域构成应用的最上层。接下来我们会依次展开 StoreDomain:先看它的 Actions,再看一个操作的契约、场景和具体实现。

4 × ActionStoreDomain

拥有购物车、订购和交付

3 × ActionBillingDomain

拥有付款、发票和退款

3 × ActionMessagingDomain

拥有客户沟通和网络钩子

3 × ActionAnalyticsDomain

拥有活动、集市和业务报告

完整文档 ↗

地图进一步变成能力目录

系统能做什么?

领域地图说明责任在哪里,却还没有说明系统能做什么。为此,每个领域都会展开为 Action 目录,也就是它向系统其他部分提供的具名能力。

Action 是领域的一项具名公开能力。目录展示的不是内部函数,而是一个领域向系统其他部分提供的操作,以及它们之间的有向关系。每个 Action 都成为一个单一的调用点:调用方按含义挑选它所需要的能力,而不是用一个个单独的方法拼装出这个操作。

Action 目录带来了什么

图中突出显示了启动订单创建的 CreateOrderAction,旁边是 StoreDomain 的其他 Actions 及其关系。一个操作可以依赖另一个操作,但依赖始终有方向,并且不会闭合成环。在这一层只展示 Action 的名称和关系;输入、结果、场景和代码会在后面逐层展开。

StoreDomain命名可执行能力
CreateOrderAction

创建订单:验证、预订和收费

→ ChargePaymentAction

GetOrderAction

读取订单的当前状态

CancelOrderAction

取消订单并发起退款

→ RefundPaymentAction

ShipOrderAction

将确认的订单交给交货

完整文档 ↗

固定操作的边界

操作需要什么,又承诺什么?

这项能力已经有了名称和系统中的位置。要把它当作黑盒使用,还需要准确知道它接收哪些数据,以及必须返回什么结果。

Contract 让 Action 的边界具有明确类型且没有歧义。Params 描述操作接收的全部内容,Result 描述它必须返回的全部内容。调用代码依赖这项承诺,而不是 Action 的内部结构。只要输入、输出和可观察行为保持兼容,实现就可以被替换。

Action 契约带来了什么

熟悉的 CreateOrderAction 现在拥有完整签名:左侧是 CreateOrderParams,右侧是 CreateOrderResult。字段不再是孤立的数据模型,而是属于一个位置和目的都明确的操作。使用这个 Action 仍然不需要打开它的内部实现。

CreateOrderParams
customer_id: str
items: list[Item]
currency: str
CreateOrderAction

在一个明确边界内编排订单创建

Params → Result
CreateOrderResult
order_id: str
payment_id: str
status: OrderStatus
完整文档 ↗

把操作展开为场景

场景如何推进?

契约固定了操作的起点和终点,却没有展示两者之间如何转化。为此,我们继续展开业务场景的内部流程。

Pipeline 让主要业务场景变得线性且可见:校验输入、预留库存、扣款、组装结果。这并不意味着错误、分支和补偿不存在;它们会相对于主线获得明确位置。每个步骤也有自己的局部契约:声明从 Params、State 和 Context 中读取什么,以及为后续步骤向 State 中加入什么。

操作场景带来了什么

领域为场景确定位置,Action 为它命名,Contract 固定起点和承诺的结果。Pipeline 进一步加入因果顺序和内部边界。reserve_inventory 被隔离为一次独立的状态变化:它接收已经校验的数据,并留下 reservation_id 供场景继续使用。

regularvalidatevalidated_items
regularreserve_inventoryreservation_id
regularcharge_paymentpayment_id
summarycreate_resultOrderResult
完整文档 ↗

最后才进入具体行为

具体行为在哪里?

Pipeline 精确指出所需行为的位置,并大幅缩小搜索范围。现在不必打开整个代码仓库,甚至不必打开整个 Action,只需查看一个目标、输入和输出都已经明确的步骤。

Code 成为第五层,并不是因为细节不重要,而是因为这些细节现在已经被意义包围。我们已经知道行为属于哪个领域、Action 实现什么能力、Contract 承诺什么,以及 reserve_inventory 在 Pipeline 中处于什么位置。实现代码因此成为对具体任务的局部回答,而不是一次无休止代码仓库调查的入口。

将行为隔离在单个步骤中带来了什么

打开 reserve_inventory 时,我们已经知道它的意图、输入以及在场景中的位置。原则仍然相同:外部效果应通过已声明的 Resources 和公开 Actions 发生。架构语法让偏离规则的做法可见、可验证,但并不假装仅凭说明文字就能在物理上阻止所有绕行。

store/actions/create_order.py
@regular_aspect("Reserve inventory")@result_string("reservation_id", required=True)@context_requires("user.tenant_id", "env.inventory_region")async def reserve_inventory_aspect(    self, params, state, box, connections, ctx):    inventory = box.resolve(InventoryResource)    reservation_id = await inventory.reserve(        tenant_id=ctx.get("user.tenant_id"),        region=ctx.get("env.inventory_region"),        items=state["validated_items"],    )    return {"reservation_id": reservation_id}
$ uv run python store/actions/create_order.py
输出
完整文档 ↗

五个层级不会隐藏代码,也不会把系统简化成一张图。它们保留了从整体模型走向具体细节的路径。新人逐步建立上下文;架构师管理系统语法和边界,而不是逐个管理文件;AI 智能体则在声明的规则内完成局部工作:人设计系统的语言,机器用这种语言行动。

深入复杂系统的五个层级 · aoa.run