本文是 Agent 工程里的经典架构 系列的一篇。文中的业务场景和代码为讲解设计,产品能力以所链接的官方资料为准。

以前做管理后台,一个“批准订单”按钮会经过接口、业务服务和数据库。现在把同样的能力交给 Agent,有时却只写一个工具函数,允许模型直接改状态。入口变得更聪明,业务边界反而更薄了。这个变化值得停下来想一想。

企业应用架构模式目录整理了业务逻辑、数据访问、会话状态等常见组织方式。这里不准备逐个介绍所有模式,而是选择服务层、领域模型和数据映射这条关系,讨论一件具体的事:怎样让网页、定时任务和 Agent 遵守同一套订单规则。

我会先把批准订单定义为应用服务提供的操作。入口负责接收请求与确认身份;业务对象判断订单是否允许批准;存储组件负责保存。Agent 工具只是另一个调用入口。这样修改金额上限时,不必同时寻找网页接口、脚本和模型提示词里的三份规则。

机制图:把入口、业务规则和数据访问分开,让约束能被复用。

代码中的订单对象约束状态转换,应用服务协调读取和保存,仓库用内存字典模拟存储。复制对象是为了防止调用方绕过保存接口,直接改到字典里的实例。它说明职责分配,不代表已经实现了数据库事务、并发控制和完整权限体系。

from dataclasses import dataclass
from copy import deepcopy


@dataclass
class Order:
    amount: int
    status: str = "pending"

    def approve(self, ceiling):
        if self.status != "pending" or self.amount > ceiling:
            raise ValueError("订单状态或审批金额不满足条件")
        self.status = "approved"


class Repository:
    def __init__(self):
        self.rows = {"O1": Order(8000)}

    def get(self, key):
        return deepcopy(self.rows[key])

    def save(self, key, order):
        self.rows[key] = deepcopy(order)


def approve_order(repo, key, ceiling):
    order = repo.get(key)
    order.approve(ceiling)
    repo.save(key, order)


repo = Repository()
try:
    approve_order(repo, "O1", 7000)
except ValueError:
    pass
assert repo.get("O1").status == "pending"
approve_order(repo, "O1", 9000)
assert repo.get("O1").status == "approved"
print("规则由业务对象执行,拒绝后没有写入状态")

完整示例可下载,使用 Python 3.9 或以上版本运行:python3 example.py。只依赖标准库,不调用真实业务服务。

领域对象并不是一个只能放字段的容器。当它承担“什么情况下可以批准”的判断,状态转换就有了明确入口。与此同时,数据库连接、HTTP 请求和模型调用不必都塞进对象里面。哪些逻辑放在对象,哪些放在应用服务,要根据规则是否属于该实体来决定。

如果业务只是一个简单登记表,几段清楚的事务脚本可能已经足够。为了套模式而创建十几个接口、工厂和空实现,维护成本未必划算。只有当规则在多个用例中反复出现、对象之间有稳定约束时,更完整的领域模型才容易体现价值。架构选择应该回应复杂度,不能主动制造复杂度。

数据映射的意义也不是“必须写一个 Repository 类”。更实际的目标,是让业务规则不依赖表字段、ORM 会话和第三方 SDK 的细节。供应商字段名称变化时,影响可以留在适配层;业务层仍然处理有明确意义的报价对象。测试时也可以用小型替身验证规则,而不必启动完整环境。

生产场景必须补上示例省略的边界。读取到保存之间需要版本校验,身份来自可信认证上下文,金额上限来自审批记录而不是调用方随意传入。操作编号、审计记录和对外副作用还要与事务策略配合。这些能力可以由应用服务统一协调,但不能只靠类名表达已经存在。

与高并发架构的关系,在于拆分后的职责更容易独立治理。数据访问可以优化索引与批量读取,入口可以做限流,应用服务可以做任务编排,领域规则可以保持小而确定。性能优化时也更容易知道自己改变了哪一层,而不会顺手改变业务含义。

工具返回值也应该表达业务状态。相比统一返回一个字符串“失败”,可以区分参数错误、条件冲突、等待审批和结果未知。调用方才能选择修正参数、重新读取、继续等待或转人工。错误分类属于业务接口的一部分,不能完全依赖模型阅读异常堆栈。

测试这些层次时,我会先独立验证规则,再验证应用服务如何协调失败路径,最后检查真实存储适配器的事务行为。内存替身只能帮助检查职责组织,不能证明数据库并发写入也正确。

对于 Agent,我希望工具名称表达业务动作,例如“提交退款申请”,而不只是“执行 SQL”。前者可以附带明确的前置条件、权限与返回状态,后者往往把本应由系统承担的约束交给模型猜测。能够提供多少工具不是目标,让每个工具有清楚的业务承诺,才是长期维护时真正省力的地方。

上一篇 · 系列目录 · 下一篇