api-design
- 作者仓库星标 0
- 作者更新于 2026年8月8日 02:11
- 作者仓库 dotnet-skills
Public API Design and Compatibility
When to Use This Skill
Use this skill when:
- Designing public APIs for NuGet packages or libraries
- Making changes to existing public APIs
- Planning wire format changes for distributed systems
- Implementing versioning strategies
- Reviewing pull requests for breaking changes
The Three Types of Compatibility
| Type | Definition | Scope |
|---|---|---|
| API/Source | Code compiles against newer version | Public method signatures, types |
| Binary | Compiled code runs against newer version | Assembly layout, method tokens |
| Wire | Serialized data readable by other versions | Network protocols, persistence formats |
Breaking any of these creates upgrade friction for users.
Extend-Only Design
The foundation of stable APIs: never remove or modify, only extend.
Three Pillars
- Previous functionality is immutable - Once released, behavior and signatures are locked
- New functionality through new constructs - Add overloads, new types, opt-in features
- Removal only after deprecation period - Years, not releases
Benefits
- Old code continues working in new versions
- New and old pathways coexist
- Upgrades are non-breaking by default
- Users upgrade on their schedule
Resources:
API Change Guidelines
Safe Changes (Any Release)
// 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
}
Unsafe Changes (Never or Major Version Only)
// 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!
Deprecation Pattern
// 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 Approval Testing
Prevent accidental breaking changes with automated API surface testing.
Using 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);
}
Creates ApprovePublicApi.verified.txt:
namespace MyLibrary
{
public class OrderProcessor
{
public OrderProcessor() { }
public void Process(Order order) { }
public Task ProcessAsync(Order order, CancellationToken ct = default) { }
}
}
Any API change fails the test - reviewer must explicitly approve changes.
PR Review Process
- PR includes changes to
*.verified.txtfiles - Reviewers see exact API surface changes in diff
- Breaking changes are immediately visible
- Conscious decision required to approve
Wire Compatibility
For distributed systems, serialized data must be readable across versions.
Requirements
| Direction | Requirement |
|---|---|
| Backward | Old writers → New readers (current version reads old data) |
| Forward | New writers → Old readers (old version reads new data) |
Both are required for zero-downtime rolling upgrades.
Safely Evolving Wire Formats
Phase 1: Add read-side support (opt-in)
// 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()
};
Phase 2: Enable write-side (opt-out, next minor version)
// Config to enable new format (off by default initially)
akka.cluster.use-heartbeat-v2 = on
Phase 3: Make default (future version)
After install base has absorbed read-side code.
Schema-Based Serialization
Prefer schema-based formats over reflection-based:
| Format | Type | Wire Compatibility |
|---|---|---|
| Protocol Buffers | Schema-based | Excellent - explicit field numbers |
| MessagePack | Schema-based | Good - with contracts |
| System.Text.Json | Schema-based (with source gen) | Good - explicit properties |
| Newtonsoft.Json | Reflection-based | Poor - type names in payload |
| BinaryFormatter | Reflection-based | Terrible - never use |
See dotnet/serialization skill for details.
Encapsulation Patterns
Internal APIs
Mark non-public APIs explicitly:
// Attribute for documentation
[InternalApi]
public class ActorSystemImpl { }
// Namespace convention
namespace MyLibrary.Internal
{
public class InternalHelper { } // Public for extensibility, not for users
}
Document clearly:
Types in
.Internalnamespaces or marked with[InternalApi]may change between any releases without notice.
Sealing Classes
// 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
Interface Segregation
// 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!
}
Versioning Strategy
Semantic Versioning (Practical)
| Version | Changes Allowed |
|---|---|
| Patch (1.0.x) | Bug fixes, security patches |
| Minor (1.x.0) | New features, deprecations, obsolete removal |
| Major (x.0.0) | Breaking changes, old API removal |
Key Principles
- No surprise breaks - Even major versions should be announced and planned
- Extensions anytime - New APIs can ship in any release
- Deprecate before remove -
[Obsolete]for at least one minor version - Communicate timelines - Users need to plan upgrades
Chesterton's Fence
Before removing or changing something, understand why it exists.
Assume every public API is used by someone. If you want to change it:
- Socialize the proposal on GitHub
- Document migration path
- Provide deprecation period
- Ship in planned release
Pull Request Checklist
When reviewing PRs that touch public APIs:
- No removed public members (use
[Obsolete]instead) - No changed signatures (add overloads instead)
- No new required parameters (use defaults)
- API approval test updated (
.verified.txtchanges reviewed) - Wire format changes are opt-in (read-side first)
- Breaking changes documented (release notes, migration guide)
Anti-Patterns
Breaking Changes Disguised as Fixes
// "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) { }
Silent Behavior Changes
// 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
Polymorphic Serialization
// AVOID: Type names in wire format
{ "$type": "MyApp.Order, MyApp", "Id": 123 }
// Renaming Order class = wire break!
// PREFER: Explicit discriminators
{ "type": "order", "id": 123 }
Resources
- 流狐分类
- 设计与多媒体
- 作者声明 Agent
- 未找到明确声明;不据此推断已兼容或已测试
- 静态检查
- 88 / 100 · 启发式扫描,不代表运行安全
- 作者 / 版本 / 许可
- @Aaronontheweb · 未声明 license
- 流狐 Token 估算
- 低消耗
- 流狐接入估算
- 需简单配置
- 是否需要外部 API Key
- 未发现要求
- 检测到的系统要求
- 未声明
- 底层运行要求
- 未声明
- 检测到的文件与系统行为
-
- 只读
- 允许写入 / 修改
- 检测到的网络行为
- 仅限本地
- 安装命令数
- 无(仅作为资料)
档案由构建时根据 SKILL.md 与安装命令自动衍生,可能与作者实际意图存在差异。
需要注意: 未限定 allowed-tools,默认拥有全部工具权限。
作者没有在当前 SKILL.md 中定义固定输出样例。 Use this skill when: Designing public APIs for NuGet packages or libraries Making changes to existing public APIs
Type · Definition · Scope API/Source · Code compiles against newer version · Public method signatures, types Binary · Compiled code runs against newer version · Assembly layout, method tokens
The foundation of stable APIs: never remove or modify, only extend.
Previous functionality is immutable - Once released, behavior and signatures are locked New functionality through new constructs - Add overloads, new types, opt-in features Removal only after deprecation period - Years, not releases
Old code continues working in new versions New and old pathways coexist Upgrades are non-breaking by default
API Change Guidelines
# Public API Design and Compatibility
## When to Use This Skill
Use this skill when:
- Designing public APIs for NuGet packages or libraries
- Making changes to existing public APIs
- Planning wire format changes for distributed systems
- Implementing versioning strategies
- Reviewing pull requests for breaking changes
---
## The Three Types of Compatibility
| Type | Definition | Scope |
|------|------------|-------|
| **API/Source** | Code compiles against newer version | Public method signatures, types |
| **Binary** | Compiled code runs against newer version | Assembly layout, method tokens |
| **Wire** | Serialized data readable by other versions | Network protocols, persistence formats |
Breaking any of these creates upgrade friction for users.
---
## Extend-Only Design
The foundation of stable APIs: **never remove or modify, only extend**.
### Three Pillars
1. **Previous functionality is immutable** - Once released, behavior and signatures are locked
2. **New functionality through new constructs** - Add overloads, new types, opt-in features
3. **Removal only after deprecation period** - Years, not releases
### Benefits
- Old code continues working in new versions
- New and old pathways coexist
- Upgrades are non-breaking by default
- Users upgrade on their schedule
**Resources:**
- [Extend-Only Design](https://aaronstannard.com/extend-only-design/)
- [OSS Compatibility Standards](https://aaronstannard.com/oss-compatibility-standards/)
---
## API Change Guidelines
### Safe Changes (Any Release)
```csharp
// 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)
{
… 作者原文负责流程事实;流狐只索引当前章节、要点、文件与命令。
章节 -> When to Use This Skill → The Three Types of Compatibility → Extend-Only Design → Three Pillars → Benefits → API Change Guidelines
要点 -> API/Source · Binary · Wire · never remove or modify, only extend · Previous functionality is immutable · New functionality through new constructs · Removal only after deprecation period · Resources
文件/命令 -> ApprovePublicApi.verified.txt · .verified.txt · dotnet/serialization · .Internal · [InternalApi] · [Obsolete]
内容 SHA-256 -> 7b9a18632b13
方法与流程
适用与边界
原文中的明确线索
ApprovePublicApi.verified.txt、.verified.txt、dotnet/serialization、.Internal、[InternalApi]、[Obsolete]