合约优先设计Skill contract-first-design

合约优先设计技能用于在API开发中,先使用OpenAPI和AsyncAPI规范设计和制定API契约,然后再进行实现。这确保了API的标准化、可维护性和消费者友好性。关键词:API设计、契约优先、OpenAPI、AsyncAPI、API开发、软件架构。

架构设计 0 次安装 0 次浏览 更新于 3/11/2026

名称: 合约优先设计 描述: 在实现前使用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