Synapse:用 AI 辅助微服务项目开发的一些经验
Synapse:用 AI 辅助微服务项目开发的一些经验
Synapse,一个多租户 SaaS 平台。三个月开发,各服务流程已全打通。记录 AI 写代码过程中遇到的问题和处理方式。
三个月前,我开始在 Synapse 项目里大规模用 AI 写代码。网关路由分发、用户权限管理、商品订单、计费账单、工作流引擎、认证服务、实时监控面板……各服务流程已全打通,教训也攒够了。
一、依赖管理:AI 喜欢到处 new,但有些对象不该 new
这不是风格问题,是资源浪费和状态不一致的问题。
单例对象不该到处 new
项目里有一批对象必须是单例:DB 连接池、Redis 客户端、etcd 客户端、gRPC 连接池、日志实例。它们用 sync.Once 做实例管理:
var (
db *gorm.DB
once sync.Once
)
func InitDB(cfg configs.DatabaseConfig) {
once.Do(func() {
db = gorm.Open(...)
})
}
func DB() *gorm.DB {
if db == nil {
panic("db not initialized")
}
return db
}
Repository 通过 DB() 拿到同一个连接池实例,Service 通过 Wire 注入 Repository——整个链路共享同一个连接池。
但 AI 写新模块时,经常自己 new 一套:
// AI 写的——每个 Service 自己建 DB 连接
func NewSomeService() *SomeService {
dsn := "postgres://user:pass@localhost/db"
db, _ := gorm.Open(postgres.Open(dsn), ...)
return &SomeService{db: db}
}
看起来能跑,但问题是:每个 Service 持有一个独立的 DB 连接池。十个 Service 就是十个连接池,数据库连接数迅速打满。Redis、etcd 同理。
更隐蔽的是状态不一致。比如权限缓存——网关和 ums 各自 new 了一个 Redis 客户端,各自维护各自的缓存。网关删了权限缓存,ums 那边还是旧数据。
AI 为什么会这样?因为它没有"实例管理"的概念。对 AI 来说,new 是最直接的创建方式,它不会主动去想"这个对象是不是应该全局只有一个"。
处理方式:在约定文档里明确哪些对象是单例、怎么访问。让 AI 写代码时先看 pkg/ 目录下有哪些初始化好的实例,用 pg.DB() 而不是自己连数据库。给它正确的入口,它就不会自己造一个。
Wire 依赖注入,AI 也常出错
我们用 Wire 做编译期依赖注入。Wire 的用法本身不复杂,但 AI 在两个地方经常出错。
一是 Wire Set 的依赖顺序。Wire 要求 provider 函数的参数必须先被声明。AI 写出来的 wire.NewSet,经常把 NewMenuService 放在 NewMenuRepo 前面——因为 Service 比 Repo "重要",AI 习惯把重要的放前面。但 Wire 不认重要性,只认依赖顺序。结果 wire 命令直接报错,编译都跑不通。
二是 BuildApiContainer 的签名不一致。这个项目里,有的模块无参数,有的接收 Config。AI 生成新模块时,不知道应该用哪种。它通常选"看起来更合理"的(接收 Config),但和项目里大多数模块(无参数)不一致。
这两个问题有一个共同点:AI 写的 Wire 代码,不跑一次 wire 命令根本不知道能不能用。 它生成的代码语法正确、逻辑自洽,但就是不符合 Wire 的约束。
处理方式:Wire 相关的代码,让 AI 生成后必须跑一次 wire 命令验证。签名问题,在 AGENTS.md 里明确写了两种模式的区别,让 AI 生成前先读。AI 写 DI 代码,编译验证是必须的,不能跳过。
二、创建新微服务,AI 几乎一定会"偏离架构"
这是最常遇到的坑。
Handler 统一签名、DTO 统一格式、中间件链统一顺序、错误码统一范围——这些约定在已有的模块里没问题。新加一个微服务,让 AI 从零生成,它写出来的代码看起来没问题,但:
- Handler 签名改了——有的用
gin.Context直接处理,有的用context.Context + Request[T] - 错误处理改了——有的返回 HTTP 状态码区分,有的统一 200 走 JSON body
- 路由注册改了——有的实现
Route接口,有的在main.go里直接r.GET() - 中间件顺序改了——Auth 和 RateLimit 的顺序反了
每个差异单独看都"合理",但合在一起就是维护噩梦。
处理方式:不再让 AI "从零生成"。把已有模块的 main.go、router.go、一个完整的 API 实现作为模板喂给它,让它"按这个模式写新模块"。给例子,不给描述。
三、跨多个微服务改一个功能,AI 帮不上忙
多租户系统里,一个需求往往涉及多个服务。比如"新增一种权限类型":
- 认证服务要改 token 里的 claims 结构
- 网关要改权限校验中间件的逻辑
- 用户管理服务要改权限表的 DDL 和 DML
- 每个业务服务的前端要改菜单和按钮的权限判断
让 AI 一次性改四个服务?它做不到。上下文窗口放不下四个服务的代码,即使放得下,它也不理解改动之间的依赖关系。
更深层的问题是:大任务让 AI 自己拆,不如人来拆。 早期我试过把整个需求丢给 AI,让它自己规划、自己拆分、自己执行。结果它要么漏掉某个服务,要么拆分粒度不合理——把该一起改的拆散了,把该分开的绑在一起。一轮下来,返工的时间比直接人工拆分还长。
后来调整了策略:人负责拆任务,AI 负责执行子任务。 人清楚服务之间的依赖关系、知道改动的先后顺序、明白哪些可以并行哪些必须串行。把大需求拆成"改认证服务 claims"、"改网关中间件"、"改用户管理 DDL"、"改前端权限判断"四个子任务,每个子任务单独让 AI 执行。速度快、出错少、出了问题也好定位。
前端改动有个反直觉的发现:描述越具体,效果越差;给出代码块加抽象描述,效果反而更好。 比如让 AI"把这个表格加个筛选栏,支持按状态和日期筛选,样式要好看"——它生成的 UI 千篇一律,布局僵硬,配色平庸。但如果把现有组件的代码块直接贴给它,然后说"在这个表格上方加一个筛选区域,风格与页面保持一致"——它生成的 UI 反而更协调、更精致。
原因是:AI 看到具体代码后,能理解现有的设计系统、颜色变量、间距模式、组件结构。抽象的描述给了它发挥的空间,而代码上下文保证了它不会偏离项目风格。反过来,描述越具体("左边放两个下拉框,右边放一个日期选择器,间距 16px"),AI 越容易写成模板化的 UI,因为它在"执行指令"而不是"理解设计"。
处理方式:跨服务改动,人工做"改动清单",然后逐个服务让 AI 改。改完一个,跑一次 CI,确认没破坏别的。AI 适合单点修改,不适合全局协调。大任务的人机分工是——人拆任务,AI 执行子任务。前端改动时,贴代码块、给抽象描述,而不是写详细的需求说明。
四、让 AI 写测试?我们放弃了
这个项目里,零单元测试。
不是不想写,是踩过坑。让 AI 给数据访问层写单元测试,它生成的代码跑不过测试框架——对 GORM 的 mock 库 API 理解有偏差,mock 的行为和真实数据库不一致。
让 AI 给业务逻辑写测试,它要么只测 happy path,要么 mock 了所有依赖,测的只是 mock 本身。
处理方式:用静态分析代替单元测试。CI 流水线跑 go vet、staticcheck、tsc type-check、cargo clippy。这些工具比 AI 写的测试更能发现实际问题。
五、AI 是"复制粘贴"的加速器
前端有八个,每个都有 AuthContext、MenuContext、TenantContext、ThemeContext。代码几乎一样,散落在八个独立目录里。
为什么没抽成共享包?因为抽共享包需要设计 API 边界、版本管理、发布流程——这些 AI 做不了。而让 AI 在每个模块里"复制"一份,五分钟搞定。
方便是方便,但八个模块改八个地方的维护成本是真实的。AI 让复制变得毫无心理负担,但这不等于复制是好的工程决策。
处理方式:明确什么该复制,什么该共享。基础设施代码(DB 连接、Redis 客户端)用包级单例。业务代码(Context provider、页面组件)允许复制,但要接受"改就要改多处"的成本。
六、服务治理的改动,AI 理解不了依赖关系
我们的服务治理链路:
服务启动 → etcd 注册路由和版本 → 心跳保活
↓
网关从 etcd 发现服务 → 请求转发 → gRPC 代理 → 权限校验 → 限流
让 AI 写其中某一环,没问题。但让它理解整条链路的依赖关系,它做不到。
比如改了 etcd 的 key 路径格式,需要同时改注册端和发现端。AI 改完注册端,不会主动去改发现端,因为它不知道这两端是关联的。
处理方式:服务治理相关的改动,人工画依赖图,列出所有受影响的模块,逐个改。AI 只负责写每个模块内部的代码,不负责跨模块协调。
最后
AI 写代码不是什么革命。它就是一个效率极高的初级工程师——擅长按模板填空、快速复制、生成能跑起来的代码。但它不懂架构一致性、不做全局协调、不维护约定、不理解服务之间的依赖关系。
用好 AI 的关键:
- 把架构约定写下来——AGENTS.md 是给 AI 看的,不是给人看的
- 新模块用模板生成,不用描述生成——给代码例子,不给文字说明
- 跨服务改动,人工做清单,AI 逐个改——它不适合全局协调
- 依赖管理明确告诉 AI 哪些是单例——它默认倾向于到处 new
- AI 写的代码跑一次编译和 lint——Wire 命令、staticcheck 靠工具兜底
- 服务治理改动,先画依赖图——AI 理解不了链路,你能
做到这几点,AI 能帮你省下大量重复劳动。做不到,它会帮你制造大量"能跑但不一致"的技术债。
Synapse:多租户 SaaS 平台,Go + React + gRPC + etcd 服务发现 + Rust 监控,Monorepo 架构,RBAC 权限体系,自研网关做路由分发和限流。