Community코딩 & 개발github.com

api-design

확장 전용 설계 원칙을 사용하여 안정적이고 호환 가능한 공개 API를 설계합니다. NuGet 패키지 및 분산 시스템에 대한 API 호환성, 와이어 호환성 및 버전 관리를 관리합니다.

api-design란 무엇인가요?

이 스킬은 개발자와 AI 에이전트가 안정적이고 호환 가능한 공개 API를 설계하고 유지할 수 있도록 안내합니다. 특히 NuGet 패키지, 라이브러리, 분산 시스템에서 API/Source, Binary, Wire의 세 가지 유형의 호환성을 중점적으로 다룹니다. '확장 전용 설계(Extend-Only Design)' 원칙을 기반으로 기존 기능을 절대 제거하거나 수정하지 않고 새 기능만 추가하여 하위 호환성을 보장합니다. 또한 API 표면 변경을 자동으로 감지하는 API 승인 테스트(ApiApprover + Verify), 안전한 와이어 포맷 진화 전략, 체스터턴의 울타리(Chesterton's Fence) 원칙을 포함한 버전 관리 전략을 제공합니다. 이 스킬은 .NET 에코시스템(특히 Akka.NET)의 모범 사례를 반영하며, C# 코드 예제와 실용적인 Pull Request 체크리스트를 통해 실제 개발 환경에 직접 적용할 수 있도록 돕습니다.

지원 대상~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 변경
  • 분산 시스템의 와이어 형식 변경 계획
  • 버전 관리 전략 구현
  • 주요 변경 사항을 검토하는 Pull Request 검토

세 가지 유형의 호환성

유형정의범위
API/소스새 버전에서 코드 컴파일 가능공개 메서드 시그니처, 타입
바이너리컴파일된 코드가 새 버전에서 실행 가능어셈블리 레이아웃, 메서드 토큰
와이어직렬화된 데이터를 다른 버전에서 읽을 수 있음네트워크 프로토콜, 영속성 형식

이 중 하나라도 깨지면 사용자에게 업그레이드 마찰이 발생합니다.


확장 전용 설계

안정적인 API의 기초: 절대 제거하거나 수정하지 말고, 확장만 하세요.

세 가지 기둥

  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; }  // 새 멤버
}

안전하지 않은 변경 (절대 또는 메이저 버전만)

// 공개 멤버 제거 또는 이름 변경
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("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. 제거 전에 사용 중단 표시 - 최소 한 마이너 버전 동안 [Obsolete] 사용
  4. 일정 전달 - 사용자가 업그레이드를 계획할 수 있어야 함

체스터턴의 울타리

무언가를 제거하거나 변경하기 전에, 그것이 왜 존재하는지 이해하세요.

모든 공개 API는 누군가에 의해 사용된다고 가정하세요. 변경하려면:

  1. GitHub에서 제안 홍보
  2. 마이그레이션 경로 문서화
  3. 사용 중단 기간 제공
  4. 계획된 릴리스에 제공

Pull Request 체크리스트

공개 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.

관련 스킬