OpenSpec 是一种以规格(Spec)为中心的工程方法与工具链,旨在通过统一的结构化文档与自动化工作流,提升复杂系统在设计到交付全周期的确定性与可验证性。本文结合一个小型电商网站的完整案例,演示从架构设计、系统设计、模块设计、接口设计,到单元测试、接口测试、集成测试、性能测试的全流程,以促进在真实项目中落地。
进阶阅读:关于本案例的 AI 协作全流程复盘(Prompt 设计与交互记录)及 Python 版本的跨语言复刻验证,请参考 OpenSpec 实战指南:AI 辅助软件工程全流程深度复盘。
CLI 参考手册:关于 OpenSpec CLI 的完整命令说明(init、validate、archive 等),请参考 OpenSpec 使用手册。
培训材料:本项目提供了配套的演示文稿 OpenSpec 使用手册,适合用于团队培训与技术分享。
OpenSpec 不仅仅是一套文档格式,更是一种**Spec 驱动开发(Spec-Driven Development)**的工程实践。它主张“以规格为源”,确保代码与测试始终与设计保持一致,解决传统开发中“文档落后于代码”的顽疾。
OpenSpec 的设计遵循四大原则:
- 流动而非僵化 (Fluid not rigid):不设强制关卡,按需创建文档。
- 迭代而非瀑布 (Iterative not waterfall):在构建中学习,随时修正规格。
- 简单而非复杂 (Easy not complex):轻量级启动,无繁琐流程。
- 存量优先 (Brownfield-first):不仅适用于新项目,也能通过 Delta 机制平滑接入现有代码库。
OpenSpec 将项目状态分为两个核心区域,确保“当前状态”与“变更过程”分离:
-
Source of Truth (
openspec/specs/) 这是系统当前的真实行为描述。所有已发布的特性都必须在此处有对应的规格定义。目录结构通常按领域(Domain)划分,如auth/spec.md,payment/spec.md。 -
Proposed Changes (
openspec/changes/) 这是进行中的变更。每个变更是一个独立的文件夹(如openspec/changes/add-login/),包含完整的上下文:- Proposal (
proposal.md):Why & What。阐述背景、意图与范围。 - Design (
design.md):How。技术方案、架构图与数据流。 - Specs (Deltas):Changes。对
openspec/specs/中现有规格的修改草案。 - Tasks (
tasks.md):Steps。具体的实施步骤与验收标准。
- Proposal (
当变更开发完成并归档(Archive)后,其 Delta Spec 会合并入主 Spec,形成新的事实来源。
OpenSpec 提供了两种主要的工作流方式:CLI 和 Slash Commands(AI 协作)。
CLI (openspec) 适合人类开发者进行管理操作:
- 初始化 (
init):一键生成标准目录结构,配置.openspec环境。 - 浏览与查看 (
list/view):快速检索现有的变更与规格。 - 校验 (
validate):检查文档结构的合法性。 - 归档 (
archive):将完成的变更移入归档区。
在支持的 AI 编辑器(如 Cursor, Claude Code)中,推荐使用 Slash Commands 驱动开发:
- 提出变更 (
/opsx:propose):快速创建变更提案。 - 实现变更 (
/opsx:apply):根据规范自动编写代码。 - 归档变更 (
/opsx:archive):完成并归档当前工作。
-
结构化校验 (Validation) OpenSpec 引入了 Zod 等校验机制,确保 Spec 文档不仅仅是文本,而是符合 Schema 定义的结构化数据。这使得自动生成测试用例(Test Case Generation)成为可能。
-
遥测 (Telemetry) 内置基于 PostHog 的匿名遥测(可选),用于收集命令执行数据(如
command_executed),帮助团队分析工具使用频率与流程瓶颈。设计上严格遵循隐私原则,不收集参数、IP 或业务内容,并支持OPENSPEC_TELEMETRY=0环境变量完全关闭。
OpenSpec 的迭代流程围绕着“规格优先”展开,但并不强制要求繁琐的审批流程。
在任何代码开始之前,首先通过 proposal.md 定义Why和What。
- 业务目标:例如“构建一个最小可用的电商下单流程”。
- 非功能性指标 (SLO):
- p99 延迟 < 100ms
- 支持 50 RPS 并发
- 内存数据存储(Demo 阶段)
使用 CLI 快速初始化项目结构(如果你直接使用 examples/ecommerce-mini 源码,此步骤已完成,可跳过):
openspec init --tools none在 openspec/changes/ 下创建一个新的变更集(如 v1-mvp),并包含以下文件:
这是项目的起点,清晰定义了“我们为什么要做这个”以及“成功的标准是什么”。
# Proposal: Ecommerce Mini MVP
## Why
### Background
本项目起源于社区关于 AI 编程的深度探讨,需要一个完整的实战案例来演示 OpenSpec 规范在 AI 辅助编程中的具体应用。
### Problem Statement
需要一个规范化的方式来确保人与 AI 对需求达成一致,同时保持代码与文档的同步。
### Alternatives Considered
1. **直接编写代码**: 快速但缺乏文档支撑。
2. **传统需求文档**: 与代码分离,容易过时。
3. **OpenSpec 规范驱动**: 先定义规范,再编写代码,保持一致性 ✓ 已选择此方案。
## What Changes
### New Resources Added
- **领域模型**: Product、User、Cart、Order 等核心实体。
- **API 接口**: 商品、购物车、订单、支付等 RESTful 接口。
### New Capabilities
- 商品列表查询与上架。
- 购物车管理(添加、移除)。
- 下单结算与库存扣减。
## Goals (SLO)
- **Latency**: 核心接口 p99 < 100ms。
- **Quality**: 核心逻辑测试覆盖率 > 80%。
## Scope
- **In Scope**: Catalog, Cart, Order, User, Memory Storage.
- **Out of Scope**: Search, Recommendation, Payment Gateway.design.md: 系统设计草案(架构图、数据流)。specs/: 具体的接口定义与数据模型。tasks.md: 拆解后的开发任务。
基于 Proposal,在 design.md 中确定:
- 边界:明确 Catalog, Cart, Order, User 等模块的职责。
- 数据流:绘制“用户 -> 加购 -> 下单 -> 支付”的关键路径。
- 接口:定义 RESTful API 的 URL 结构与 Verb。
这是 OpenSpec 的核心——代码是对规格的映射。
- 领域层 (
domain/):直接映射 Spec 中的数据模型。 - 接口层 (
http/):直接映射 Spec 中的 API 定义。 - 测试层 (
__tests__/):直接映射 Spec 中的验收标准 (Scenarios)。
- 自动化测试:运行单元测试与集成测试,确保实现符合 Spec。
- 基线度量:在开发阶段就运行性能基准测试 (
performance.spec.js),确保 SLO 达标。 - 规范验证:使用
openspec validate命令验证规范文档格式是否正确。
# 验证变更规范
openspec validate v1-mvp
# 验证成功输出
✓ Change 'v1-mvp' is valid注意:验证命令会检查 Proposal 章节完整性、目录结构、Delta Header 格式以及 Scenario 覆盖率。详细的验证规则与错误排查请参考 OpenSpec 使用手册 - 6. 验证与常见错误。
当 v1-mvp 开发完成并通过验收后,运行 openspec archive。这会将变更集中的 Spec 合并到主分支 (openspec/specs/),成为系统最新的事实来源。
本案例构建一个名为 ecommerce-mini 的微型电商系统,包含五个核心上下文:
- Catalog (商品): 管理商品信息与库存。
- User (用户): 身份识别与认证。
- Cart (购物车): 临时存放欲购买商品。
- Order (订单): 交易的核心单据与状态流转。
- Payment (支付): 资金结算模拟。
为了聚焦 OpenSpec 流程,本项目做了以下工程折衷:
- 数据存储:仅使用内存 Map 或本地文件,不依赖外部 DB。
- 单体架构:所有模块运行在同一个 Node.js 进程中,通过模块导入通信。
- 环境依赖:仅依赖 Node.js (v20+),零 npm 依赖(生产级扩展除外)。
- 延迟:核心 API (GET /products, POST /orders) p99 < 100ms。
- 可靠性:订单数据在服务重启后不丢失(需持久化扩展)。
- 质量:核心业务逻辑覆盖率 > 80%。
本项目采用经典的四层架构,确保关注点分离:
| 层级 | 目录 (src/) |
职责 | 依赖方向 |
|---|---|---|---|
| 接口层 | http/ |
处理 HTTP 请求,参数解析,鉴权,响应格式化 | -> Application |
| 应用层 | services/ |
用例编排(Orchestration),如“下单”涉及扣库存、清购物车 | -> Domain, Repo |
| 领域层 | domain/ |
纯净的业务实体 (types.ts) 与逻辑,无外部依赖 |
None |
| 基础设施 | repo/, persist/ |
数据持久化实现(Memory/File) | Implementation Detail |
- 严格单向依赖:HTTP -> Service -> Domain。
- 依赖倒置:Service 层定义 Repository 接口,Infrastructure 层实现它(本示例简化为直接调用)。
- 数据隔离:模块间不直接访问对方数据库,必须通过 Service 接口调用。
以“下单”场景为例:
- User 发起
POST /api/orders请求。 - HTTP Layer 解析 Token,验证用户身份。
- Order Service 接收请求:
- 调用 Cart Service 获取当前购物车商品。
- 调用 Catalog Service 扣减库存(事务一致性边界)。
- 计算总价,生成订单实体。
- Order Repo 保存订单数据。
- HTTP Layer 返回
201 Created及订单详情。
采用标准的 RESTful JSON 风格。
- URL 规范:资源复数形式,如
/api/products。 - 状态码:
200 OK: 查询/修改成功。201 Created: 资源创建成功。400 Bad Request: 业务校验失败(如库存不足)。401 Unauthorized: 未登录。409 Conflict: 资源冲突(如重复下单)。
在 OpenSpec 中,我们首先在 specs/domain/spec.md 中定义模型。规范文件必须使用 Delta Header + Requirement + Scenario 格式:
Spec 定义 (specs/domain/spec.md):
# Domain Specification
## Overview
定义核心业务实体与规则,不依赖任何外部框架。
## ADDED Requirements
### Requirement: 商品实体定义
系统 SHALL 定义商品实体,包含唯一标识、名称、价格和库存。
**Priority**: P0 (Critical)
**Rationale**: 商品是电商系统的核心实体,是所有交易的基础。
#### Scenario: 创建有效商品
Given 需要创建新商品
When 提供商品信息 { id, name, priceCents, stock }
Then 商品实体创建成功
And id 格式为 prod_xxxx
And priceCents >= 0
And stock >= 0
---
### Requirement: 库存非负校验
库存扣减后 MUST NOT 为负数。
**Priority**: P0 (Critical)
**Rationale**: 库存为负会导致超卖,影响业务准确性。
#### Scenario: 库存不足时扣减
Given 商品库存为 5
When 尝试扣减 6 个库存
Then 抛出 OUT_OF_STOCK 错误
And 库存保持不变代码实现 (src/domain/types.js / JSDoc):
由于本项目使用原生 JS,我们通过 JSDoc 实现对 Spec 的映射:
/**
* @typedef {Object} Product
* @property {string} id
* @property {string} name
* @property {number} priceCents
* @property {number} stock
*/统一错误响应结构,便于前端处理:
{
"code": "OUT_OF_STOCK",
"message": "商品 [prod_123] 库存不足"
}- Capabilities:
listProducts(): 全量查询(Demo 不做分页)。getProduct(id): 详情查询。deductStock(id, qty): 原子扣减库存,需处理并发竞争(Demo 简化为单线程锁)。
- Capabilities:
addToCart(userId, item): 增量更新。clearCart(userId): 下单后清空。
- Storage: Key 为
userId,Value 为Cart对象。
- Capabilities:
createOrder(userId): 核心复杂逻辑,协调 Cart 与 Catalog。payOrder(orderId): 状态流转PENDING->PAID。
OpenSpec 的核心优势在于使用 Markdown 编写可读性极强的规格文档,同时保持结构化。以下是 openspec/specs/api/spec.md 的真实片段:
# API Specification
## Overview
定义对外 RESTful 接口契约,涵盖商品、购物车、订单、支付等核心功能。
## ADDED Requirements
### Requirement: 商品列表查询
系统 SHALL 提供商品列表查询接口,返回所有可用商品。
**Priority**: P0 (Critical)
**Rationale**: 商品浏览是电商系统的核心入口功能。
#### Scenario: 获取所有商品
Given 系统中存在商品数据
When 用户请求 GET /api/products
Then 返回状态码 200
And 返回商品数组 Product[]
---
### Requirement: 订单创建
系统 SHALL 支持结算购物车生成订单,处理库存扣减。
**Priority**: P0 (Critical)
**Rationale**: 订单创建是交易的核心流程。
#### Scenario: 成功创建订单
Given 用户购物车中有商品
And 商品库存充足
When 发送 POST /api/orders 携带 { userId }
Then 返回状态码 201
And 返回新创建的订单 Order
#### Scenario: 创建订单时库存不足
Given 用户购物车中有商品
And 商品库存不足
When 发送 POST /api/orders 携带 { userId }
Then 返回状态码 409
And 返回错误信息 "Stock insufficient"注意:
- 必须使用
## ADDED/MODIFIED/REMOVED Requirements作为 Delta Header。- 每个需求使用
### Requirement: <标题>格式,描述中必须包含SHALL或MUST。- 每个需求至少包含一个
#### Scenario:块,使用 Gherkin 格式(Given/When/Then)。- 这里的
Product[]和Order引用了domain/spec.md中定义的数据模型,保持了定义的一致性。
这是 OpenSpec 的核心——代码是对规格的映射。在编写代码时,开发者(或 AI)应始终打开 Spec 文件作为参考。
建立 Spec ↔ 代码 ↔ 测试 的映射关系,确保每一条 Spec 都有代码落地。详见 10.1 追踪矩阵(Traceability Matrix)。
OpenSpec-practise/
├── docs/ <-- 文档目录
│ ├── OpenSpec使用手册.md <-- OpenSpec 使用手册
│ ├── openspec-practical-guide.md <-- 实战指南(本文档)
│ └── openspec-ai-workflow-analysis.md <-- AI 协作流程分析
├── examples/
│ ├── openspec/ <-- OpenSpec 规范目录
│ │ └── changes/v1-mvp/ <-- MVP 变更规范
│ │ ├── proposal.md <-- 对应提案文档
│ │ ├── design.md <-- 对应设计文档
│ │ ├── tasks.md <-- 对应任务清单
│ │ └── specs/
│ │ ├── api/spec.md <-- 对应 API 规范
│ │ └── domain/spec.md <-- 对应领域模型规范
│ ├── ecommerce-mini/ <-- Node.js Implementation
│ │ └── src/
│ │ ├── domain/types.js <-- 对应 Spec 中的 Data Models
│ │ ├── services/ <-- 对应 Spec 中的 Business Rules
│ │ ├── http/server.js <-- 对应 Spec 中的 API Definitions
│ │ └── persist/ <-- 对应 Design 中的 Storage Strategy
│ └── ecommerce-mini-python/ <-- Python Implementation
Controller 层 (src/http/server.js):
// 对应 Spec: POST /api/orders
if (pathname === "/api/orders" && req.method === "POST") {
const body = await readJson(req);
try {
// 编排业务逻辑 (Orchestration)
// 1. 检查购物车 (Rule: Cart Not Empty)
// 2. 检查库存 (Rule: Stock Check)
// 3. 创建订单
const order = orderService.createOrder(body.userId);
return sendJson(res, 201, order);
} catch (e) {
if (e.message === "CART_EMPTY")
return sendError(res, "CART_EMPTY", "购物车为空", 400);
if (e.message === "OUT_OF_STOCK")
return sendError(res, "OUT_OF_STOCK", "库存不足", 409);
throw e;
}
}编写代码后,如何有效评估实现与 Spec 之间的一致性?本节介绍 OpenSpec 推荐的验证方法与工具。
建立 Spec ↔ 代码 ↔ 测试 的映射关系,确保每一条 Spec 都有代码落地:
| Spec 定义 (Requirements) | 代码实现 (Implementation) | 验证方式 (Verification) |
|---|---|---|
POST /api/orders |
src/http/server.js (Route Handler) |
Integration Test |
Response 409: Stock insufficient |
catch (e) { if (e.message === 'OUT_OF_STOCK') ... } |
Unit Test (Error Case) |
Order.totalCents (Model) |
src/domain/types.js (Interface) |
TypeScript Compile |
p99 < 100ms (SLO) |
performance.spec.js (Performance Test) |
CI Pipeline |
实践建议:在代码注释中引用对应的 Spec 章节,如 // 对应 Spec: POST /api/orders。
OpenSpec 的 Gherkin 格式场景天然适合转化为测试用例:
Spec 中的场景(specs/api/spec.md):
#### Scenario: 成功创建订单
Given 用户购物车中有商品
And 商品库存充足
When 发送 POST /api/orders 携带 { userId }
Then 返回状态码 201
And 返回新创建的订单 Order
And 购物车被清空
And 库存被扣减对应的集成测试(__tests__/integration.spec.js):
it('完整购物流程', async () => {
// 1. 上架商品 → Given 商品库存充足
const res1 = await fetch(`${base}/api/products`, {...})
assert.strictEqual(res1.status, 201)
// 2. 加购 → Given 用户购物车中有商品
const res2 = await fetch(`${base}/api/cart/items`, {...})
assert.strictEqual(res2.status, 200)
// 3. 下单 → When 发送 POST /api/orders
const res3 = await fetch(`${base}/api/orders`, {...})
assert.strictEqual(res3.status, 201) // Then 返回状态码 201
const order = await res3.json()
assert.strictEqual(order.totalCents, 1000) // Then 返回新创建的订单
assert.strictEqual(order.status, 'PENDING_PAYMENT')
})OpenSpec 不是替代 TDD,而是与 TDD 协同:
传统 TDD: 需求 → 写测试 → 写代码 → 重构
OpenSpec + TDD: 需求 → 写 Spec → 验证 Spec → 写测试(基于 Scenario)→ 写代码 → 重构
关键区别:
- Spec 先于测试:Spec 是人与 AI 的共识,测试是 Spec 的技术实现
- Scenario 直接映射:每个 Gherkin Scenario 可以对应一个测试用例
- AI 可理解:Spec 使用自然语言 + 结构化格式,AI 能直接参与生成测试
在 PR 审查时,检查实现与 Spec 的一致性:
- 每个 Requirement 都有对应的代码实现
- 每个 Scenario 都有对应的测试用例
- 边界场景(如库存不足、订单不存在)都有覆盖
- SLO 指标(如 p99 延迟)有性能测试验证
- 代码注释中引用了对应的 Spec 章节
测试不是事后补充,而是规格的可执行版本。
针对纯函数逻辑,如金额计算、状态机流转。
- Spec: "订单总价等于所有条目单价乘以数量之和"
- Test: 使用 Node.js 原生测试运行器
import { test } from "node:test";
import assert from "node:assert";
import { calculateTotal } from "./logic.js";
test("calculateTotal sums up item prices", () => {
const items = [
{ priceCents: 100, quantity: 2 },
{ priceCents: 50, quantity: 1 },
];
const total = calculateTotal(items);
assert.strictEqual(total, 250);
});模拟真实用户路径,验证模块间协作。
- 流程:注册 -> 登录 -> 浏览 -> 加购 -> 下单 -> 支付。
- 运行方式:
node --test examples/ecommerce-mini/__tests__/integration.spec.js
定义并验证 SLO。
- Spec: "下单接口 p99 < 100ms"
- Test: 脚本并发发送请求,统计延迟分布,若 p99 > 100ms 则测试失败。
- Node.js: 需安装 v20.0.0 或更高版本(使用了
node:test和fetch)。 - Git: 用于版本控制。
- Editor: 推荐使用 VS Code。
本项目无 package.json 依赖(除开发工具外),不仅展示了原生能力,也降低了试运行门槛。
开发版使用内存存储,重启后数据重置。
# 启动服务
node examples/ecommerce-mini/src/http/server.js
# 在另一个终端运行测试套件
node --test examples/ecommerce-mini/__tests__/生产版开启了文件持久化、鉴权与幂等性检查。
# 启动生产服务 (Port 3002)
node examples/ecommerce-mini/src/http/server.prod.js为了演示 OpenSpec 如何应对复杂性,我们在 server.prod.js 中引入了三个高级特性。
Spec 变更: 系统需要即使在重启后也能保留用户数据。
实现:
- 实现
FileStore类,使用fs.writeFileSync原子写入 JSON 文件。 - 启动时从磁盘加载数据到内存 Map。
Spec 变更: 所有非公开接口必须携带 Bearer Token。
实现:
- 增加
POST /api/auth/login接口。 - 使用 HMAC-SHA256 签名生成 JWT(无第三方库实现)。
- 中间件拦截校验
Authorization头。
Spec 变更: 对同一订单的重复支付请求,系统应返回相同结果且不重复扣款。
实现:
- 客户端在 Header 中发送
Idempotency-Key。 - 服务端检查 Key 是否已存在:
- 若存在,直接返回缓存的响应。
- 若不存在,执行业务逻辑并缓存结果。
Spec 变更: 系统需暴露 /metrics 端点供监控采集。
实现:
- 记录每个路由的请求次数与耗时。
- 暴露 JSON 格式指标:
{ "requests": 100, "latencies": { "p99": 12 } }。
通过 ecommerce-mini 案例,我们展示了 OpenSpec 如何贯穿从 "Proposal" 到 "Production" 的全生命周期。
- 文档即设计:结构化的 Spec 澄清了思路。
- 代码即映射:清晰的分层架构使实现变得机械而简单。
- 测试即验收:自动化的脚本保证了重构的信心。
希望本实战指南能帮助你在实际项目中更好地应用 OpenSpec 方法论。