Communityコーディング&開発github.com

公開API設計

拡張のみの設計原則を用いて、安定した互換性のある公開APIを設計します。NuGetパッケージや分散システム向けに、API互換性、ワイヤ互換性、バージョニングを管理します。

公開API設計 とは?

このスキルは、安定した公開APIを設計・維持するためのベストプラクティスを提供します。特にNuGetパッケージや分散システムを対象とし、API互換性、バイナリ互換性、ワイヤ互換性の3種類の互換性を扱います。拡張のみの設計(Extend-Only Design)を中核とし、既存の機能を決して削除・変更せず、新しい機能は新しいコンストラクトとして追加することを推奨します。また、API表面の自動テスト(ApiApprover + Verify)による意図しない破壊的変更の防止、安全なワイヤフォーマットの進化方法(読み取り側先行、書き込み側後続)、セマンティックバージョニングに基づいた実践的なバージョン戦略について詳しく解説します。C#のコード例を豊富に含み、非推奨パターン、カプセル化のテクニック、プルリクエストレビューのチェックリストなど、開発者が実際にAPI設計で直面する課題に対する具体的なガイダンスを提供します。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/csharp-api-design

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

公開API設計と互換性

このスキルを使用するタイミング

以下の場合にこのスキルを使用します:

  • NuGetパッケージやライブラリ向けの公開APIを設計する場合
  • 既存の公開APIに変更を加える場合
  • 分散システムにおけるワイヤフォーマットの変更を計画する場合
  • バージョニング戦略を実装する場合
  • 破壊的変更を含むプルリクエストをレビューする場合

3種類の互換性

種類定義スコープ
API/ソース新しいバージョンに対してコードがコンパイルできること公開メソッドシグネチャ、型
バイナリコンパイル済みコードが新しいバージョンで動作することアセンブリレイアウト、メソッドトークン
ワイヤシリアル化されたデータが他のバージョンでも読み取り可能であることネットワークプロトコル、永続化フォーマット

これらのいずれかを破ると、ユーザーのアップグレードに摩擦が生じます。


拡張のみの設計

安定したAPIの基盤:決して削除や変更は行わず、拡張のみを行う。

3つの柱

  1. 以前の機能は不変 - 一度リリースされた動作とシグネチャはロックされる
  2. 新しい機能は新しいコンストラクトを通じて - オーバーロード、新しい型、オプトイン機能を追加する
  3. 削除は非推奨期間の後でのみ - リリース単位ではなく、年単位

利点

  • 古いコードは新しいバージョンでも引き続き動作する
  • 新しいパスと古いパスが共存できる
  • アップグレードはデフォルトで非破壊的
  • ユーザーは自分のペースでアップグレードできる

リソース:


API変更ガイドライン

安全な変更(任意のリリース)

// デフォルトパラメータを持つ新しいオーバーロードを追加
public void Process(Order order, CancellationToken ct = default);

// 既存のメソッドに新しいオプションパラメータを追加
public void Send(Message msg, Priority priority = Priority.Normal);

// 新しい型、インターフェース、列挙型を追加
public interface IOrderValidator { }
public enum OrderStatus { Pending, Complete, Cancelled }

// 既存の型に新しいメンバーを追加
public class Order
{
    public DateTimeOffset? ShippedAt { get; init; }  // NEW
}

安全でない変更(絶対に行わないか、メジャーバージョンでのみ)

// 公開メンバーの削除またはリネーム
public void ProcessOrder(Order order);  // 以前: Process()

// パラメータ型または順序の変更
public void Process(int orderId);  // 以前: Process(Order order)

// 戻り値の型の変更
public Order? GetOrder(string id);  // 以前: public Order GetOrder()

// アクセス修飾子の変更
internal class OrderProcessor { }  // 以前: public

// デフォルト値なしの必須パラメータ追加
public void Process(Order order, ILogger logger);  // 呼び出し元が壊れる!

非推奨化パターン

// ステップ1: バージョン付きでObsoleteとしてマーク(任意のリリース)
[Obsolete("v1.5.0から廃止されました。代わりにProcessAsyncを使用してください。")]
public void Process(Order order) { }

// ステップ2: 新しい推奨APIを追加(同じリリース)
public Task ProcessAsync(Order order, CancellationToken ct = default);

// ステップ3: 次のメジャーバージョンで削除(v2.0以降)
// ユーザーが移行する時間を確保した後にのみ

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. レビュー担当者はdiffで正確なAPI表面の変更を確認できる
  3. 破壊的変更は即座に可視化される
  4. 承認には意識的な決定が必要

ワイヤ互換性

分散システムの場合、シリアル化されたデータはバージョン間で読み取り可能である必要があります。

要件

方向要件
後方互換古い書き込み → 新しい読み取り(現在のバージョンが古いデータを読める)
前方互換新しい書き込み → 古い読み取り(古いバージョンが新しいデータを読める)

ゼロダウンタイムのローリングアップグレードには両方が必要です。

ワイヤフォーマットを安全に進化させる

フェーズ1: 読み取り側のサポートを追加(オプトイン)

// 新しいメッセージタイプ - 読み取り側が先にデプロイされる
public sealed record HeartbeatV2(
    Address From,
    long SequenceNr,
    long CreationTimeMs);  // 新しいフィールド

