mcpField
A
api-design

api-design

github.com/aaronontheweb开发1.2k673 次安装3 天前更新
代码仓库

关于 api-design

面向 .NET 库开发者的公共 API 设计与兼容性技能,涵盖源代码、二进制和网络格式兼容。提供 API 变更审查、版本策略及自动化表面测试的指导。

主要功能

  1. 01源代码、二进制与网络兼容原则
  2. 02只扩展 API 的设计策略
  3. 03弃用与语义化版本指南
  4. 04使用 Verify 审批 API 表面
  5. 05演进序列化格式的方法

适用场景

  1. 01规划 NuGet 包的公共 API
  2. 02评估现有 API 变更是否破坏兼容
  3. 03为分布式系统安排网络格式升级
  4. 04在 PR 中审核 API 变更

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/aaronontheweb/dotnet-skills --skill api-design

安装后在 Claude Code 中输入/api-design确认已加载。

详细介绍

公共 API 设计与兼容性

何时使用此技能

在以下情况下使用此技能:

  • 为 NuGet 包或库设计公共 API
  • 修改现有公共 API
  • 规划分布式系统的线路格式变更
  • 实施版本控制策略
  • 审查可能引入破坏性变更的拉取请求

三种兼容性类型

类型定义范围
API/源代码代码可针对较新版本编译公共方法签名、类型
二进制已编译代码可在较新版本上运行程序集布局、方法标记
线路其他版本可读取序列化数据网络协议、持久化格式

破坏其中任何一种兼容性,都会增加用户升级的阻力。


仅扩展设计

稳定 API 的基础:只扩展,绝不删除或修改。

三大支柱
  1. 先前功能不可变 - 发布后,行为和签名即固定
  2. 通过新构造添加新功能 - 添加重载、新类型、可选择启用的功能
  3. 仅在弃用期结束后移除 - 以年为单位,而非以发布次数为单位
优点
  • 旧代码在新版本中继续可用
  • 新旧路径并存
  • 默认情况下,升级不会造成破坏
  • 用户可按自己的计划升级

资源:


API 变更指南

安全变更(任何版本均可)
// SAFE: Add NEW overload methods that delegate to existing methods
// Existing method - do not modify its signature
public void Process(Order order) { ... }
// New overload - safe to add
public void Process(Order order, CancellationToken ct)
{
    // implementation that handles cancellation
}

// SAFE: Add NEW overloads for additional functionality
// Existing method - do not modify
public void Send(Message msg) { ... }
// New overload - safe to add
public void Send(Message msg, Priority priority)
{
    // implementation that handles priority
}

// ADD new types, interfaces, enums
public interface IOrderValidator { }
public enum OrderStatus { Pending, Complete, Cancelled }

// ADD new members to existing types
public class Order
{
    public DateTimeOffset? ShippedAt { get; init; }  // NEW
}
不安全变更(绝不允许,或仅限主版本)
// REMOVE or RENAME public members
public void ProcessOrder(Order order);  // Was: Process()

// CHANGE parameter types or order
public void Process(int orderId);  // Was: Process(Order order)

// CHANGE return types
public Order? GetOrder(string id);  // Was: public Order GetOrder()

// CHANGE access modifiers
internal class OrderProcessor { }  // Was: public

// ADD optional parameters to EXISTING methods (binary incompatible!)
// The compiled IL method signature changes - callers compiled against
// the old signature will get MissingMethodException at runtime.
// Optional parameter defaults are baked into the CALLER's assembly at compile time.
public void Process(Order order, CancellationToken ct = default);  // Breaks binary compat!
public void Send(Message msg, Priority priority = Priority.Normal);  // Breaks binary compat!
// Correct approach: add a NEW overload method instead (see Safe Changes above)

// ADD required parameters without defaults
public void Process(Order order, ILogger logger);  // Breaks callers!
弃用模式
// Step 1: Mark as obsolete with version (any release)
[Obsolete("Obsolete since v1.5.0. Use ProcessAsync instead.")]
public void Process(Order order) { }

// Step 2: Add new recommended API (same release)
public Task ProcessAsync(Order order, CancellationToken ct = default);

// Step 3: Remove in next major version (v2.0+)
// Only after users have had time to migrate

API 审批测试

通过自动化 API 表面测试,防止意外的破坏性变更。

使用 ApiApprover + Verify
dotnet add package PublicApiGenerator
dotnet add package Verify.Xunit
[Fact]
public Task ApprovePublicApi()
{
    var api = typeof(MyLibrary.PublicClass).Assembly.GeneratePublicApi();
    return Verify(api);
}

生成 ApprovePublicApi.verified.txt:

namespace MyLibrary
{
    public class OrderProcessor
    {
        public OrderProcessor() { }
        public void Process(Order order) { }
        public Task ProcessAsync(Order order, CancellationToken ct = default) { }
    }
}

任何 API 变更都会导致测试失败 - 审查者必须明确批准变更。

