名称: 合约优先设计 描述: 在实现前使用OpenAPI和AsyncAPI规范设计和管服API契约,用于合约优先开发 允许工具: 读取、写入、编辑、Glob、Grep、Bash
合约优先设计技能
何时使用此技能
使用此技能当:
- 合约优先设计任务 - 使用openapi和asyncapi规范在实现前设计和管服api契约进行合约优先开发
- 规划或设计 - 需要合约优先设计方法的指导
- 最佳实践 - 想要遵循既定模式和标准
概述
应用合约优先开发方法论于API,确保规范驱动实现。
合约优先方法论
核心原则
合约优先原则:
设计在代码前:
描述: "API规范先于实现"
好处:
- "从消费者处获得早期反馈"
- "启用并行开发"
- "清晰的测试契约"
- "从一开始就有文档"
规范作为真相来源:
描述: "规范是权威的,代码符合它"
强制执行:
- "从规范生成代码"
- "根据规范验证实现"
- "CI/CD门控基于规范合规性"
消费者中心:
描述: "为消费者需求设计,而非提供者便利"
实践:
- "在设计评审中涉及消费者"
- "消费者驱动的契约测试"
- "收集真实世界使用模式"
进化优于革命:
描述: "在不破坏消费者的情况下进化契约"
实践:
- "语义版本控制"
- "默认向后兼容性"
- "移除前先弃用"
开发工作流
合约优先工作流:
阶段:
1_设计:
活动:
- "识别API消费者和用例"
- "定义资源和操作"
- "起草规范(OpenAPI/AsyncAPI)"
- "与利益相关者评审"
产出物:
- "草案API规范"
- "用例文档"
门控: "规范被消费者批准"
2_验证:
活动:
- "对规范进行风格/标准检查"
- "检查向后兼容性"
- "生成模拟服务器"
- "使用模拟进行消费者验收测试"
产出物:
- "检查报告"
- "兼容性报告"
- "模拟服务器配置"
门控: "消费者对模拟进行了验证"
3_实现:
活动:
- "生成服务器存根"
- "实现业务逻辑"
- "针对规范的契约测试"
- "集成测试"
产出物:
- "生成的代码"
- "契约测试结果"
门控: "实现通过契约测试"
4_发布:
活动:
- "发布规范到目录"
- "生成文档"
- "更新变更日志"
- "通知消费者"
产出物:
- "发布的规范"
- "API文档门户"
- "变更日志条目"
门控: "文档上线,消费者已通知"
5_操作:
活动:
- "监控API使用"
- "收集消费者反馈"
- "跟踪破坏性变更请求"
- "规划下一个版本"
产出物:
- "使用指标"
- "反馈日志"
- "弃用计划"
契约管理
规范组织
规范组织:
目录结构:
推荐:
规范/
openapi/
订单服务.yaml
客户服务.yaml
库存服务.yaml
asyncapi/
订单事件.yaml
库存事件.yaml
共享/
模式/
公共类型.yaml
错误响应.yaml
参数/
分页.yaml
安全/
认证方案.yaml
文件命名:
模式: "{服务名称}.yaml"
版本化: "{服务名称}-v{主版本}.yaml"
模块化规范:
描述: "将大型规范拆分为组件"
方法:
主文件: "定义路径,引用组件"
组件目录: "可重用的模式、参数、响应"
共享目录: "跨API共享定义"
示例主文件:
openapi: "3.1.0"
信息:
标题: "订单服务API"
版本: "1.0.0"
路径:
$ref: "./路径/订单.yaml"
组件:
模式:
$ref: "./模式/_索引.yaml"
版本管理
版本管理:
语义版本控制:
主版本: "破坏性变更"
次版本: "向后兼容的添加"
补丁版本: "向后兼容的修复"
规范中的版本:
位置: "信息.版本"
格式: "主版本.次版本.补丁版本"
API版本控制策略:
URL路径:
规范示例:
服务器:
- url: "https://api.example.com/v1"
变更方法: "新规范文件用于主版本"
头部:
规范示例:
参数:
API版本:
in: header
required: false
schema:
type: string
default: "2025-01-01"
变更日志要求:
位置: "CHANGELOG.md与规范一起"
格式: "保持变更日志"
内容:
- "版本号和日期"
- "添加:新端点/字段"
- "更改:修改行为"
- "弃用:标记为移除"
- "移除:破坏性删除"
- "修复:错误修复"
- "安全:漏洞补丁"
破坏性变更检测
破坏性变更:
定义: "可能破坏现有消费者的变更"
openapi破坏性:
移除:
- "移除端点"
- "移除必需的响应字段"
- "移除枚举值"
- "移除支持的内容类型"
修改:
- "更改字段类型"
- "添加必需的请求字段"
- "缩小验证(更小的最大值,更大的最小值)"
- "更改认证要求"
重命名:
- "重命名字段(等同于移除+添加)"
- "更改端点路径"
asyncapi破坏性:
移除:
- "移除通道"
- "移除消息类型"
- "移除必需的负载字段"
修改:
- "不兼容地更改负载模式"
- "更改通道地址格式"
- "修改必需的头部"
检测工具:
openapi:
- "openapi-diff"
- "oasdiff"
- "speccy"
asyncapi:
- "asyncapi/diff"
CI集成:
脚本: |
# 将当前规范与主分支比较
oasdiff breaking main.yaml current.yaml
if [ $? -ne 0 ]; then
echo "检测到破坏性变更!"
exit 1
fi
C# 契约管理模型
namespace SpecDrivenDevelopment.ContractFirst;
/// <summary>
/// 表示API契约生命周期状态
/// </summary>
public enum ContractStatus
{
Draft,
InReview,
Approved,
Implementing,
Published,
Deprecated,
Retired
}
/// <summary>
/// API契约元数据
/// </summary>
public record ApiContract
{
public required string Id { get; init; }
public required string Name { get; init; }
public required string Version { get; init; }
public required ContractType Type { get; init; }
public required ContractStatus Status { get; init; }
public required string SpecificationPath { get; init; }
public string? Description { get; init; }
public List<string> Owners { get; init; } = [];
public List<string> Consumers { get; init; } = [];
public DateTimeOffset CreatedAt { get; init; }
public DateTimeOffset? PublishedAt { get; init; }
public DateTimeOffset? DeprecatedAt { get; init; }
public DateTimeOffset? SunsetAt { get; init; }
}
public enum ContractType
{
OpenApi,
AsyncApi,
GraphQL,
gRPC
}
/// <summary>
/// 跟踪契约版本间的变更
/// </summary>
public record ContractChange
{
public required string ContractId { get; init; }
public required string FromVersion { get; init; }
public required string ToVersion { get; init; }
public required ChangeType Type { get; init; }
public required BreakingLevel Breaking { get; init; }
public required string Path { get; init; }
public required string Description { get; init; }
public string? MigrationGuide { get; init; }
}
public enum ChangeType
{
Added,
Modified,
Deprecated,
Removed
}
public enum BreakingLevel
{
None,
Minor, // 向后兼容
Major // 破坏性变更
}
/// <summary>
/// 契约验证结果
/// </summary>
public record ContractValidationResult
{
public required bool IsValid { get; init; }
public List<ValidationIssue> Issues { get; init; } = [];
public List<ContractChange> Changes { get; init; } = [];
public bool HasBreakingChanges => Changes.Any(c => c.Breaking == BreakingLevel.Major);
}
public record ValidationIssue
{
public required ValidationSeverity Severity { get; init; }
public required string Code { get; init; }
public required string Message { get; init; }
public required string Path { get; init; }
public string? Suggestion { get; init; }
}
public enum ValidationSeverity
{
Error,
Warning,
Info
}
/// <summary>
/// 消费者契约注册
/// </summary>
public record ConsumerContract
{
public required string ConsumerId { get; init; }
public required string ConsumerName { get; init; }
public required string ProviderId { get; init; }
public required string ProviderVersion { get; init; }
public List<string> UsedEndpoints { get; init; } = [];
public List<string> UsedSchemas { get; init; } = [];
public DateTimeOffset RegisteredAt { get; init; }
public DateTimeOffset? LastVerifiedAt { get; init; }
}
/// <summary>
/// 契约管理服务
/// </summary>
public interface IContractService
{
Task<ApiContract> CreateContractAsync(CreateContractRequest request);
Task<ApiContract> GetContractAsync(string id);
Task<IReadOnlyList<ApiContract>> ListContractsAsync(ContractFilter? filter = null);
Task<ContractValidationResult> ValidateContractAsync(string id);
Task<ContractValidationResult> CompareVersionsAsync(string id, string fromVersion, string toVersion);
Task PublishContractAsync(string id);
Task DeprecateContractAsync(string id, DateTimeOffset sunsetDate);
Task RegisterConsumerAsync(string contractId, ConsumerContract consumer);
Task<IReadOnlyList<ConsumerContract>> GetConsumersAsync(string contractId);
}
public record CreateContractRequest
{
public required string Name { get; init; }
public required ContractType Type { get; init; }
public required string SpecificationContent { get; init; }
public string? Description { get; init; }
public List<string> Owners { get; init; } = [];
}
public record ContractFilter
{
public ContractType? Type { get; init; }
public ContractStatus? Status { get; init; }
public string? Owner { get; init; }
public string? Consumer { get; init; }
}
契约测试
提供者验证
提供者验证:
描述: "验证实现是否匹配规范"
方法:
模式验证:
描述: "验证请求/响应是否符合规范"
工具:
- "express-openapi-validator"
- "NSwag中间件"
- "Spectral"
契约测试:
描述: "测试端点是否与规范完全匹配"
工具:
- "Dredd"
- "Schemathesis"
- "Prism"
dotnet示例:
集成测试: |
[Fact]
public async Task GetOrder_ReturnsValidResponse()
{
// 安排
var spec = await OpenApiDocument.FromFileAsync("规范/订单服务.yaml");
var validator = new OpenApiValidator(spec);
// 行动
var response = await _client.GetAsync("/orders/123");
var body = await response.Content.ReadAsStringAsync();
// 断言
var result = validator.ValidateResponse(
"/orders/{orderId}",
"get",
(int)response.StatusCode,
body);
Assert.True(result.IsValid, result.ErrorMessage);
}
CI流水线:
步骤:
- "加载OpenAPI/AsyncAPI规范"
- "启动被测应用"
- "对所有端点运行契约测试"
- "如果任何契约违规,构建失败"
消费者驱动契约
消费者驱动契约:
描述: "消费者定义预期的提供者行为"
工作流:
1_消费者定义:
行动: "消费者创建契约包含预期交互"
产出物: "Pact文件或类似契约"
2_提供者验证:
行动: "提供者运行消费者契约"
验证: "提供者能否满足所有消费者期望"
3_发布:
行动: "发布已验证契约到代理"
启用: "可以部署检查"
Pact示例:
消费者测试: |
[Fact]
public async Task GetOrder_ExpectedBehavior()
{
var pact = Pact.V3("订单消费者", "订单提供者");
pact.Given("订单123存在")
.UponReceiving("对订单123的请求")
.WithRequest(HttpMethod.Get, "/orders/123")
.WillRespond()
.WithStatus(200)
.WithJsonBody(new
{
id = "123",
status = Match.Type("pending"),
totalAmount = Match.Decimal(99.99m)
});
await pact.VerifyAsync(async ctx =>
{
var client = new OrderClient(ctx.MockServerUri);
var order = await client.GetOrderAsync("123");
Assert.NotNull(order);
});
}
asyncapi契约:
方法: "消息模式契约"
验证:
- "生产者发布有效消息"
- "消费者能反序列化所有消息版本"
- "模式注册强制执行兼容性"
API治理
风格指南
API风格指南:
命名:
资源:
- "使用复数名词(用户、订单、产品)"
- "多词使用连字符(订单项)"
- "避免在资源名中使用动词"
操作:
- "operationId: 骆驼命名法(getUser、createOrder)"
- "跨API一致的动词使用"
字段:
- "JSON属性使用骆驼命名法"
- "一致的日期格式(ISO 8601)"
- "使用标准字段名(id、createdAt、updatedAt)"
结构:
版本控制: "URL路径版本控制(/v1/)"
分页: "基于游标或偏移,一致的方法"
错误: "RFC 7807问题详情"
过滤: "查询参数,命名一致"
安全:
认证: "OAuth 2.0 / API密钥作为标准"
授权: "在规范中记录范围"
敏感数据: "绝不在URL参数中"
文档:
必需:
- "每个操作都有摘要"
- "复杂操作有描述"
- "请求/响应体示例"
- "错误响应文档"
自动化强制执行
自动化强制执行:
检查:
工具:
- "Spectral (OpenAPI)"
- "AsyncAPI Studio"
- "Redocly CLI"
spectral规则: |
extends: ["spectral:oas", "spectral:asyncapi"]
rules:
operation-operationId:
severity: error
operation-summary:
severity: error
operation-tags:
severity: warn
info-contact:
severity: error
response-error-format:
description: "错误必须使用RFC 7807"
severity: error
given: "$.paths.*.*.responses[?(@property >= '400')]"
then:
field: content.application/problem+json
function: truthy
破坏性变更检测:
CI步骤: |
- name: 检查破坏性变更
run: |
oasdiff breaking \
--base main:规范/api.yaml \
--revision HEAD:规范/api.yaml \
--fail-on ERR
预提交钩子:
配置: |
repos:
- repo: local
hooks:
- id: lint-openapi
name: Lint OpenAPI
entry: spectral lint 规范/**/*.yaml
language: node
types: [yaml]
API目录
API目录:
目的: "所有API契约的中央注册表"
功能:
发现: "按名称、域、能力查找API"
文档: "从规范自动生成"
版本控制: "跟踪所有版本和变更"
依赖关系: "消费者/提供者关系"
指标: "使用、错误、延迟"
每个API的元数据:
身份:
- "名称和描述"
- "所有者团队"
- "域/能力"
生命周期:
- "当前版本"
- "状态(草案/发布/弃用)"
- "如果弃用,日落日期"
消费者:
- "注册的消费者"
- "使用统计"
质量:
- "文档评分"
- "测试覆盖率"
- "破坏性变更历史"
集成:
规范来源: "Git仓库"
CI/CD: "合并到主分支时发布"
门户: "开发者门户生成"
验证清单
合约优先清单:
预设计:
- "消费者用例已记录"
- "资源模型已定义"
- "认证/授权策略已选择"
- "版本控制策略已同意"
规范:
- "有效的OpenAPI/AsyncAPI语法"
- "通过检查规则"
- "所有操作都有operationId"
- "为复杂类型提供示例"
- "错误响应已文档化"
- "安全方案已定义"
评审:
- "消费者代表已评审"
- "无不必要的破坏性变更"
- "向后兼容性已验证"
- "文档完整"
实现:
- "使用生成的存根"
- "契约测试通过"
- "启用响应验证"
- "模式变更先通过规范"
发布:
- "变更日志已更新"
- "目录条目已创建"
- "消费者已通知"
- "如果适用,弃用警告"
参考
- 相关技能:
openapi-authoring- OpenAPI规范编写 - 相关技能:
asyncapi-authoring- AsyncAPI规范编写 - 相关技能:
gherkin-authoring- Gherkin格式的验收标准
最后更新: 2025-12-26