
1. 引言
OpenSpec 不仅仅是一个工具,它代表了一种以规格(Spec)为中心的工程实践与完整的方法论。其核心目标是通过结构化的文档与自动化工作流,来提升复杂系统从设计到交付整个周期的确定性与可验证性。本文将通过构建一个小型电商网站的完整案例,为你演示如何将 OpenSpec 应用于真实项目:从架构设计、模块划分、接口定义,到单元测试、集成测试乃至性能验证的全流程。理解了 OpenSpec 在 系统架构 设计中的价值后,你就能更好地管理复杂项目。
图中的流程仅供参考,更多详细信息可以访问其官方网站:https://openspec.dev/ 。
案例代码仓库:https://github.com/ForceInjection/OpenSpec-practise
2. 方法论与 OpenSpec 核心概念
OpenSpec 远不止一套文档模板,它倡导的是一种 “规格驱动开发(Spec-Driven Development)” 的工程文化。它坚持“规格即源头”,确保代码与测试永远跟设计对齐,从而解决传统开发中“文档永远跟不上代码”的老大难问题。
2.1 核心哲学
OpenSpec 的设计遵循四大原则,让实践过程更灵活、高效:
- 流动而非僵化 (Fluid not rigid):不设强制审批关卡,按实际需求创建和更新文档。
- 迭代而非瀑布 (Iterative not waterfall):在构建过程中不断学习和修正规格,而非一次性定死。
- 简单而非复杂 (Easy not complex):以轻量级的方式启动,避免繁琐的流程拖累团队。
- 存量优先 (Brownfield-first):不仅适用于全新的“绿地项目”,更能通过其 Delta(增量变更)机制,平滑地接入现有的、复杂的代码库。
2.2 目录结构与单一事实来源
为了清晰区分“当前已确定的状态”和“正在进行中的变更”,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。列出具体的实施步骤与验收标准。
当一个变更开发完成并通过验收后,通过归档(Archive)操作,其 Delta Spec 会合并回主 Spec(openspec/specs/),从而形成新的事实来源。
2.3 工作流与 CLI 工具
OpenSpec CLI (openspec) 工具串联了从设计构思到最终交付的完整流程,并且天然支持人类开发者与 AI Agent 的协同工作:
- 初始化 (
init):一键生成标准的 OpenSpec 目录结构,配置 .openspec 环境。
- 浏览与查看 (
list / view):快速检索项目现有的变更与规格,支持 JSON 格式输出,便于 AI 工具解析。
- 校验 (
validate):基于预设的 Schema 检查文档结构的合法性与完整性,确保 Spec 格式严谨无误。
- 归档 (
archive):将已完成的变更移入专门的归档区,保持当前工作区的整洁与聚焦。
2.4 验证与可观测性
- 结构化校验 (Validation)
OpenSpec 引入了如 Zod 这样的校验机制,确保 Spec 文档不仅是纯文本,更是符合 Schema 定义的结构化数据。这为后续自动生成测试用例(Test Case Generation)铺平了道路。
- 遥测 (Telemetry)
工具内置了基于 PostHog 的匿名遥测功能(可选开启),用于收集如 command_executed 这类命令执行数据,帮助团队分析工具的使用频率和识别流程中的瓶颈。其设计严格遵循隐私原则,不收集命令参数、IP地址或任何业务内容,并支持通过设置环境变量 OPENSPEC_TELEMETRY=0 来完全关闭此功能。
3. 迭代流程总览
OpenSpec 的迭代流程紧紧围绕“规格优先”的理念展开,但它摒弃了传统瀑布模型中繁琐的审批环节,倡导灵活、敏捷的协作方式。
3.1 目标与范围 (Proposal)
在动手写第一行代码之前,首先通过 proposal.md 文件明确 Why(为什么做) 和 What(做什么)。
- 业务目标:例如“构建一个最小可用的电商下单流程”。
- 非功能性指标 (SLO):
- p99 接口延迟 < 100ms
- 支持 50 RPS(每秒请求数)的并发
- 使用内存数据存储(演示阶段)
3.2 规格初始化 (Schema)
使用 CLI 工具快速初始化项目结构(如果你直接使用 examples/ecommerce-mini 源码,此步骤已完成,可跳过):
openspec init --tools none
在 openspec/changes/ 目录下创建一个新的变更集(例如 v1-mvp),并准备以下核心文件:
3.2.1 proposal.md (宏观意图)
这是项目的总纲,清晰定义了初衷和成功标准。
# Proposal: Ecommerce Mini MVP
## Intent
构建一个最小可用的电商系统,演示 OpenSpec 端到端流程。
## Goals (SLO)
- **Latency**: 核心接口 p99 < 100ms。
- **Quality**: 核心逻辑测试覆盖率 > 80%。
## Scope
- **In Scope**: Catalog, Cart, Order, User, Memory Storage.
- **Out of Scope**: Search, Recommendation, Payment Gateway.
3.2.2 其他核心文件
design.md: 系统设计草案,包含架构图和数据流。
specs/: 具体的接口定义与数据模型规格。
tasks.md: 拆解后的具体开发任务清单。
3.3 架构与系统设计 (Design)
基于 Proposal 定下的基调,在 design.md 中进一步确定技术方案:
- 边界:明确 Catalog(商品)、Cart(购物车)、Order(订单)、User(用户)等各个模块的职责与界限。
- 数据流:绘制关键业务路径,如“用户浏览 -> 添加购物车 -> 提交订单 -> 模拟支付”。
- 接口:定义 RESTful API 的 URL 结构、HTTP 方法及请求响应格式。
3.4 规范驱动实现 (Implementation)
这是 OpenSpec 理念的实践核心——代码是对规格的精确映射。
- 领域层 (
domain/):直接实现 Spec 中定义的数据模型和业务规则。
- 接口层 (
http/):直接实现 Spec 中定义的 API 端点。
- 测试层 (
__tests__/):直接验证 Spec 中列出的验收场景(Scenarios)。
3.5 验证与度量 (Verification)
- 自动化测试:运行单元测试与集成测试,确保每一行实现都符合 Spec 的预期。
- 基线度量:在开发阶段就运行性能基准测试(例如
performance.spec.js),确保系统满足既定的 SLO(服务水平目标)。
3.6 归档与沉淀 (Archive)
当 v1-mvp 变更开发完成并通过所有验收后,运行 openspec archive 命令。这个操作会将变更集中的 Spec 修改(Deltas)合并到主分支(openspec/specs/),使其成为系统最新的、单一的事实来源。
4. 案例背景:小型电商网站
4.1 核心域与上下文
本案例将构建一个名为 ecommerce-mini 的微型电商系统,它包含五个核心业务上下文:
- Catalog (商品): 负责管理商品信息与库存。
- User (用户): 处理用户身份识别与认证。
- Cart (购物车): 临时存放用户打算购买的商品。
- Order (订单): 交易的核心单据,管理订单状态的完整流转。
- Payment (支付): 模拟资金结算流程。
4.2 简化假设 (Constraints)
为了将焦点完全放在 OpenSpec 的流程演示上,本项目在工程实现上做了一些折中和简化:
- 数据存储:仅使用内存 Map 或本地 JSON 文件,不引入外部数据库。
- 单体架构:所有业务模块运行在同一个 Node.js 进程中,通过模块导入方式进行通信。
- 环境依赖:核心部分仅依赖 Node.js (v20+),实现零 npm 生产依赖(生产级扩展特性除外)。
4.3 非功能性目标 (SLO)
- 延迟:核心 API (如 GET /products, POST /orders) 的 p99 延迟 < 100ms。
- 可靠性:订单数据在服务重启后不应丢失(这需要通过持久化扩展来实现)。
- 质量:核心业务逻辑的代码测试覆盖率 > 80%。
5. 架构设计
5.1 分层架构
项目采用经典的四层架构,确保清晰的关注点分离:
| 层级 |
目录 (src/) |
职责 |
依赖方向 |
| 接口层 |
http/ |
处理 HTTP 请求/响应,参数解析,鉴权,响应格式化 |
-> Application |
| 应用层 |
services/ |
用例编排(Orchestration),例如“下单”涉及扣库存、清购物车等多个步骤 |
-> Domain, Repo |
| 领域层 |
domain/ |
纯净的业务实体(定义在 types.ts)与业务逻辑,无任何外部依赖 |
None |
| 基础设施 |
repo/, persist/ |
数据持久化的具体实现(内存/文件) |
Implementation Detail |
5.2 边界与依赖规则
- 严格单向依赖:HTTP 层 -> Service 层 -> Domain 层。
- 依赖倒置:Service 层定义 Repository 接口,由 Infrastructure 层提供具体实现(本示例为简化直接调用)。
- 数据隔离:模块间不允许直接访问对方的数据存储,必须通过公开的 Service 接口进行调用。
5.3 数据流概览
以“用户下单”这个核心场景为例,描述其数据流转过程:
- User 发起
POST /api/orders 请求。
- HTTP Layer 解析请求头中的 Token,验证用户身份。
- Order Service 接收请求并协调业务:
- 调用 Cart Service 获取当前用户的购物车商品列表。
- 调用 Catalog Service 原子性地扣减相应商品的库存(这里是事务一致性的边界)。
- 计算订单总价,生成订单实体。
- Order Repo 将新生成的订单数据保存至存储。
- HTTP Layer 返回
201 Created 状态码及订单详情。
6. 系统设计
6.1 接口协议
采用广泛使用的 RESTful JSON 风格。
- URL 规范:资源使用复数形式,例如
/api/products。
- 状态码:
200 OK: 查询或修改成功。
201 Created: 资源创建成功。
400 Bad Request: 业务校验失败(如参数错误)。
401 Unauthorized: 用户未登录或 Token 无效。
409 Conflict: 资源状态冲突(如重复下单、库存不足)。
6.2 数据模型
在 OpenSpec 实践中,我们首先在 specs/domain/spec.md 中定义模型。为了方便理解,这里使用 TypeScript Interface 语法作为通用描述语言:
Spec 定义 (specs/domain/spec.md):
interface Product {
id: string; // 格式:prod_xxxx
name: string;
priceCents: number; // Integer, min 0
stock: number; // Integer, min 0
}
代码实现 (src/domain/types.js / JSDoc):
由于本项目使用原生 JavaScript,我们通过 JSDoc 注释来实现对 Spec 的映射:
/**
* @typedef {Object} Product
* @property {string} id
* @property {string} name
* @property {number} priceCents
* @property {number} stock
*/
6.3 错误处理
定义统一的错误响应结构,便于前端进行标准化处理:
{
"code": "OUT_OF_STOCK",
"message": "商品 [prod_123] 库存不足"
}
7. 模块详细设计
7.1 Catalog (商品域)
- Capabilities:
listProducts(): 查询所有商品(演示阶段不做分页)。
getProduct(id): 根据ID查询商品详情。
deductStock(id, qty): 原子性扣减库存,需处理并发竞争(演示中简化为单线程锁)。
7.2 Cart (购物车域)
- Capabilities:
addToCart(userId, item): 向指定用户的购物车中添加商品(增量更新)。
clearCart(userId): 用户下单后清空其购物车。
- Storage: 使用内存 Map,Key 为
userId,Value 为 Cart 对象。
7.3 Order (订单域)
- Capabilities:
createOrder(userId): 核心复杂逻辑,协调 Cart 与 Catalog 服务,完成下单。
payOrder(orderId): 处理支付,将订单状态从 PENDING 流转至 PAID。
8. 接口设计 (Spec)
OpenSpec 的一大优势是使用 Markdown 编写兼具极高可读性与结构化的规格文档。以下是 openspec/specs/api/spec.md 文件的一个真实片段:
# API Specification
## Endpoints
### GET /api/products
获取所有商品列表。
- **Response 200**: `Product[]`
### POST /api/cart/items
添加商品到购物车。
- **Body**: `{ productId: string, quantity: number }`
- **Response 200**: Updated `Cart`
### POST /api/orders
结算购物车生成订单。
- **Body**: `{ userId: string }`
- **Response 201**: `Order`
- **Response 409**: Stock insufficient (库存不足)
注意:这里的 Product[] 和 Order 引用了 domain/spec.md 中定义的数据模型,确保了整个规格定义的一致性。
9. 规范驱动实现 (Implementation)
这是 OpenSpec 理念落地的核心环节——编写代码的过程,就是对照规格进行精确映射的过程。开发者(或 AI)在编码时应始终打开相关的 Spec 文件作为唯一参考。
9.1 追踪矩阵 (Traceability)
我们可以建立如下映射关系,确保每一条 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 / JSDoc Check |
p99 < 100ms (SLO) |
performance.spec.js (Performance Test) |
CI Pipeline |
9.2 目录结构映射
examples/
├── openspec/ <-- 对应 Spec Source of Truth (共享规格)
│ └── changes/v1-mvp/...
├── 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
9.3 代码实现示例
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;
}
}
10. 测试设计:验证规格
在 OpenSpec 的实践中,测试不是事后的补充,而是规格的可执行版本,是验证实现是否符合设计的最终手段。
10.1 单元测试 (src/domain/logic.spec.js)
针对领域层中的纯函数逻辑进行测试,例如金额计算、状态机流转。
- 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);
});
10.2 集成测试 (integration.spec.js)
模拟真实用户的完整操作路径,验证多个模块间的协同工作是否正常。
- 流程:模拟用户注册 -> 登录 -> 浏览商品 -> 加入购物车 -> 提交订单 -> 支付。
- 运行方式:
node --test examples/ecommerce-mini/__tests__/integration.spec.js
定义并验证在“系统设计”阶段提出的 SLO(服务水平目标)。
- Spec: “下单接口的 p99 延迟应小于 100ms”
- Test: 编写脚本并发发送大量请求,统计响应时间的分布情况。如果 p99 值大于 100ms,则测试标记为失败。
11. 示例代码操作手册
11.1 准备环境
- Node.js: 需要安装 v20.0.0 或更高版本(因为使用了
node:test 和 fetch 等新特性)。
- Git: 用于克隆代码仓库和版本控制。
- Editor: 推荐使用 VS Code 等现代化代码编辑器。
本项目核心部分没有 package.json 依赖(除少数开发工具外),这不仅展示了 Node.js 的原生能力,也极大降低了运行和体验的门槛。
11.2 运行开发版服务
开发版使用内存存储,服务重启后所有数据将重置。
# 启动服务
node examples/ecommerce-mini/src/http/server.js
# 在另一个终端运行完整的测试套件
node --test examples/ecommerce-mini/__tests__/
11.3 运行生产版服务
生产版开启了文件持久化、JWT鉴权与幂等性检查等高级特性。
# 启动生产服务 (默认端口 3002)
node examples/ecommerce-mini/src/http/server.prod.js
12. 生产级扩展实践
为了演示 OpenSpec 如何优雅地应对真实世界的复杂性,我们在 server.prod.js 中引入了三个常见的高级特性。
12.1 持久化存储 (src/persist/fileStore.js)
- Spec 变更: 系统需要在服务重启后仍能保留用户和订单数据。
- 实现:
- 实现
FileStore 类,使用 fs.writeFileSync 进行原子化的 JSON 文件写入。
- 服务启动时,从磁盘加载数据到内存 Map 中。
12.2 鉴权与安全
- Spec 变更: 所有非公开的 API 接口必须携带有效的 Bearer Token 才能访问。
- 实现:
- 新增
POST /api/auth/login 登录接口。
- 使用 HMAC-SHA256 签名手动生成 JWT(演示无第三方库的实现)。
- 在 HTTP 层添加中间件,拦截并校验
Authorization 请求头。
12.3 幂等性 (Idempotency)
- Spec 变更: 对于同一订单的重复支付请求,系统应返回完全相同的结果,且不产生重复扣款等副作用。
- 实现:
- 客户端需要在请求头中发送一个唯一的
Idempotency-Key。
- 服务端检查此 Key 是否已处理过:
- 若存在,直接返回之前缓存的响应结果。
- 若不存在,则执行业务逻辑,并将处理结果与此 Key 关联缓存起来。
12.4 可观测性
- Spec 变更: 系统需要暴露一个
GET /metrics 端点,供监控系统(如 Prometheus)采集指标。
- 实现:
- 在内存中记录每个 API 路由的请求次数与处理耗时。
- 将指标以 JSON 格式暴露,例如:
{ "requests": { "/api/orders": 100 }, "latencies": { "p99": 12 } }。
13. 结语
通过 ecommerce-mini 这个完整的案例,我们展示了 OpenSpec 如何贯穿一个项目从最初的“提案(Proposal)”到具备生产级特性的“交付(Production)”的全生命周期。
- 文档即设计:结构化的 Spec 在动工前就澄清了思路,统一了认知。
- 代码即映射:清晰的分层架构和“规格驱动”的理念,使得编码过程变得更具确定性。
- 测试即验收:自动化、多层次的测试脚本为持续重构和演进提供了坚实信心。
希望这份实战指南能成为你在实际项目中应用 OpenSpec 方法论的得力助手。如果你对这类规范驱动开发或全栈实践有更多想法,欢迎到 云栈社区 与更多开发者交流探讨。