// デシリアライザは新旧両方を処理する
public object Deserialize(byte[] data, string manifest) => manifest switch
{
    "Heartbeat" => DeserializeHeartbeatV1(data),   // 古いフォーマット
    "HeartbeatV2" => DeserializeHeartbeatV2(data), // 新しいフォーマット
    _ => throw new NotSupportedException()
};

フェーズ2: 書き込み側を有効化(オプトアウト、次のマイナーバージョン)

// 新しいフォーマットを有効にする設定(初期はデフォルトでオフ)
akka.cluster.use-heartbeat-v2 = on

フェーズ3: デフォルトにする(将来のバージョン)

インストールベースが読み取り側コードを取り込んだ後。

スキーマベースのシリアル化

リフレクションベースよりもスキーマベースのフォーマットを優先:

フォーマット種類ワイヤ互換性
Protocol Buffersスキーマベース優れている - 明示的なフィールド番号
MessagePackスキーマベース良い - コントラクトあり
System.Text.Jsonスキーマベース(ソース生成あり)良い - 明示的なプロパティ
Newtonsoft.Jsonリフレクションベース悪い - ペイロードに型名
BinaryFormatterリフレクションベース非常に悪い - 絶対に使用しない

詳細は dotnet/serialization スキルを参照。


カプセル化パターン

内部API

非公開APIを明示的にマーク:

// ドキュメント用の属性
[InternalApi]
public class ActorSystemImpl { }

// 名前空間の慣例
namespace MyLibrary.Internal
{
    public class InternalHelper { }  // 拡張性のために公開、ユーザー向けではない
}

明確に文書化:

.Internal 名前空間の型、または [InternalApi] でマークされた型は、予告なく任意のリリース間で変更される可能性があります。

クラスのシール

// 行う: 継承を意図していないクラスはシールする
public sealed class OrderProcessor { }

// 行わない: うっかりシールを忘れる
public class OrderProcessor { }  // ユーザーが継承する可能性があり、変更を妨げる

インターフェース分離

// 行う: 小さく焦点を絞ったインターフェース
public interface IOrderReader
{
    Order? GetById(OrderId id);
}

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

// 行わない: モノリシックなインターフェース(メソッドを追加すると破壊的になる)
public interface IOrderRepository
{
    Order? GetById(OrderId id);
    Task SaveAsync(Order order);
    // 新しいメソッドを追加すると、すべての実装が壊れる!
}

バージョニング戦略

セマンティックバージョニング(実践的)

バージョン許可される変更
パッチ (1.0.x)バグ修正、セキュリティパッチ
マイナー (1.x.0)新機能、非推奨化、廃止属性の削除
メジャー (x.0.0)破壊的変更、古いAPIの削除

主要原則

  1. 驚きの破壊はなし - メジャーバージョンでも、発表と計画を行うべき
  2. いつでも拡張可能 - 新しいAPIは任意のリリースで出荷できる
  3. 削除前に非推奨化 - 少なくとも1つのマイナーバージョンで [Obsolete] を使用
  4. タイムラインを伝達 - ユーザーはアップグレードを計画する必要がある

チェスタートンのフェンス

何かを削除または変更する前に、なぜそれが存在するのかを理解せよ。

すべての公開APIは誰かに使用されていると想定する。変更したい場合:

  1. GitHubで提案を周知する
  2. 移行パスを文書化する
  3. 非推奨期間を設ける
  4. 計画されたリリースで出荷する

プルリクエストチェックリスト

公開APIに触れるPRをレビューする際:

  • 公開メンバーが削除されていない(代わりに [Obsolete] を使用)
  • シグネチャが変更されていない(代わりにオーバーロードを追加)
  • 新しい必須パラメータがない(デフォルト値を使用)
  • API承認テストが更新されている(.verified.txt の変更がレビューされている)
  • ワイヤフォーマットの変更がオプトインである(読み取り側が先)
  • 破壊的変更が文書化されている(リリースノート、移行ガイド)

アンチパターン

修正と偽装した破壊的変更

// ユーザーを壊す「バグ修正」
public async Task<Order> GetOrderAsync(OrderId id)  // 以前は同期!
{
    // 非同期に「修正」されたが、すべての呼び出し元を壊す
}

// 正しい: 新しいメソッドを追加し、古いメソッドを非推奨にする
[Obsolete("代わりにGetOrderAsyncを使用してください")]
public Order GetOrder(OrderId id) => GetOrderAsync(id).Result;

public async Task<Order> GetOrderAsync(OrderId id) { }

暗黙的な動作変更

// デフォルト値の変更は、古い動作に依存していたユーザーを壊す
public void Configure(bool enableCaching = true)  // 以前: false!

// 正しい: 新しい名前の新しいパラメータ
public void Configure(
    bool enableCaching = false,  // 元のデフォルトを維持
    bool enableNewCaching = true)  // 新しい動作はオプトイン

ポリモーフィックなシリアル化

// 避ける: ワイヤフォーマットに型名を含める
{ "$type": "MyApp.Order, MyApp", "Id": 123 }

// Orderクラスのリネーム = ワイヤの破壊!

// 推奨: 明示的な識別子
{ "type": "order", "id": 123 }

リソース

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-best-practices

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

crap-analysis

Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.

csharp-concurrency-patterns

Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Avoid locks and manual synchronization unless absolutely necessary.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-project-structure

Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

mailpit-integration

Test email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests.

marketplace-publishing

Workflow for publishing skills and agents to the dotnet-skills Claude Code marketplace. Covers adding new content, updating plugin.json, validation, and release tagging.

関連スキル