ai主导下的项目架构演化研究
ai主导下的项目架构演化研究
在AI作为主要生产力的当下,现有的业务项目架构已经不适配与ai来协同进行工作,例如上下文限制,幻觉和偏移等问题,以前的DDD架构、六边形架构、洋葱圈架构都是基于人来进行的设计的,ai无法向人一样进行思考和联系,因此业务项目架构也要与时俱进进行迭代,来适配ai主导下的工作模式;
当下遇到的问题
-
LLM上下文限制
LLM的context窗口容量限制,ai最宝贵的资源就是token就是上下文窗口的大小,ai每读取一个文件就会把它放到上下文窗口中,虽然随着1M上下文甚至支持更大的上下文模型的出现,但是llm模型的基本原理决定了越小越精确的上下文推导的结果就是会好于大且杂的上下文; -
代码的检索
目前claude code使用的是grep+lsp的方案,cusor使用的是Graph RAG+代码库索引的技术手段,Claude Code的方案对于检索确实没有Graph RAG的方案高效,但是Graph RAG解决的是代码查找的问题,不能解决将加载代码的问题; -
修改半径爆炸的问题
由于采用分层架构的话,会导致为了局部功能修改了公共功能,导致依赖这个公共功能的其他功能不可用,并且ai很难评估影响; -
不适配ai编程工具
claude code的claude.md文件或者或者kilo 的Steering记忆模块设置,对于传统的架构,不太适配,无法在claude.md文件中描述全局视图,需要额外的文件来进行描述
一种业务侧的解决思路
先说一下传统的分层架构,是按照技术指责来进行分层,常见的分层如下:
com.example.app/
├── controller/
│ ├── OrderController.java
│ └── PaymentController.java
├── service/
│ ├── OrderService.java
│ └── PaymentService.java
├── dao/
│ ├── OrderRepository.java
│ └── PaymentRepository.java
└── model/
├── Order.java
└── Payment.java
可以在一个项目中按照package进行划分也可以按照模块进行划分,这样的调用链路是按照从前向后进行调用的,例如从 controller 到 service,再到 po 层,这样划分有这样几个问题:
-
业务关联性差
如果要理解一个业务需要在不同的层或者不同的模块之间进行搜索,这样的搜索会到导致不相关的类也加载到上下文中; -
修改风险扩散:修改共享的通用代码可能意外破坏所有使用方,ai无法分析到引用的地方会存在很大的风险
-
DRY 优先但违反 KISS:追求代码复用导致抽象过度,单段代码认知负担增大;
DRY:不要写重复的代码
KISS:要把“简单”作为首要目标
对于ai主导开发探索一种按照垂直分片的模块化架构来进行解决,垂直分片又被称为特征包的组织方式,其实就是按业务模块进行组织代码的一种方式;
这种划分的优点是,按业务特征分组,每个包或者模块就包含这个业务的所有技术层代码。
核心原则:“最小化切片间耦合,最大化切片内耦合。”
-
每个切片(请求/用例)独立决定如何实现,不强制统一架构模式
-
某些切片可以用 Transaction Script,某些用 Domain Model — 按需选择
-
共享抽象(repository、service、controller)会"消融" — 只在必要时保留
-
天然导向 CQRS:系统自然分为命令和查询
-
“分析某个业务,只需分析对应的包或者模块” — 这是一条判断结构是否合理的重要标准
-
抵制跨包复用,遵循 Rule of Three 再抽取到
common包 -
KISS 优先于 DRY — 引用 Sandi Metz:“宁肯重复,不要错误的抽象”
这种垂直分片或者特征包的模式也不是万能的,在当下也存在以下几个问题:
- 将一个大单体应用在逻辑上演变成多个微型单体应用
- “隐式调用”让Ai产生严重的信息死角,使用AOP或者反射等隐式逻辑连接会让ai工具的静态分析无法触及
- 垂直分片没有一个大局观,导致的问题是ai可以写好这个业务模块内的代码,但是无法从项目的整体全局来进行思考
解决思路有是有的:
第一:在进行业务划分时,不能太细,按照整块的业务经划分,可以避免“垂直分片没有大局观”和“扩张成无数个单体应用”的问题
第二:需要“硬性隔离或者硬性规则”限制住模块与模块之间的相互调用,这样可以笔面"调用无序"
第三:另外一种解决隐式调用的思路就是在工具层面,使用Graph RAG\使用LSP高级扩展增强等功能
基于这种解决思路的实现
使用Spring Modulith 来进行模块组织
com.example.platform.modules/
├── order/
│ ├── order-api/ # Provided Interface
│ │ └── src/main/java/com/example/order/api/
│ │ ├── facade/
│ │ └── event/
│ ├── order-domain/ # Internal
│ ├── order-app/ # Internal
│ └── order-infra/ # Internal
│
└── payment/
└── ...(同上)
整体架构设计
enterprise-platform/ # 仓库根目录
├── CLAUDE.md # 全局 AI 上下文(架构地图 + 构建命令 + 事件流)
├── .claude/
│ └── rules/
│ ├── java-conventions.md # Java 编码约定
│ ├── module-boundary.md # 模块边界规则
│ └── api-design.md # API 设计规范
│
├── build.gradle.kts # 根构建文件(子项目公共配置)
├── settings.gradle.kts # 子项目注册 + 插件管理
├── gradle/
│ └── libs.versions.toml # Version Catalog:统一全平台第三方依赖版本
│
├── platform-common/ # 共享内核(最小化,Rule of Three)
│ ├── CLAUDE.md
│ └── src/main/java/com/example/platform/common/
│ ├── exception/ # 通用异常体系
│ ├── event/ # 领域事件基类 + 事件载荷接口
│ ├── dto/ # 分页、通用响应包装
│ └── util/ # 纯工具(无业务逻辑)
│
├── platform-starter/ # 应用启动器(唯一 @SpringBootApplication)
│ └── src/main/java/com/example/platform/
│ ├── PlatformApplication.java
│ └── config/ # 全局配置类
│
├── platform-gateway/ # API 网关 / BFF 层
│ ├── CLAUDE.md
│ └── src/main/java/com/example/platform/gateway/
│ ├── controller/ # REST 控制器(按模块分包)
│ └── assembler/ # DTO 组装器
│
├── platform-archtest/ # 全局架构测试
│ └── src/test/java/com/example/platform/
│ └── ArchitectureTest.java # ArchUnit 规则
│
├── modules/ # ★ 所有业务模块的根目录
│ ├── order/ # 订单有界上下文
│ │ ├── CLAUDE.md
│ │ ├── order-api/ # API 契约模块
│ │ ├── order-domain/ # 领域模型模块
│ │ ├── order-app/ # 应用服务模块
│ │ └── order-infra/ # 基础设施模块
│ │
│ ├── payment/ # 支付有界上下文
│ │ └── ...(同上 4 模块结构)
│ │
│ └── inventory/ # 库存有界上下文
│ └── ...
│
├── starters/ # ★ 平台级 Spring Boot Starter
│ ├── starter-db/ # 数据库 Starter
│ ├── starter-mq/ # 消息队列 Starter
│ ├── starter-redis/ # Redis Starter
│ └── starter-security/ # 安全认证 Starter
│
└── docs/
└── architecture/
├── module-map.md # 模块关系图
├── event-flow.md # 事件流全景
└── decision-log.md # 架构决策记录(ADR)
对于模块内部还是的依赖与约束
┌─────────────┐
│ order-api │ ← 无依赖(纯接口 + DTO + 事件)
└──────┬──────┘
│
┌──────▼──────┐
│order-domain │ ← 仅依赖 order-api
└──────┬──────┘
│
┌──────▼──────┐
│ order-app │ ← 依赖 order-api + order-domain
└──────┬──────┘
│
┌──────▼──────┐
│ order-infra │ ← 依赖所有 + 其他模块的 -api
└─────────────┘
跨上下文只读查询的解决方案
当需要跨上下文查询数据时,尽量使用CQRS读模式
┌─────────────────────────────────────────────────────────────┐
│ CQRS 读模型 │
│ │
│ 写模型(命令侧) 读模型(查询侧) │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ order_schema │ 事件同步 │ order_read │ │
│ │ orders │ ──────────→ │ orders_v │ │
│ │ │ │ (含库存状态) │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ 优点:查询性能高,不跨 schema JOIN │
│ 缺点:数据最终一致,需要维护读模型 │
└─────────────────────────────────────────────────────────────┘
模块之间的通信规则
┌─────────────────────────────────────────────────────────────┐
│ 通信规则(严格递减) │
│ │
│ 同一有界上下文内 → 直接方法调用(最简单) │
│ 跨上下文查询 → 通过 Facade 接口同步调用 │
│ 跨上下文命令 → 通过领域事件异步通知 │
│ 跨服务边界 → 消息队列 + 事件外部化 │
│ │
│ 禁止: │
│ ❌ 直接依赖其他上下文的 -domain / -app / -infra │
│ ❌ 跨上下文共享数据库表 │
│ ❌ 跨上下文同步调用命令方法(用事件) │
└─────────────────────────────────────────────────────────────┘
测试策略
┌───────────┐
│ E2E │ ~10% → order-e2e/ (独立模块)
│ 30s-5min │ 启动完整 Spring 上下文
├───────────┤
│ 集成测试 │ ~20% → order-infra/src/test/
│ 1s-30s │ TestContainers(DB/MQ/Cache)
├───────────┤
│ 单元测试 │ ~70% → order-domain/src/test/ + order-app/src/test/
│ <1s │ 纯 Java,无 Spring,无 IO
└───────────┘
| 测试层级 | 所在模块 | 框架 | 单测耗时 | 期望占比 |
|---|---|---|---|---|
| 领域单元测试 | -domain/src/test/ |
JUnit 5 + AssertJ | < 100ms | 40% |
| 用例单元测试 | -app/src/test/ |
JUnit 5 + Mockito | < 200ms | 30% |
| API 契约测试 | -api/src/test/ |
JUnit 5 + JSON Assert | < 50ms | 5% |
| 持久化集成测试 | -infra/src/test/ |
TestContainers + JPA | 1-10s | 12% |
| 消息集成测试 | -infra/src/test/ |
TestContainers + Kafka | 1-10s | 5% |
| E2E 测试 | -e2e/src/test/ |
Spring Boot Test | 10s-2min | 5% |
核心原则:
- ✅ 测试代码与业务代码同模块,确保编译期就能发现测试引用的破坏性变更
- ✅
-domain的 stub 实现放在src/test/java,供-app测试复用 - ❌ 不创建独立的
order-test-common模块
Claude Code的自动加载策略
Claude Code 会根据当前工作目录自动加载对应的规则文件:
┌─────────────────────────────────────────────────────────────┐
│ 路径规则加载优先级 │
│ │
│ 1. 全局规则(~/.claude/rules/) │
│ └── 始终加载 │
│ │
│ 2. 项目根规则(.claude/rules/) │
│ └── 在项目内工作时加载 │
│ │
│ 3. 子目录规则(.claude/rules/modules/order/) │
│ └── 在对应子目录工作时加载 │
│ │
│ 加载顺序:全局 → 项目根 → 子目录(越具体优先级越高) │
└─────────────────────────────────────────────────────────────┘
总结
现在使用垂直分包来适配当下的模型是有用的,但是未来模型的能力变的更强大以后,这样也许就是负作用了,这只是展示了一种想法;