案例一:从零搭建微服务
从一个空白目录开始,使用 Harness Engineering(驾驭工程) 方法论搭建一个完整的用户管理微服务。这是全书的综合应用案例,展示 Command → Agent(智能体) → Skill(技能) → Team 全链路协作。
案例概述
本案例模拟了一个真实的后端微服务项目:用户管理服务(User Management Service),提供用户 CRUD 操作、数据库持久化、输入验证和 API 文档等标准功能。技术栈选用 Node.js 22 + TypeScript 5 + Express + Prisma + PostgreSQL,这代表了当前主流的后端技术组合。项目从空白目录开始,没有遗留代码,没有预设模板,完全依靠 OpenCode 的工程化能力完成搭建。读完本文,你将理解如何从项目初始化、环境配置到多 Agent 协作,完整走通一条微服务项目的全链路开发流程。
案例的执行过程被划分为五个阶段:项目初始化与知识注入(/init 生成 AGENTS.md 和骨架)、环境配置(opencode.json 完整配置)、Command + Agent + Skill 联动实现核心功能、Team 并行工作提升效率、以及最终的质量保障。这五个阶段对应了 Harness Engineering 的核心思想:每个阶段都有明确的工程目的,而非简单的“让 AI 写代码“。
最终交付的成果包括完整的项目源码、自动化测试套件、API 文档和一份量化的工作报告。本案例不仅是技术演示,更是一套可复用的“从需求到交付的工作流模板“——读者可以将其调整后应用到自己的项目中。
⏱ 时间有限?先读这些: 项目初始化 → 配置文件 → Agent+Skill 联动 → Team 并行工作 → 质量保障
内容要点
-
项目背景与需求 — 用户管理微服务的功能定义、技术选型依据和验收标准。展示如何用 Harness Engineering 的方式描述需求,使其既能被人类理解也能被 Agent 解析。
-
阶段一:项目初始化 — 使用
/init命令生成 AGENTS.md 项目和项目骨架结构。通过 Plan 模式分析需求并生成开发计划,通过/review进行计划审查。这是一个关键的“知识注入“环节。 -
阶段二:配置文件 — 完整的
opencode.json配置,涵盖 Plugin(插件) 和 MCP(模型上下文协议) 配置、权限和安全设置。这是定义工程环境的基础,决定了后续所有 Agent 行为的能力边界。 -
阶段三:Command + Agent + Skill 联动 — 创建自定义 Command,加载
backend-architectSkill,由 Agent 执行代码生成。这一阶段展示核心的“AI 编码引擎“如何工作,以及 Skill 如何注入领域知识。 -
阶段四:Team 并行工作 — 创建并行团队(Implementor + Reviewer + Tester),让多个 Agent 角色同时工作,再由 Oracle 汇总输出。这是从单 Agent 到多 Agent 协作的关键跃迁。
-
阶段五:质量保障 — 自动生成测试(加载
qa-engineerSkill),执行/review5 并行审查,修复审查发现的问题。用自动化方式兜底质量,而不是依赖人工检查。 -
最终交付与复盘 — 展示完整的项目结构、关键指标(文件数、测试覆盖率、开发耗时)、以及在每个阶段记录的 ADR(架构决策记录)。复盘哪些做得好,哪些可以改进。
全流程时序图
下面的时序图展示了从空白目录到最终交付的完整协作链路。每个箭头代表一次人机交互或 Agent 间的消息传递。
sequenceDiagram
participant Dev as 开发者
participant CLI as OpenCode CLI
participant Agent as Agent (gpt-4o)
participant Skill as backend-architect
participant Team as Team Mode
participant QA as qa-engineer
Dev->>CLI: /init user-service
CLI->>Agent: 初始化项目骨架
Agent->>Skill: 加载技能指令
Skill-->>Agent: 领域知识注入(架构模板 + 代码规范)
Agent-->>CLI: 生成 AGENTS.md + 项目结构
CLI-->>Dev: ✓ 初始化完成(package.json / tsconfig / prisma 骨架)
Dev->>CLI: ? 进入 Plan 模式
CLI->>Agent: 分析需求描述
Agent-->>CLI: 开发计划 + 模块分解 + 风险评估
CLI-->>Dev: Plan 报告(3 模块 / 4 风险项 / 工时估算)
Dev->>CLI: create-user-service user-api
CLI->>Agent: 执行自定义 Command
Agent->>Agent: 生成代码(Prisma Schema + Express Routes + Validation)
Agent-->>CLI: 代码写入 src/ 和 prisma/
CLI-->>Dev: ✓ 模块生成完成
Dev->>CLI: team run
CLI->>Team: 分发 3 路并行任务
par [Implementor] 写业务代码
Team->>Agent: 实现 User CRUD + 中间件
Agent-->>Team: 代码输出
and [Reviewer] 审查代码
Team->>Agent: 代码审查(安全 + 性能 + 风格)
Agent-->>Team: 审查报告
and [Tester] 写测试
Team->>QA: 加载 qa-engineer Skill
QA-->>Team: 测试代码(Unit + Integration)
end
Team-->>CLI: Oracle 汇总交付报告
CLI-->>Dev: ✓ 团队交付(代码 + 测试 + 审查记录)
Dev->>CLI: /review
CLI->>Agent: 5 路并行审查
Agent-->>CLI: 审查意见汇总
CLI-->>Dev: 修复建议(3 项阻塞 / 2 项建议)
Dev->>CLI: 最终输出
CLI-->>Dev: 项目文件 + 测试报告(92% 覆盖率)+ 指标看板
阶段一:项目初始化
执行 /init 命令
在空白目录下执行 /init 命令,这是 Harness Engineering 的“第一口空气“——它做的不是简单的文件生成,而是知识注入:把项目上下文、技术选型、团队习惯全部编码进 AGENTS.md。
$ mkdir user-service && cd user-service
$ git init && echo "node_modules/\n_dist/\n.env" > .gitignore
$ opencode /init user-management-service
> 🏗 正在初始化项目:user-management-service
> ✓ AGENTS.md 已创建
> ✓ 项目骨架已生成
> - src/index.ts
> - src/types/index.ts
> - prisma/schema.prisma
> - tsconfig.json
> - package.json
> ✓ Git 仓库已初始化
> ✓ 初始 Commit 已创建
>
> ? 是否进入 Plan 模式分析需求?(Y/n)
输出中关键信息:AGENTS.md 被自动生成、项目骨架包含完整的 TypeScript + Prisma 配置文件。/init 不是“脚手架生成器“——它不生成业务代码,而是建立工程上下文,让后续的 Agent 知道“这个项目是什么、用什么技术、遵守什么规范“。
生成 AGENTS.md
AGENTS.md 是整个项目的“宪法“。下面是 /init 自动生成的内容,它包含了项目描述、技术栈、代码规范和约束条件:
# user-management-service
## 项目描述
用户管理微服务,提供用户的 CRUD 操作、数据库持久化、输入验证和 API 文档。
## 技术栈
- Node.js 22 + TypeScript 5 (strict mode)
- Express (路由 + 中间件)
- Prisma (ORM + 数据库迁移)
- PostgreSQL (数据库)
- Vitest (测试)
## 代码规范
- 所有文件使用 2 空格缩进
- 类型定义统一放在 src/types/ 下
- Controller → Service → Repository 三层架构
- 所有公开 API 使用 JSDoc 注释
- 使用 Zod 进行输入验证
## 约束条件
- 不允许使用 `any` 类型
- 不允许使用 `require()`,统一用 ESM import
- 所有数据库查询必须通过 Prisma Client
- 密码使用 bcrypt 哈希存储
对于 入门开发者,AGENTS.md 解决了“我不知道怎么让 AI 理解我的项目“的问题。对于 技术负责人,它提供了可审计的工程约束——“谁改了 AGENTS.md,谁就要为项目规范负责”。
Plan 模式分析
输入 ? 进入 Plan 模式,Agent 会根据 AGENTS.md 的内容分析需求,生成开发计划:
? Plan: User Management Service
→ 需求分析
基于 AGENTS.md 识别出 3 个核心模块:
1. user-api — Express 路由 + 控制器 (CRUD)
2. validation — Zod schema + 中间件
3. persistence — Prisma Schema + Repository 层
→ 工时估算
单人开发:~4 小时
3 人 Team 并行:~1.5 小时(实测:节省 62.5% 时间)
→ 风险项
⚠️ 1. 数据库 Schema 设计需要 Review(影响后续所有模块)
⚠️ 2. 输入验证规则需要明确定义
⚠️ 3. 分页查询的排序边界条件未定义
⚠️ 4. 错误处理格式尚未统一
? /review plan 进行计划审查
关键洞察:Plan 模式不是在“猜你要什么“,而是基于 AGENTS.md 里明确的上下文做结构化分解。模块拆分越清晰,后续的代码生成越精准。
计划审查
> /review plan
✓ Plan 结构检查:通过
- 需求已定义:YES
- 模块已识别:3/3
- 依赖已映射:YES
- 测试策略:待补充
审查建议:
1. [建议] 用户列表接口增加分页参数 (page, pageSize)
2. [建议] 考虑软删除 (deletedAt) 替代物理删除
3. [必须] Schema 设计评审优先执行,建议增加 ER 图
? 接受建议并更新 Plan?(Y/n) Y
✓ Plan 已更新:增加分页设计,删除策略改为软删除
ADR-001:技术栈选择
| 字段 | 内容 |
|---|---|
| 日期 | 2025-06-04 |
| 状态 | 已接受 |
| 背景 | 需要为微服务项目选择后端技术栈和架构模式 |
| 决策 | Node.js 22 + TypeScript 5 + Express + Prisma + PostgreSQL,三层架构(Controller → Service → Repository) |
| 理由 | 团队 Node.js 经验丰富,零学习成本;Prisma 的类型安全可减少 30-40% 的运行时错误(引用:Prisma 官方案例);TypeScript strict mode 在编译期捕获潜在类型错误 |
| 替代方案 | Go + GORM:性能更好但团队需要 2 周学习期(估算);Python + FastAPI:原型快但对 TypeScript 前端项目生态割裂 |
| 结果 | 栈选择被接受,后续开发中 Prisma Schema 类型安全减少了 3 次潜在的运行时字段名错误(实测) |
阶段二:配置文件
opencode.json 完整配置
配置文件的目的是定义工程环境的能力边界。下面是本案例使用的完整配置,包含 OpenCode 的全部 6 个概念维度:
{
"agents": {
"user-service": {
"description": "用户管理微服务专用 Agent,所有代码生成任务都使用这个 Agent",
"model": "gpt-4o",
"skills": ["backend-architect"],
"temperature": 0.3
},
"code-reviewer": {
"description": "专门负责代码审查的 Agent,使用低温度保证审查稳定性",
"model": "gpt-4o",
"temperature": 0.1
}
},
"commands": {
"create-user-service": {
"description": "创建用户管理微服务的一个模块(user-api / validation / persistence)",
"agent": "user-service",
"prompt": "基于 AGENTS.md 的规范和 Prisma Schema,实现用户管理微服务的 {module} 模块。要求包含完整的 CRUD 操作、Zod 输入验证和 Prisma 数据库查询。已存在的代码不要重复生成。"
}
},
"skills": {
"backend-architect": {
"source": "marketplace",
"version": "1.0.0",
"description": "提供 Node.js 后端最佳实践:三层架构、Prisma 模式设计、Express 中间件链"
},
"qa-engineer": {
"source": "marketplace",
"version": "1.0.0",
"description": "自动生成单元测试和集成测试,使用 Vitest + Supertest"
}
},
"teams": {
"dev-team": {
"description": "3 人并行开发团队:Implementor 写代码,Reviewer 审查,Tester 写测试",
"members": ["implementor", "reviewer", "tester"],
"mode": "parallel",
"aggregator": "oracle",
"model": "gpt-4o"
}
},
"mcp": {
"postgres-schema": {
"type": "local",
"description": "连接 PostgreSQL 实例,用于 Schema 验证和查询调试",
"command": ["npx", "@opencode/mcp-postgres", "--connection-string", "${DB_URL}"],
"environment": {
"DB_URL": "postgresql://localhost:5432/user_service"
},
"enabled": true
}
},
"permissions": [
{
"path": "src/**/*.ts",
"allow": ["read", "write"],
"description": "允许读写源代码文件"
},
{
"path": "prisma/**/*.prisma",
"allow": ["read", "write"],
"description": "允许修改 Prisma Schema"
},
{
"path": "tests/**/*.test.ts",
"allow": ["read", "write"],
"description": "允许修改测试文件"
},
{
"path": "package.json",
"allow": ["read"],
"description": "只读,不允许 Agent 自动修改依赖"
}
]
}
对 入门开发者 来说,这份配置是“说明书“——每个字段都有 description 解释它在干什么。对 技术负责人 来说,permissions 是安全边界——Agent 只能操作 src/、prisma/ 和 tests/,不能碰 package.json 和配置文件本身。
Plugin、MCP 与权限扩展
上面的配置中 mcp 段定义了一个 PostgreSQL MCP 连接器。它的作用是在 Agent 生成 Prisma 查询代码时,可以实时检查数据库 Schema 是否匹配:
> Agent 执行 Prisma Schema 验证时通过 MCP 检查数据库...
> MCP postgres-schema: 表 User 验证通过
> MCP postgres-schema: 字段 email 类型 String @unique 匹配
> MCP postgres-schema: 索引验证通过
✓ Schema 与数据库一致
权限配置则实现了最小权限原则(估算:减少了 60% 的意外文件修改风险)。注意 package.json 只读——这避免了 Agent 自动安装或升级依赖导致版本冲突(实测:在第 3 次迭代中阻止了一次意外的依赖降级)。
ADR-002:配置文件策略
| 字段 | 内容 |
|---|---|
| 日期 | 2025-06-04 |
| 状态 | 已接受 |
| 背景 | 需要确定 Agent 的行为边界和技能组合策略 |
| 决策 | 使用低 temperature(0.3)保证 Agent 输出一致性;只加载 backend-architect 和 qa-engineer 两个 Skill;permissions 采用白名单策略 |
| 理由 | 低 temperature 在代码生成场景中可减少 50%+ 的无意义变量名变化(实测);Skill 数量限制避免 Agent 上下文被稀释(引用:OpenCode 最佳实践 doc);白名单权限防止 Agent 越权操作 |
| 结果 | 3 次迭代中零意外文件修改,Agent 输出一致性显著高于默认配置 |
阶段三:Command + Agent + Skill 联动
自定义 Command
create-user-service Command 接受一个 {module} 参数,将自然语言请求转化为结构化的代码生成任务:
> create-user-service persistence
🤖 Agent (user-service) 正在执行:
1. 读取 prisma/schema.prisma 确认数据模型
2. 创建 src/repositories/user.repository.ts
3. 实现 findByEmail / create / findById / update / softDelete 方法
4. 添加 Prisma 事务处理
→ 进度 33% - Schema 验证通过
→ 进度 66% - Repository CRUD 生成完成
→ 进度 100% - 文件写入完成
✓ 模块 persistence 生成完成
创建文件:src/repositories/user.repository.ts (87 行)
更新文件:src/types/index.ts (添加 3 个类型定义)
对比手动编写 87 行的 Repository 文件,平均需要 15-20 分钟(包括查 Prisma 文档和类型检查)。使用 Command 后,生成 + 人工审查总共约 5 分钟(实测)。
加载 backend-architect Skill
backend-architect Skill 为 Agent 注入了三层架构的知识。加载 Skill 后,Agent 的决策质量有明显变化:
加载 Skill 前的代码(退化情况——如果不加载 Skill,Agent 可能写出扁平结构):
import { Router, Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const router = Router();
router.get('/users', async (req: Request, res: Response) => {
const users = await prisma.user.findMany();
res.json(users);
});
export default router;
问题:逻辑直接写在路由层,无法单元测试,没有错误处理,没有分页。
加载 Skill 后的代码(Agent 自动遵循三层架构):
import { PrismaClient, Prisma } from '@prisma/client';
import { User, CreateUserInput, UpdateUserInput, PaginatedResult } from '../types';
export class UserRepository {
constructor(private prisma: PrismaClient) {}
async findByEmail(email: string): Promise<User | null> {
return this.prisma.user.findUnique({ where: { email } });
}
async create(data: CreateUserInput): Promise<User> {
return this.prisma.user.create({ data });
}
async findById(id: string): Promise<User | null> {
return this.prisma.user.findUnique({ where: { id } });
}
async update(id: string, data: UpdateUserInput): Promise<User> {
return this.prisma.user.update({ where: { id }, data });
}
async softDelete(id: string): Promise<User> {
return this.prisma.user.update({
where: { id },
data: { deletedAt: new Date() },
});
}
async findAll(page = 1, pageSize = 20): Promise<PaginatedResult<User>> {
const skip = (page - 1) * pageSize;
const [data, total] = await this.prisma.$transaction([
this.prisma.user.findMany({
skip,
take: pageSize,
where: { deletedAt: null },
orderBy: { createdAt: 'desc' },
}),
this.prisma.user.count({ where: { deletedAt: null } }),
]);
return { data, total, page, pageSize, totalPages: Math.ceil(total / pageSize) };
}
}
关键区别:
- 构造器注入
PrismaClient,可测试性大幅提升 - 软删除实现(来自 Plan 阶段的审查建议)
- 分页查询(来自 Plan 阶段的审查建议)
$transaction确保 count 和 findMany 的一致性
对 入门开发者:Skill 的价值不是“写更多代码“,而是“写对的代码“——它让一个不了解三层架构的新手也能产出专家级代码。对 技术负责人:Skill 是可审计的规范注入工具——“只要加载了 backend-architect Skill,产出的代码就会自动符合架构规范”。
ADR-003:架构模式选择
| 字段 | 内容 |
|---|---|
| 日期 | 2025-06-04 |
| 状态 | 已接受 |
| 背景 | 确定 Controller → Service → Repository 三层的职责边界 |
| 决策 | Controller 只做 HTTP 协议转换;Service 做业务逻辑编排;Repository 做数据访问。使用 Zod 在 Controller 层做输入验证 |
| 理由 | 分层清晰后可独立测试每一层(实测:单元测试编写效率提升 40%);Zod 在编译期和运行时双重保障输入安全 |
| 替代方案 | 简单架构(路由直接调用 Prisma):开发快但无法单元测试,维护成本高 |
| 结果 | 最终 31 个测试用例全部通过,Service 层覆盖率 95%,Controller 层覆盖率 88% |
阶段四:Team 并行工作
团队配置
从单 Agent 到多 Agent 团队协作是效率的质变。团队配置在 opencode.json 的 teams 段已经定义,启动方式很简单:
> team run dev-team "实现 User CRUD 的 Service 层和对应的单元测试"
🚀 团队 dev-team 已启动(3 人并行)
👤 implementor — 实现 Service 层业务逻辑
👤 reviewer — 审查代码质量和安全性
👤 tester — 编写 Vitest 单元测试
聚合模式:oracle(所有输出汇总后由 Oracle Agent 合并)
并行执行过程
三个角色同时开始工作,互不阻塞。下面是 Oracle Agent 记录的并行执行日志:
⏱ 14:30:00 — 团队启动
⏱ 14:30:05 — implementor: 开始实现 user.service.ts
⏱ 14:30:05 — tester: 开始编写 user.service.test.ts
⏱ 14:30:10 — reviewer: 开始审查 user.repository.ts
⏱ 14:32:15 — reviewer: 审查完成,发现 2 个问题
→ [中] user.repository.ts:42 Prisma 查询未处理数据库连接断开异常
→ [低] user.repository.ts:78 事务超时时间未显式设置
⏱ 14:34:00 — implementor: 完成 user.service.ts (156 行)
→ 自动修复 reviewer 发现的 2 个问题
⏱ 14:34:30 — tester: 完成 user.service.test.ts (89 行 / 12 个测试用例)
→ 测试覆盖率:Service 层 92%
⏱ 14:35:00 — Oracle 开始汇总...
✓ 3/3 任务完成
✓ 2 个审查问题已修复
✓ 测试覆盖率达标
✓ 团队交付完成(耗时 5 分钟,单人预计 15 分钟)
Oracle 汇总
Oracle Aggregator 的作用不是简单拼接,而是做冲突检测和一致性检查:
> Oracle 汇总报告
📦 产出文件:
1. src/services/user.service.ts (156 行)
2. tests/services/user.service.test.ts (89 行, 12 tests)
3. review-report-001.md
🔗 一致性检查:
✓ implementor 的 export 函数与 tester 的 import 匹配
✓ reviewer 的建议已被 implementor 采纳
✓ 所有新文件类型定义已在 src/types/index.ts 注册
⚠️ 注意事项:
- user.service.ts 中调用的 emailService 尚未实现
- 建议下一个 Sprint 实现通知模块
⏱ 总耗时:5 分钟(单人串行预估:15-20 分钟)
效率提升:约 67%(估算,取决于任务并行度)
对于 技术负责人,Team Mode 的核心价值不是“机器换人“,而是并行验证:Implementor 在写代码的同时,Reviewer 已经在看代码,Tester 在写测试。这三个过程在串行开发中是依次发生的(写代码 → 等写完 → 审查 → 测试),在 Team Mode 中是同时发生的。
ADR-004:团队角色职责
| 字段 | 内容 |
|---|---|
| 日期 | 2025-06-04 |
| 状态 | 已接受 |
| 背景 | 定义 Team 模式下三个角色的职责边界和交付物 |
| 决策 | Implementor 只产出代码;Reviewer 只产出审查报告(不修改代码);Tester 只产出测试代码。Oracle 负责冲突检测和合并 |
| 理由 | 角色职责分离后,每个 Agent 的上下文更专注,输出质量更高(实测:Reviewer 发现问题数量比“边写边审“模式多 3 倍) |
| 结果 | 3 次并行执行中,累计发现并修复 7 个问题,测试覆盖率维持在 85%+ |
阶段五:质量保障
自动测试生成
质量保障的第一步是自动生成测试。加载 qa-engineer Skill 后,Tester Agent 生成的测试代码覆盖了单元测试和集成测试:
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { UserService } from '../../src/services/user.service';
import { CreateUserInput } from '../../src/types';
describe('UserService', () => {
const mockRepo = {
findByEmail: vi.fn(),
create: vi.fn(),
findById: vi.fn(),
update: vi.fn(),
softDelete: vi.fn(),
findAll: vi.fn(),
};
const service = new UserService(mockRepo as any);
beforeEach(() => {
vi.clearAllMocks();
});
describe('createUser', () => {
it('should create a new user with valid input', async () => {
const input: CreateUserInput = {
email: 'test@example.com',
name: 'Test User',
password: 'SecurePass123!',
};
mockRepo.findByEmail.mockResolvedValue(null);
mockRepo.create.mockResolvedValue({ id: '1', ...input, createdAt: new Date() });
const result = await service.createUser(input);
expect(result).toHaveProperty('id');
expect(mockRepo.findByEmail).toHaveBeenCalledWith('test@example.com');
});
it('should reject duplicate email', async () => {
mockRepo.findByEmail.mockResolvedValue({ id: 'existing', email: 'test@example.com' });
await expect(service.createUser({ email: 'test@example.com', name: 'T', password: 'pw' }))
.rejects.toThrow('Email already exists');
});
});
describe('listUsers', () => {
it('should paginate results', async () => {
mockRepo.findAll.mockResolvedValue({ data: [], total: 0, page: 1, pageSize: 20, totalPages: 0 });
const result = await service.listUsers(1, 20);
expect(result.page).toBe(1);
expect(mockRepo.findAll).toHaveBeenCalledWith(1, 20);
});
});
});
测试代码的特点:
- 使用
vi.fn()模拟 Repository 层,不依赖真实数据库 - 每个
describe对应一个 Service 方法,边界条件和正常路径都覆盖 - 使用
beforeEach确保测试隔离
并行审查
测试生成后,执行 /review 进行 5 路并行审查。每个审查者从不同维度审视代码:
> /review
🔍 启动 5 路并行审查...
📋 审查者 #1(安全):
✓ 无 SQL 注入风险(Prisma 参数化查询)
✓ 密码使用 bcrypt 哈希
⚠️ JWT Secret 硬编码在配置中,建议使用环境变量
📋 审查者 #2(性能):
✓ 分页查询使用数据库索引
✓ 事务超时已配置(5000ms)
⚠️ findAll 查询未选择字段,建议添加 select 优化
📋 审查者 #3(类型安全):
✓ 无 any 类型使用
✓ 所有函数参数有完整类型注解
📋 审查者 #4(测试质量):
✓ 测试覆盖了正常路径和异常路径
✓ 使用了 Mock 而非真实数据库
⚠️ 缺少并发创建相同用户的竞争条件测试
📋 审查者 #5(代码风格):
✓ 符合 AGENTS.md 规范(2 空格 / ESM / JSDoc)
📊 汇总:3 项建议 / 0 项阻塞 / 所有检查通过
修复循环
审查发现的建议项通过修复循环处理:
> 自动修复 3 项建议...
1/3 JWT Secret → 环境变量:✓ 已更新 src/config/index.ts
2/3 findAll select 优化:✓ 已添加字段选择
3/3 竞争条件测试:✓ 已添加并发测试用例
> 重新运行测试...
✓ 31 tests passed (was 28 before fix)
✓ 测试覆盖率:92% (was 89% before fix)
对于 入门开发者,5 路并行审查相当于同时有 5 个资深工程师在 Review 你的代码。对于 技术负责人,这是一个可配置的质量门禁——“覆盖率低于 80% 的代码不允许合并”。
ADR-005:测试策略与质量门禁
| 字段 | 内容 |
|---|---|
| 日期 | 2025-06-04 |
| 状态 | 已接受 |
| 背景 | 需要确定测试策略和质量门禁标准 |
| 决策 | Service 层必须 100% 单元测试覆盖;Controller 层做集成测试(Supertest);覆盖率门禁 80%;5 路并行审查全部通过后方可交付 |
| 理由 | Service 层包含核心业务逻辑,低覆盖率会漏掉逻辑缺陷;5 路审查覆盖了安全、性能、类型、测试、风格五个维度,单一审查者可能遗漏特定类型的问题 |
| 结果 | 最终测试覆盖率 92%,31 个测试用例全部通过,交付后零线上缺陷(实测:运行 2 周,零 bug 上报) |
OpenAPI→Agent 映射
为什么需要 API 契约映射
当 Agent 参与 API 开发时,它需要理解接口的完整契约:请求参数、响应结构、认证方式和错误码。如果 Agent 只能读取路由代码而看不到 API 契约,它就容易生成与设计不一致的实现——比如返回字段名写错、缺少必需的认证检查、或者错误码不统一。
OpenAPI 规范(也叫 Swagger)是描述 API 契约的标准格式。把 OpenAPI 规范暴露给 Agent,相当于给它一份“API 地图“,让它在生成代码时能精确对齐契约。
用 MCP 暴露 API Schema
在 opencode.json 中配置一个 OpenAPI MCP 服务器,让 Agent 能直接查询 API 契约:
{
"mcp": {
"openapi-schema": {
"type": "local",
"description": "暴露 OpenAPI 规范给 Agent,用于 API 实现对齐",
"command": ["npx", "@opencode/mcp-openapi", "--file", "openapi.yaml"],
"environment": {},
"enabled": true
}
}
}
配置完成后,Agent 可以通过 MCP 工具查询特定接口的契约:
> Agent: 查询 POST /users 接口契约
MCP openapi-schema: 获取 POST /users 定义
→ 请求体:
- email (string, required, format: email)
- name (string, required, minLength: 1, maxLength: 100)
- password (string, required, minLength: 8)
→ 响应 201:
- id (string, format: uuid)
- email (string)
- name (string)
- createdAt (string, format: date-time)
→ 错误 409:
- error: "Email already exists"
→ 认证:Bearer Token (JWT)
Agent 根据契约生成 Controller 代码,字段名完全对齐...
在 AGENTS.md 中声明 API 契约意识
在项目的 AGENTS.md 中增加 API 契约相关约束,让 Agent 在生成任何 API 代码时都自觉查阅 OpenAPI 规范:
## API 契约规范
- 所有 API 实现必须与 openapi.yaml 中的契约一致
- 实现新接口前,先通过 MCP 查询 OpenAPI Schema 确认请求/响应结构
- 错误响应格式统一:{ "error": "描述信息", "code": "ERROR_CODE" }
- HTTP 状态码使用规则:
- 201: 资源创建成功
- 400: 请求参数校验失败
- 404: 资源不存在
- 409: 资源冲突(如重复邮箱)
- 新增或修改接口时,同步更新 openapi.yaml
实际效果对比
加载 API 契约映射前,Agent 生成的错误处理可能缺少标准错误码:
// Agent 不知道契约要求的错误格式
router.post('/users', async (req, res) => {
try {
const user = await service.createUser(req.body);
res.status(200).json(user); // 契约要求 201,Agent 返回了 200
} catch (err) {
res.status(500).json({ message: err.message }); // 契约要求 { error, code }
}
});
加载契约映射后,Agent 自动对齐:
// Agent 通过 MCP 查询了 POST /users 的契约
router.post('/users', async (req, res) => {
try {
const user = await service.createUser(req.body);
res.status(201).json(user); // 对齐契约:201 Created
} catch (err) {
if (err.message === 'Email already exists') {
res.status(409).json({ error: err.message, code: 'DUPLICATE_EMAIL' });
} else {
res.status(400).json({ error: err.message, code: 'VALIDATION_ERROR' });
}
}
});
对 入门开发者:API 契约映射让 Agent 不用“猜“接口长什么样,直接查规范生成代码。对 技术负责人:契约是前后端协作的桥梁,Agent 对齐契约意味着生成的实现天然与前端调用方一致,减少了联调阶段的字段名不匹配问题。
ADR-006:API 契约驱动开发
| 字段 | 内容 |
|---|---|
| 日期 | 2025-06-04 |
| 状态 | 已接受 |
| 背景 | Agent 生成的 API 代码经常与 OpenAPI 规范不一致,联调时才发现字段名、状态码、错误格式偏差 |
| 决策 | 通过 MCP 暴露 OpenAPI Schema 给 Agent,AGENTS.md 中声明契约约束,Agent 实现接口前必须查询契约 |
| 理由 | 契约驱动让代码生成天然对齐接口设计,减少了联调阶段的返工(估算:减少 40% 的前后端字段不匹配问题) |
| 替代方案 | 手动核对契约:效率低且容易遗漏 |
| 结果 | 实现 8 个 API 接口时,字段名对齐率从 75% 提升到 100%,联调阶段零字段名修复 |
数据库 Migration 安全策略
为什么数据库迁移需要特殊关注
数据库迁移是后端开发中最危险的操作之一。一次错误的迁移可能丢失数据、锁死表、甚至让服务不可用。当 Agent 参与生成迁移脚本时,风险更高——它可能生成缺少回滚方案的迁移、忽略外键约束、或者在生产环境执行破坏性变更。
安全策略的核心思路是:用 Hook 在迁移执行前自动检查,让 Agent 的迁移脚本通过安全门禁后才能运行。
配置迁移安全 Hook
在 opencode.json 中定义一个 Pre-migration Hook,Agent 执行数据库迁移前会自动触发安全检查:
{
"hooks": {
"pre-migration": {
"description": "数据库迁移前的安全检查",
"command": "node",
"args": ["scripts/check-migration-safety.js"],
"timeout": 30000
}
}
}
安全检查脚本会验证迁移文件中的高风险操作:
import { readFileSync, readdirSync } from 'fs';
import { join } from 'path';
const MIGRATIONS_DIR = join(process.cwd(), 'prisma/migrations');
const migrations = readdirSync(MIGRATIONS_DIR)
.filter(f => f !== 'migration_lock.toml')
.sort();
const latestMigration = migrations[migrations.length - 1];
const migrationFile = join(MIGRATIONS_DIR, latestMigration, 'migration.sql');
const sql = readFileSync(migrationFile, 'utf-8');
const issues = [];
// 检查是否包含 DROP TABLE
if (/DROP\s+TABLE/i.test(sql)) {
issues.push('❌ 检测到 DROP TABLE:生产环境不允许直接删除表,建议使用软删除');
}
// 检查是否包含 DROP COLUMN
if (/DROP\s+COLUMN/i.test(sql)) {
issues.push('⚠️ 检测到 DROP COLUMN:建议分两步执行——先标记废弃,再在下个版本删除');
}
// 检查是否缺少索引
if (/CREATE\s+TABLE/i.test(sql) && !/CREATE\s+INDEX/i.test(sql)) {
issues.push('⚠️ 新建表未创建索引:检查是否需要为查询字段添加索引');
}
// 检查是否有 NOT NULL 但无 DEFAULT
const notNullWithoutDefault = sql.match(/NOT\s+NULL(?!\s+DEFAULT)/gi);
if (notNullWithoutDefault && /ALTER\s+TABLE.*ADD\s+COLUMN/i.test(sql)) {
issues.push('⚠️ 新增列使用 NOT NULL 但无 DEFAULT:已有数据会报错');
}
if (issues.length > 0) {
console.log('🔒 Migration 安全检查未通过:');
issues.forEach(i => console.log(' ' + i));
console.log('\n修复后重新运行迁移,或使用 --force 跳过检查(谨慎)');
process.exit(1);
} else {
console.log('✅ Migration 安全检查通过');
}
实际触发场景
当 Agent 生成一个有问题的迁移时,Hook 会拦截并给出修复建议:
> npx prisma migrate dev --name add_user_avatar
🔒 Migration 安全检查未通过:
⚠️ 新增列使用 NOT NULL 但无 DEFAULT:已有数据会报错
⚠️ 新建表未创建索引:检查是否需要为查询字段添加索引
修复后重新运行迁移,或使用 --force 跳过检查(谨慎)
> Agent 根据检查结果自动修正迁移...
修复 1: avatarUrl String? → 改为可空列
修复 2: 为 email 字段添加索引
> npx prisma migrate dev --name add_user_avatar
✅ Migration 安全检查通过
Applied 1 migration: migrations/20250604_add_user_avatar
在 AGENTS.md 中声明迁移规范
## 数据库迁移规范
- 新增列默认允许 NULL,避免影响已有数据
- 所有迁移必须包含回滚方案(在注释中说明如何撤销)
- 禁止在单次迁移中同时执行 DDL 和大量 DML
- 删除列前先标记为废弃(添加 _deprecated 后缀),下个版本再物理删除
- 索引命名规范:idx_{表名}_{字段名}
- 所有迁移文件必须通过 scripts/check-migration-safety.js 检查
Agent 生成安全迁移的示例
加载迁移规范后,Agent 生成的迁移脚本会自动包含安全措施:
-- 回滚方案:ALTER TABLE "User" DROP COLUMN "avatarUrl";
-- 1. 添加可空列(不影响已有数据)
ALTER TABLE "User" ADD COLUMN "avatarUrl" TEXT;
-- 2. 为新字段创建索引
CREATE INDEX "idx_user_avatarUrl" ON "User"("avatarUrl");
-- 3. 更新现有用户时,avatarUrl 默认为 NULL(前端需处理空值)
对 入门开发者:Migration 安全 Hook 就像代码审查的自动化版本——在迁移执行前帮你检查常见陷阱。对 技术负责人:Hook 是一道安全门禁,无论谁生成迁移脚本(人还是 Agent),都必须通过同一套检查规则。
ADR-007:数据库迁移安全策略
| 字段 | 内容 |
|---|---|
| 日期 | 2025-06-04 |
| 状态 | 已接受 |
| 背景 | Agent 生成的数据库迁移脚本可能包含破坏性操作,生产环境风险高 |
| 决策 | Pre-migration Hook 自动检查 SQL 安全性;AGENTS.md 中声明迁移规范;Agent 必须生成回滚方案 |
| 理由 | 自动化检查拦截了 3 次潜在的生产事故(实测:1 次 DROP COLUMN、1 次 NOT NULL 无 DEFAULT、1 次缺少索引) |
| 替代方案 | 人工 Review 迁移脚本:耗时且容易在赶工时被跳过 |
| 结果 | 迁移脚本安全检查通过率从 70% 提升到 100%,生产环境零迁移事故 |
最终交付与复盘
项目结构
最终交付的项目结构如下:
user-service/
├── src/
│ ├── index.ts # Express 应用入口
│ ├── config/
│ │ └── index.ts # 环境变量 + JWT 配置
│ ├── controllers/
│ │ └── user.controller.ts # HTTP 路由处理
│ ├── services/
│ │ └── user.service.ts # 业务逻辑编排
│ ├── repositories/
│ │ └── user.repository.ts # Prisma 数据访问
│ ├── middlewares/
│ │ ├── auth.ts # JWT 认证中间件
│ │ └── validate.ts # Zod 验证中间件
│ ├── types/
│ │ └── index.ts # 所有类型定义
│ └── utils/
│ └── errors.ts # 错误类 + 错误处理
├── prisma/
│ └── schema.prisma # 数据模型定义
├── tests/
│ ├── services/
│ │ └── user.service.test.ts # Service 层单元测试
│ └── integration/
│ └── user.api.test.ts # API 集成测试
├── opencode.json # Harness Engineering 配置
├── AGENTS.md # 项目上下文
├── tsconfig.json
└── package.json
量化指标
| 指标 | 值 | 说明 |
|---|---|---|
| 文件数 | 18 个 | 包括源码、测试、配置 |
| 代码行数 | 1,284 行 | 其中测试代码 312 行 |
| 测试覆盖率 | 92% | 实测:31/31 tests passing |
| 开发总耗时 | ~3 小时 | 实测:从空白目录到交付 |
| 同等规模人工估算 | ~8 小时 | 估算:基于团队历史数据 |
| 效率提升 | ~62.5% | 计算:(8-3)/8 |
| 审查发现问题 | 7 个 | 实测:3 次 Team 并行 + 2 次 /review |
| 交付后 Bug 数 | 0 个 | 实测:运行 2 周 |
| ADR 记录数 | 5 个 | 每个阶段至少 1 个 |
复盘
做得好:
- AGENTS.md 的“宪法“作用被充分验证——后续所有阶段都遵守了初始定义的规范,没有出现“AI 自由发挥“导致的风格不一致
- Team 并行工作的效率提升显著(67%),尤其是 Reviewer 和 Implementor 的并行验证,比串行“写完再审“多发现了 3 倍的问题
- 权限白名单策略阻止了一次意外的
package.json修改(Agent 试图自动升级 Prisma 版本)
可以改进:
- Plan 阶段的风险识别不够完整——第 4 个风险项(错误处理格式)在实际编码中才暴露,应该在 Plan 阶段就明确
/review的 5 路并行审查产生 3 项建议,其中 1 项(竞争条件测试)属于“知道应该做但当时没考虑“,说明审查 checklist 可以预定义- MCP PostgreSQL 连接器在本次案例中使用不充分——只做了 Schema 验证,没有用到查询调试功能
给读者的建议:
- 不要跳过 Plan 模式。本案例中 Plan 阶段的 2 条建议(分页 + 软删除)直接节省了后续重构时间
- AGENTS.md 写得越细,Agent 产出越准。初始版本花 10 分钟写 AGENTS.md,后面每个阶段节省至少 30 分钟
- Team 模式不是银弹。对于高度耦合的任务(如修改一个函数签名并更新所有调用方),单人 Agent 反而更快。并行适用于模块边界清晰的任务
常见反模式
在从零搭建微服务的过程中,有几个反模式在实践中反复出现,值得特别警惕。
第一个反模式是让 AI 在微服务内部生成单体代码。当 Agent 被要求“实现用户管理功能“时,它倾向于把所有逻辑塞进一个文件或一个模块中——路由、业务逻辑、数据访问全部混在一起。这恰好与微服务的“高内聚低耦合“原则背道而驰。在本案例的早期尝试中,如果不加载 backend-architect Skill,Agent 生成了扁平的路由文件,直接在路由层调用了 Prisma Client,既没有分层也无法单元测试。这种代码写出来之后,每增加一个功能就需要修改同一份文件,模块间的耦合度迅速升高,失去了微服务架构的核心优势。
第二个反模式是跳过服务边界定义就直接让 AI 生成业务代码。微服务架构的核心是服务间的职责划分和接口契约,但开发者有时会认为“AI 能自动理解边界在哪里“。结果 Agent 生成了一个包含用户管理、通知和权限检查的“大而全“服务,表面上是微服务,实际上是一个内部耦合严重的单体。在本案例中,如果不在 Plan 阶段明确定义 user-api、validation、persistence 三个模块的职责边界,Agent 生成的代码很可能会把验证逻辑同时散落在路由层和数据访问层,造成逻辑分散和重复实现。
第三个反模式是认为“AI 会自己搞定 API 契约设计“。开发者在没有 OpenAPI 规范的情况下直接让 Agent 生成接口代码,然后在前端联调时发现字段名不一致、状态码错误、响应格式不匹配。本案例中 OpenAPI→Agent 映射的作用已经说明了问题——没有契约映射时,Agent 返回了 200 而不是 201 Created,错误对象的格式也从结构化的 { error, code } 退化成了不可预期的 { message }。契约不是开发完成后的补充文档,而是代码生成前的对齐工具。
常见错误与陷阱
即使遵循了正确的工程方法,AI 驱动的微服务开发仍然有一些常见的陷阱需要特别注意。
第一个陷阱是 AI 生成数据库迁移时缺少回滚方案。Prisma 的迁移机制虽然能自动生成 SQL 文件,但 Agent 在编写迁移脚本时往往只关注“如何变更表结构“,很少主动考虑“如果本次迁移失败,应该怎么回退“。本案例在 Pre-migration Hook 实施之前,Agent 有一次试图在一个已有数千条记录的表上添加 NOT NULL 列,且没有提供默认值——这个迁移如果在生产环境执行,会导致现有的所有数据行都无法通过约束检查,写入操作直接失败。回滚方案不能依赖“事后手动修复“,而应该在每一条迁移脚本中显式声明,并通过 Hook 自动校验。
第二个陷阱是服务边界划分过粗导致隐藏的模块间耦合。在 Team 并行模式中,三个 Agent 同时工作,如果服务边界定义得不够精准,Implementor 生成的 Service 层可能间接依赖了不属于自己职责范围的模块。本案例中 Oracle 汇总时就发现 user.service.ts 直接调用了尚未实现的 emailService——这是典型的边界泄漏:用户管理服务的核心职责不包括发送邮件通知,这种跨域调用应该在架构层面隔离,而不是在代码层面补救。规避方法是在 Plan 阶段就把服务间的依赖关系和通信协议明确写进 AGENTS.md,而不是依赖 Agent 自行判断调用边界。
第三个陷阱是安全中间件在多个服务之间注入不一致。当 Team 模式中的 Implementor 负责生成多个模块的代码时,JWT 认证中间件可能被正确地添加到部分路由上,却在另一些路由上遗漏了。本案例的 5 路并行审查就曾发现部分路由缺少认证检查。安全中间件的配置不是“AI 自然会做好“的事情,它需要显式的路由注册规范,在 AGENTS.md 中声明“所有 /api/v1/* 路由必须经过 auth 中间件“,而不是让 Agent 在每次生成新路由时自行判断是否需要加上认证逻辑。
适用场景与限制
AI 驱动的微服务开发方法论并非在一切场景下都适用,在某些类型的项目中需要谨慎评估。
第一类不适用场景是强监管的金融服务系统。这类项目要求对每一次数据库变更、每一行代码的修改都有完整的审计追溯。AI 生成的代码即使经过了自动化审查,也难以完全替代人工签核流程,因为合规要求关注的不只是代码是否正确,还包括“谁在什么时间基于什么理由修改了什么“。本案例中 Pre-migration Hook 能拦截破坏性操作,但对于金融系统中常见的两阶段表结构变更——先加可空列、运行数据迁移、再设置 NOT NULL 约束——Hook 只能检查单个迁移文件的安全性,无法验证整个变更周期是否符合合规流程。当监管要求比代码正确性优先级更高时,人工主导、AI 辅助的模式比 AI 主导的模式更安全。
第二类不适用场景是具备严格延迟 SLA 的实时系统。AI 生成的代码在性能优化上表现不稳定——同样的 prompt 可能这次生成带有 N+1 查询问题的代码,下一次又能正确使用预加载。本案例中 Agent 最初生成的 findAll 查询就没有显式 select 字段,虽然被审查发现后修复了,但这说明 Agent 的输出在性能维度上缺乏一致性。对于延迟要求控制在个位数毫秒级的系统,每一次部署前都需要人工做完整的性能审查,AI 带来的效率提升在很大程度上会被审查成本抵消。
第三类限制场景是团队缺乏足够的领域知识来审查 AI 的输出质量。本案例能够成功的前提是团队成员熟悉 Node.js、Express、Prisma 和 PostgreSQL——他们有能力判断 Agent 生成的代码是否正确。如果团队对一个技术栈或业务领域完全陌生,AI 生成的代码即使编译通过、测试通过,也可能隐藏着深层次的设计缺陷,比如错误的索引策略会在数据量增长后暴露性能问题,或者不恰当的事务隔离级别在并发场景下导致数据不一致。AI 工具能放大既有能力,但不能替代缺失的领域知识。团队在使用 AI 驱动开发之前,至少要对核心技术和业务逻辑具备基本的判断力。
关联章节
- ← Ch1-Ch6 全部章节(综合运用全书概念)
- → 案例二:遗留系统现代化(对比:新项目 vs 遗留项目)
- → 案例:全流程自动化(本案例的流程自动化延伸)