PR 审查流程
  1. PR 包含对 *.verified.txt 文件的变更
  2. 审查者可在差异中看到精确的 API 表面变更
  3. 破坏性变更会立即显现
  4. 必须经过审慎决策才能批准

线路兼容性

对于分布式系统,序列化数据必须能跨版本读取。

要求
方向要求
向后兼容旧写入方 → 新读取方(当前版本读取旧数据)
向前兼容新写入方 → 旧读取方(旧版本读取新数据)

要实现零停机滚动升级,两者缺一不可。

安全演进线路格式

阶段 1:添加读取端支持(选择启用)

// New message type - readers deployed first
public sealed record HeartbeatV2(
    Address From,
    long SequenceNr,
    long CreationTimeMs);  // NEW field

// Deserializer handles both old and new
public object Deserialize(byte[] data, string manifest) => manifest switch
{
    "Heartbeat" => DeserializeHeartbeatV1(data),   // Old format
    "HeartbeatV2" => DeserializeHeartbeatV2(data), // New format
    _ => throw new NotSupportedException()
};

阶段 2:启用写入端(选择退出,下一个次版本)

// Config to enable new format (off by default initially)
akka.cluster.use-heartbeat-v2 = on

阶段 3:设为默认(未来版本)

待已安装版本群体都具备读取端代码后。

基于架构的序列化

优先选择基于架构的格式,而非基于反射的格式:

格式类型线路兼容性
Protocol Buffers基于架构极佳 - 字段编号明确
MessagePack基于架构良好 - 需使用契约
System.Text.Json基于架构(使用源生成器)良好 - 属性明确
Newtonsoft.Json基于反射较差 - 负载中包含类型名称
BinaryFormatter基于反射极差 - 绝不要使用

详情请参阅 dotnet/serialization 技能。


封装模式

内部 API

明确标记非公共 API:

// Attribute for documentation
[InternalApi]
public class ActorSystemImpl { }

// Namespace convention
namespace MyLibrary.Internal
{
    public class InternalHelper { }  // Public for extensibility, not for users
}

清晰地说明:

.Internal 命名空间中的类型或标记为 [InternalApi] 的类型,可能会在任何版本中随时变更,恕不另行通知。

密封类
// DO: Seal classes not designed for inheritance
public sealed class OrderProcessor { }

// DON'T: Leave unsealed by accident
public class OrderProcessor { }  // Users might inherit, blocking changes
接口隔离
// DO: Small, focused interfaces
public interface IOrderReader
{
    Order? GetById(OrderId id);
}

public interface IOrderWriter
{
    Task SaveAsync(Order order);
}

// DON'T: Monolithic interfaces (can't add methods without breaking)
public interface IOrderRepository
{
    Order? GetById(OrderId id);
    Task SaveAsync(Order order);
    // Adding new methods breaks all implementations!
}

版本控制策略

语义化版本控制(实用版)
版本允许的变更
补丁(1.0.x)Bug 修复、安全补丁
次版本(1.x.0)新功能、弃用、移除已过时内容
主版本(x.0.0)破坏性变更、移除旧 API
核心原则
  1. 不会造成意外破坏 — 即使是主要版本,也应提前公告并规划
  2. 随时扩展 — 可在任何版本中发布新 API
  3. 先弃用,再移除 — 至少在一个次要版本中使用 [Obsolete]
  4. 明确沟通时间表 — 用户需要规划升级
切斯特顿围栏

在移除或更改某项内容之前,先弄清楚它存在的原因。

假设每个公共 API 都有人在使用。如果你想更改它:

  1. 在 GitHub 上公开讨论提案
  2. 编写迁移路径文档
  3. 提供弃用期
  4. 在计划的版本中发布

拉取请求检查清单

审核涉及公共 API 的 PR 时:

  • 不移除公共成员(改用 [Obsolete])
  • 不更改签名(改为新增重载)
  • 不新增必需参数(使用默认值)
  • 更新 API 审批测试(审核 .verified.txt 的更改)
  • 线格式更改设为可选(先支持读取)
  • 记录破坏性更改(发行说明、迁移指南)

反模式

伪装成修复的破坏性更改
// "Bug fix" that breaks users
public async Task<Order> GetOrderAsync(OrderId id)  // Was sync!
{
    // "Fixed" to be async - but breaks all callers
}

// Correct: Add new method, deprecate old
[Obsolete("Use GetOrderAsync instead")]
public Order GetOrder(OrderId id) => GetOrderAsync(id).Result;

public async Task<Order> GetOrderAsync(OrderId id) { }
静默更改行为
// Changing defaults breaks users who relied on old behavior
public void Configure(bool enableCaching = true)  // Was: false!

// Correct: New parameter with new name
public void Configure(
    bool enableCaching = false,  // Original default preserved
    bool enableNewCaching = true)  // New behavior opt-in
多态序列化
// AVOID: Type names in wire format
{ "$type": "MyApp.Order, MyApp", "Id": 123 }

// Renaming Order class = wire break!

// PREFER: Explicit discriminators
{ "type": "order", "id": 123 }

资源

评论

支持 Markdown