面向 AI 的架构原则(开放式探讨)
核心问题
结合前五章的案例和分析,能否提炼出一些通用的架构原则,指导 AI 时代的代码设计?
以下五个原则不是"标准答案",而是一个讨论的起点。

原则一:层次精简优于多级叠加
问题
传统的三层架构(Controller → Service → Mapper)本身是合理的,但实践中容易在"标准三层"之上不断叠加新层——ApplicationService、Command、RequestConverter 等。
以 Growth AIOS 的重构经历为例(参考第 04 章),一个请求链路涉及:
Controller → RequestConverter → ApplicationService → Command → DomainService → RepositoryApplicationService 几乎没有真正的业务逻辑——只是在调用 DomainService 之前做参数转换、之后做 VO 转换。Command 和 Request 字段几乎一样。RequestConverter 只做字段级映射。
对 AI 来说,这些中间层带来的成本远大于价值: 每多一层就多一次文件跳转,改一个字段需要同步修改 4-6 处,而 AI 的大部分 token 消耗在了无意义的转发逻辑上。
原则
标准三层不要动,多余的中间层要砍掉。
Controller → Service → Mapper 跨三个工程、每层职责清晰、可独立测试,这是正确的架构设计。这套模式在 C# 项目中被验证为最佳实践——Components 工作区中所有组件服务都遵从这个结构。同样适用于 Java 项目,wukong 就是按这个模式组织的。
需要精简的是那些"没有实际业务价值、纯转发"的中间层。
推荐(三层三工程,每层独立测试):
wukong-web/
BillController.java ← Web 层(接收请求、校验、返回)
wukong-business/
BillService.java ← 业务层(业务编排、事务管理)
wukong-domain/
BillMapper.java ← 数据层(数据访问)
不推荐(叠加无价值中间层):
Controller → RequestConverter → ApplicationService → Command → DomainService适用边界
跨工程的接口契约仍然重要。当中间层有真实的业务逻辑(如跨多个 DomainService 的事务协调、复杂的参数构造逻辑)时,保留是合理的。但纯转发的中间层应该果断去掉。
判断标准:如果一层只做"转发"而不做"转换",那它大概率是多余的。
原则二:聚合对象优于碎片分散
问题
传统 Java 项目中,一个业务实体对应多个碎片化对象——VO、DTO、BO、PO、DO、Form、Query、Command(参考第 02 章)。
一个简单的 CRUD 涉及的对象体系:
SysUserEntity.java ← Entity(ORM 映射)
SysUserVO.java ← VO(视图展示)
SysUserDTO.java ← DTO(数据传输)
SysUserBO.java ← BO(业务对象)
CreateUserCommand.java ← Command(写操作)
UserListQuery.java ← Query(查询条件)
UserVOConverter.java ← 转换器"分散"不是指三层三个工程分散,而是指对象体系碎片化。 对 AI 来说,这意味着:
- AI 不知道该用什么——每次生成时,它需要猜测入参用 Form 还是 Command 还是 Request
- 转换逻辑是 Bug 高发区——VO↔DTO↔BO↔Entity 的每一层转换,AI 都要精确匹配字段
- Token 的严重浪费——大部分上下文被中间对象的 import/注解/getter-setter 占用
原则
三层三工程的架构是正确的,不需要合并。需要精简的是同一层内的碎片化对象体系。
核心思路:Entity + Request + VO 三种对象覆盖 90% 场景,中间传递统一用 Entity 或 Tuple。
推荐(三层三工程,每层只保留必要对象):
wukong-web/
CreateUserRequest.java ← Request(入参,继承 BaseRequest)
UserVO.java ← VO(出参)
wukong-business/
BUserService.java ← Service(方法参数扁平,无多余包装)
SysUserEntity.java ← Entity(业务层和 Mapper 共用)
wukong-domain/
SysUserMapper.java ← Mapper
不推荐(碎片化对象体系):
web/BillVO.java
web/BillForm.java
business/BillDTO.java
business/BillBO.java
domain/BillPO.java
domain/BillDO.java
domain/BillQuery.java
business/BillService.java
business/BillServiceImpl.java适用边界
外部 RPC 接口仍然需要独立的 DTO 定义。组件化服务的接口需要标准入参出参(参考第 05 章)。跨表组合查询可以用泛型 Tuple 代替碎片化 DTO——不需要为每个组合场景创建新类。
核心思想:给 AI 一套统一的规则,它就懂得怎么干活。不给出规则,它就随机发挥。
原则三:自包含优于继承
问题
传统架构喜欢建基类——BaseController、AbstractService、BaseEntity。AI 在理解代码时,需要跟踪多层父类才能理解完整行为。
csharp
// 不推荐:AI 需要看 BaseController → BaseApiController → 当前 Controller
public class BillController : BaseController
{
// save 方法在哪里定义的?在父类还是子类?
}原则
AI 在理解代码时,自包含的文件比依赖多条继承链的文件容易得多。
不推荐:BaseController → AbstractService → 多级基类
推荐: 一个文件包含完整逻辑,通过显式调用而非隐式继承适用边界
复用代码应该通过组合而非继承实现。如果跨模块需要共享功能,用静态方法或单独的服务类,而不是通过基类注入。
原则四:数据透明优于封装隐藏
问题
领域驱动设计(DDD)是过去十年企业软件最流行的架构思想。它的核心是"封装数据库细节"——Repository 隐藏 SQL、领域对象隐藏表结构、聚合根隐藏关联关系。但对 AI 来说,这些封装恰恰是理解代码的障碍。
// 领域驱动设计——AI 需要理解多个抽象层才能追踪数据流向
Service → Repository → Domain Entity → 聚合根 → Value Object → ORM 映射 → 数据库
// MyBatis——AI 直接看到数据流动路径
Service → Mapper → SQL → 数据库程序的本质是"数据 + 算法",LLM 最擅长理解的就是数据的流动路径。 每多一层封装,AI 就需要做一次"逆向推导"才能搞清楚数据从哪里来、到哪里去。
原则
直接暴露表结构和查询操作,比层层封装后让 AI 逆向推导要高效得多。
MyBatis 是这种原则的最佳实践——基于原生 SQL,可预测、可测试、可编写、AI 可优化。虽然代码量比 Hibernate/JPA 大,但对 AI 来说,代码量大不是问题,隐式规则多才是问题。
推荐(数据透明,AI 一目了然):
// Service 中直接调用 Mapper
SysUserEntity user = sysUserMapper.selectById(userId);
List<SysOrderEntity> orders = sysOrderMapper.selectList(query);
// SQL 是透明的,AI 直接看到筛选和返回的字段
// SELECT * FROM sys_order WHERE user_id = #{userId}
不推荐(封装隐藏,AI 需要逆向推导):
// Repository 隐藏了 SQL
UserDomain user = userRepository.findById(userId);
// Domain 对象隐藏了表结构
// AI 需要追踪:findById → JPA 自动生成 → 缓存 → 懒加载 → 级联
// 隐式行为太多,AI 无法"看到"完整的数据流适用边界
不是所有封装都是有害的。当代码作为公共库暴露给外部系统,或者团队规模较大需要严格语义契约时,封装仍然是必要的。但内部业务代码中,直接暴露数据源和操作,对 AI 更友好。
判断标准:如果封装是为了"隐藏细节"而不是"复用逻辑",那对 AI 来说就是多余的。
原则五:可执行优于可声明
问题
Java 的注解机制是声明式编程的典型代表——@Transactional、@Cacheable、@Async、@Retryable……一行注解就能开启事务、启用缓存、切换异步。但对这些注解背后的行为,AI 是"看不见"的。
java
// 声明式——AI 能看到的是什么
@Transactional
public void settleBill(Long billId) {
Bill bill = billMapper.selectById(billId);
bill.setStatus(Status.SETTLED);
billMapper.updateById(bill);
}AI 看到的只是三行代码。它"看不到"的是:
- 这个方法什么时候开启事务?
- 什么时候提交?
- 什么异常触发回滚?
- 事务传播行为是什么?
- 当前线程是否已有事务?
这些行为完全由 Spring 的 AOP 代理在运行时决定,对 AI 来说是一个黑盒。 AI 无法通过代码追踪来理解事务边界——它只能"猜"注解的语义。
这和前面章节讨论的问题一样:AI 的理解方式是字段溯源,不是语义解析。 一个无法通过代码路径追踪的行为,对 AI 就是不透明的。
原则
能用编程式代码明确表达的行为,就不要用声明式注解隐藏起来。
以事务处理为例,使用 TransactionTemplate 替代 @Transactional:
java
// 可执行式——AI 能看到完整的事务边界
@Autowired
private TransactionTemplate txTemplate;
public void settleBill(Long billId) {
txTemplate.execute(status -> {
// ① 锁定——悲观锁,防止并发
Bill bill = billMapper.selectByIdForUpdate(billId);
// ② 判断——业务校验
if (bill.getStatus() != Status.PENDING) {
throw new BusinessException("当前状态不允许结算");
}
// ③ 更新——失败时自动回滚
bill.setStatus(Status.SETTLED);
billMapper.updateById(bill);
return null;
});
}AI 现在可以追踪完整的数据流:
txTemplate.execute()明确标记了事务边界selectByIdForUpdate显式声明了锁操作throw BusinessException是 AI 知道的回滚触发条件- 从锁定 → 判断 → 更新的路径,每一步都可见
注解 vs 编程式的对比
| 维度 | @Transactional(声明式) | TransactionTemplate(编程式) |
|---|---|---|
| 事务边界 | AOP 隐式代理,AI 不可见 | 代码显式包裹,AI 可见 |
| 回滚条件 | 默认 RuntimeException,隐含 | 异常抛出路径,代码可见 |
| 锁操作 | 需要额外注解或 SQL | selectByIdForUpdate 显式声明 |
| 行为验证 | 必须运行测试 | 代码审查即可确认 |
| AI 理解成本 | 高(需推断 AOP 行为) | 低(代码即行为) |
标准化事务模板 Skill
锁定 → 判断 → 更新,失败抛异常自动回滚——这个模式可以标准化为一个 Skill,既能检查现有代码是否符合规范,也能生成合规的代码。
// Skill 示例:audit-transaction
// 检查规则:
// 1. 所有写操作必须包裹在 TransactionTemplate 中
// 2. 禁止使用 @Transactional 注解(除测试代码)
// 3. 写操作必须包含:锁 → 判断 → 更新 三步
// 生成规则:
// 1. 注入 TransactionTemplate
// 2. 包裹 execute 回调
// 3. 显式声明锁(selectByIdForUpdate)
// 4. 校验通过后执行更新适用边界
声明式注解并非全无价值。在以下场景可以保留:
- 框架/公共库设计:注解作为 SPI 扩展点(如
@EventListener) - 纯查询缓存:
@Cacheable不影响数据一致性 - 测试 Mock:测试代码中的注解不影响生产逻辑
但所有影响数据一致性的行为(事务、锁、并发控制)都应该用编程式代码明确表达,而不是靠注解隐式完成。
判断标准:如果一行注解隐藏了不可见的副作用,那它对 AI 就是不友好的。
原则六:CLI 可测优于 IDE 依赖
问题
传统的测试框架把"在 IDE 中运行"作为默认方式——测试类用 [Test] 注解标记、通过 IDE 的测试运行器点击执行、结果在 IDE 面板中展示。但对 AI 来说,IDE 的测试运行器是不可操作的——AI 无法点击"Run"按钮,也无法读取测试结果面板。
更隐蔽的问题是:传统测试框架把测试和 IDE 绑定在一起——启动配置、调试端口、环境变量都藏在 IDE 的配置文件中,脱离 IDE 就无法运行。
原则
测试框架首先应该是一个 CLI 应用,其次才是一个测试框架。
Components.Test 的设计充分体现了这一原则。它的入口是一个标准的 CLI 程序,所有行为通过命令行参数控制(参考第 07 章):
bash
# 运行所有测试
Components.Test
# 运行指定模块测试
Components.Test -n # 节点测试
Components.Test -w # 工作流测试
Components.Test -l # LLM 测试
Components.Test -c # CLI 测试
# 运行指定场景
Components.Test -n TextProcessor
Components.Test -w SimpleWorkflow001每个测试用例是一个数据文件(input.json + output.json),而不是一个需要编译运行的测试类:
json
// input.json —— 测试输入(AI 可以生成)
{
"testType": "chat",
"message": "你好,请简单介绍一下你自己"
}
// output.json —— 测试输出(AI 可以验证)
{
"Success": true,
"Content": "你好!我是...",
"DurationMs": 7382
}这意味着:
- AI 可以直接读取测试用例——input/output 是 JSON,AI 天然理解
- AI 可以生成测试用例——根据接口定义生成对应的 input.json
- AI 可以执行测试——通过 CLI 命令运行,不需要 IDE
- AI 可以验证结果——output.json 包含完整的执行指标
架构特点
Components.Test 采用模块化 CLI 架构:
Components.Test/ ← CLI 入口(Program.cs)
├── Modules/ ← 按模块组织的测试逻辑
│ ├── WorkflowTestModule
│ ├── LLMTestModule
│ └── CLITestModule
├── TestCases/ ← JSON 数据驱动的测试用例
│ ├── AiAgent/BasicChat/
│ │ ├── input.json ← 输入
│ │ ├── output.json ← 输出(自动生成)
│ │ └── execute_log.txt ← 执行日志(自动生成)
├── Mock/ ← Mock 服务
├── Configuration/
│ └── CommandLineParser ← CLI 参数解析
└── Documents/ ← 测试文档即规范没有 IDE 依赖——不依赖 Visual Studio 的 Test Explorer、不依赖 IntelliJ 的测试运行器、不依赖任何 GUI 工具。测试即命令行,AI 可以直接操作。
适用边界
对于需要复杂 UI 交互验证的场景(如 Playwright 的 E2E 测试),浏览器仍然是必要的。但所有非 UI 的测试——Service 层测试、集成测试、组件测试——都应该设计为 CLI 驱动的形式,让 AI 可以直接操作。
判断标准:如果测试不能通过一条 CLI 命令执行,那它就还没有被设计好。
原则七:模块标准化优于自由组织
问题
没有标准化之前,每个开发者都有自己的模块组织偏好:
// 开发者 A 的风格
UserModule/
├── services/
│ ├── UserService.java
│ └── UserServiceImpl.java
├── models/
│ └── User.java
└── utils/
└── UserHelper.java
// 开发者 B 的风格
user/
├── UserController.java
├── UserService.java
├── UserDao.java
└── User.javaAI 在理解不同模块时需要反复适应不同的目录结构、命名风格、访问修饰符。自由组织对人是"灵活",对 AI 是"噪音"。
原则
模块应该有统一的标准结构,而且这个标准应该由 AI 来执行。
Components 项目中的 38 个模块全部遵循同一套模板,由 module-architecture-check.md 规则保证一致性:
标准模块结构:
XxxModule.cs ← 模块入口,public static,Register/Start/Stop
Interfaces/ ← 接口层,public
I{Name}Service.cs
Definition/ ← 入参出参定义,public
{Name}Request.cs
{Name}Result.cs
Implementations/ ← 实现层,internal
{Name}ServiceImpl.cs
LogFormatHelper.cs ← 日志,internal
LoggerConfig.cs ← 日志配置,internal38 个模块全部遵从这个结构:
Components/
├── AiAgent/ ← AiAgentModule.cs + Interfaces/ + Implementations/
├── CLI/ ← CLIModule.cs + Interfaces/ + Implementations/
├── LLM/ ← LLMModule.cs + Interfaces/ + Implementations/
├── MCP/ ← McpModule.cs + Interfaces/ + Implementations/
├── Plugin/ ← PluginModule.cs + Interfaces/ + Implementations/
├── Trigger/ ← TriggerModule.cs + Interfaces/ + Implementations/
├── VarVault/ ← VarVaultModule.cs + Interfaces/ + Implementations/
├── Video/ ← VideoModule.cs + Interfaces/ + Implementations/
├── Workflow.Node/ ← WorkflowNodeModule.cs + Interfaces/ + Implementations/
└── ... ← 全部 38 个模块同一套标准AI 是标准化的关键
这套标准化在 AI 时代之前很难推行——因为开发者会觉得"约束太多"、"写起来麻烦"。但 AI 的介入彻底改变了这一点:
- AI 生成模块:给定模块名,AI 自动生成符合标准的所有文件(Module、Interface、Impl、Logger),且永远一致
- AI 检查合规:
module-architecture-check.md作为 Rule 被 AI 自动遵循环,人不需要再审查 - AI 适应成本为零:AI 看到任意一个新模块,都能通过目录结构瞬间确定它的功能边界
yaml
# module-architecture-check.md —— AI 自动执行
# 三标准类
- [ ] {ModuleName}Module.cs 存在
- [ ] LogFormatHelper.cs 存在
- [ ] LoggerConfig.cs 存在
# 目录结构
- [ ] Interfaces/ 目录存在
- [ ] Interfaces/Definition/ 目录存在
- [ ] Implementations/ 目录存在
# 访问修饰符
- [ ] Interfaces/ 下为 public
- [ ] Implementations/ 下为 internal跨生态对比
不同语言生态的模块标准化能力差异很大:
| 生态 | 模块管理方式 | AI 友好度 |
|---|---|---|
| C# | .sln + 项目引用,原生支持多项目模块化 | ✅ 简单直接 |
| Java/Maven | 多 Module POM 继承,配置复杂、缺少统一约定 | ❌ 较弱 |
| Node/Rush | Rush 管理多包仓库,但需要外部工具链支持 | ⚠️ 可用 |
C# 的解决方案最简单——一个 .sln 包含多个项目,项目之间通过项目引用通信,每个项目就是标准化的模块。
可插拔性
标准化模块的另一个价值是可插拔:
csharp
// Program.cs —— 按需插拔模块
if (config["MemoryProvider"] == "OpenViking")
services.RegisterOpenViking(config); // 插上高级记忆模块
else
services.RegisterAgentMemory(config); // 插上基础记忆模块每个模块只有一个入口——XxxModule.Register(),外部不需要知道模块内部的任何细节。新增模块就是新增一个目录 + 一个入口注册方法,不影响任何现有代码。
适用边界
标准化框架不能代替领域设计。对于纯业务逻辑(如一个 CRUD 的 Service),仍然需要按业务场景组织代码。
判断标准:如果同一个项目中不同模块的目录结构、命名风格、访问修饰符不一致,那标准化就还没有到位。
知识篇:七个原则的优先级和适用场景
优先级
在开始一个新模块时,建议按以下优先级应用这些原则:
- 层次精简(最重要的)——决定了层次结构的合理性
- 聚合对象——决定了对象体系的简洁性
- 自包含优于继承——决定了复用方式
- 数据透明优于封装隐藏——决定了数据访问方式
- 可执行优于可声明——决定了验证方式
- CLI 可测优于 IDE 依赖——决定了测试方式
- 模块标准化优于自由组织——决定了模块组织方式
不适用的场景
这些原则主要适用于 业务逻辑层 和 模块内部。在以下场景需要谨慎:
- 公共库/框架设计:API 的接口契约仍然需要
- 跨团队协作:模块边界的接口声明仍然需要
- 安全敏感模块:额外的间接层可能是安全需求
这不是"放弃架构"
有人在读到这里时可能会想:"你是不是在说不要分层、不要架构了?"
不是的。
这些原则不是抛弃传统架构,而是重新审视传统架构中的哪些部分是为"人"设计的,哪些是对 AI 也友好的。
- 模块边界要保留——AI 需要知道"这件事去哪找"
- 接口契约要保留——AI 需要知道"输入输出是什么"
- 命名规范要保留——AI 需要从名字推断意图
- 但碎片化的 VO/BO/DTO 可以重新考虑——AI 不需要这些中间层来帮助理解
本章小结
| 原则 | 核心思想 | 解决的问题 | 适用边界 |
|---|---|---|---|
| 层次精简 | 砍掉无价值的中间层 | AI 中间层需要多次跳转 | 三层内 |
| 聚合对象 | 减少碎片化对象类型 | AI 猜测对象类型成本高 | 同层内 |
| 自包含优于继承 | 完整逻辑在一个文件 | AI 追踪父类成本高 | 业务逻辑 |
| 数据透明优于封装隐藏 | 直接暴露表结构和 SQL | AI 需要逆向推导封装的数据 | 内部业务 |
| 可执行优于可声明 | 编程式代码代替声明式注解 | AI 看不见 AOP 注解的隐式行为 | 影响数据一致性的逻辑 |
| CLI 可测优于 IDE 依赖 | 测试框架本身是 CLI 应用 | AI 无法操作 IDE 测试运行器 | 非 UI 测试 |
| 模块标准化优于自由组织 | 统一模块结构由 AI 执行 | AI 需适应不同开发者的组织习惯 | 组件/框架层 |
关联:下一章从反面视角切入,分析中小企业最常用的 Java + SpringBoot + Vue 技术栈在 AI 时代面临的问题。