api-design

Projete APIs públicas estáveis e compatíveis usando princípios de design apenas por extensão. Gerencie compatibilidade de API, compatibilidade de wire e versionamento para pacotes NuGet e sistemas distribuídos.

O que é api-design?

Esta skill ajuda engenheiros de software a projetar e manter APIs públicas estáveis e compatíveis, com foco em princípios de design apenas por extensão. Ela cobre os três tipos de compatibilidade (API/Source, Binária e Wire), fornecendo diretrizes claras para mudanças seguras e inseguras em APIs. A skill inclui práticas para testes de aprovação de API com ferramentas como PublicApiGenerator e Verify, versionamento semântico prático, e estratégias para evoluir formatos de wire sem quebras. É ideal para desenvolvedores que trabalham com bibliotecas NuGet, pacotes ou sistemas distribuídos, suportando plataformas como .NET e Akka.NET, e oferece um checklist de revisão de pull requests para evitar mudanças prejudiciais.

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

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Design e Compatibilidade de API Pública

Quando Usar Esta Skill

Use esta skill quando:

  • Projetando APIs públicas para pacotes NuGet ou bibliotecas
  • Fazendo mudanças em APIs públicas existentes
  • Planejando mudanças no formato de wire para sistemas distribuídos
  • Implementando estratégias de versionamento
  • Revisando pull requests para mudanças que quebram

Os Três Tipos de Compatibilidade

TipoDefiniçãoEscopo
API/SourceCódigo compila contra a versão mais novaAssinaturas de métodos públicos, tipos
BináriaCódigo compilado executa contra a versão mais novaLayout de assembly, tokens de método
WireDados serializados legíveis por outras versõesProtocolos de rede, formatos de persistência

Quebrar qualquer um destes cria atrito de upgrade para os usuários.


Design Apenas por Extensão

A base de APIs estáveis: nunca remova ou modifique, apenas estenda.

Três Pilares

  1. Funcionalidade anterior é imutável - Uma vez lançada, o comportamento e assinaturas estão bloqueados
  2. Nova funcionalidade através de novos construtos - Adicione sobrecargas, novos tipos, funcionalidades opt-in
  3. Remoção apenas após período de depreciação - Anos, não versões

Benefícios

  • Código antigo continua funcionando em novas versões
  • Caminhos novos e antigos coexistem
  • Upgrades são não-quebrantes por padrão
  • Usuários atualizam em seu próprio cronograma

Recursos:


Diretrizes para Mudanças na API

Mudanças Seguras (Qualquer Versão)

// ADICIONAR novas sobrecargas com parâmetros padrão
public void Process(Order order, CancellationToken ct = default);

// ADICIONAR novos parâmetros opcionais a métodos existentes
public void Send(Message msg, Priority priority = Priority.Normal);

// ADICIONAR novos tipos, interfaces, enums
public interface IOrderValidator { }
public enum OrderStatus { Pending, Complete, Cancelled }

// ADICIONAR novos membros a tipos existentes
public class Order
{
    public DateTimeOffset? ShippedAt { get; init; }  // NOVO
}

Mudanças Inseguras (Nunca ou Apenas Versão Principal)

// REMOVER ou RENOMEAR membros públicos
public void ProcessOrder(Order order);  // Era: Process()

// MUDAR tipos de parâmetros ou ordem
public void Process(int orderId);  // Era: Process(Order order)

// MUDAR tipos de retorno
public Order? GetOrder(string id);  // Era: public Order GetOrder()

// MUDAR modificadores de acesso
internal class OrderProcessor { }  // Era: public

// ADICIONAR parâmetros obrigatórios sem padrões
public void Process(Order order, ILogger logger);  // Quebra chamadores!

Padrão de Depreciação

// Passo 1: Marcar como obsoleto com versão (qualquer versão)
[Obsolete("Obsoleto desde v1.5.0. Use ProcessAsync em vez disso.")]
public void Process(Order order) { }

// Passo 2: Adicionar nova API recomendada (mesma versão)
public Task ProcessAsync(Order order, CancellationToken ct = default);

// Passo 3: Remover na próxima versão principal (v2.0+)
// Apenas depois que os usuários tiveram tempo para migrar

Teste de Aprovação de API

Evite mudanças acidentais que quebram com testes automatizados de superfície da API.

Usando 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);
}

Cria ApprovePublicApi.verified.txt:

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

Qualquer mudança na API falha o teste - o revisor deve aprovar explicitamente as mudanças.

Processo de Revisão de PR

  1. O PR inclui mudanças nos arquivos *.verified.txt
  2. Revisores veem a superfície exata da API alterada no diff
  3. Mudanças que quebram são imediatamente visíveis
  4. Decisão consciente necessária para aprovar

Compatibilidade de Wire

Para sistemas distribuídos, dados serializados devem ser legíveis entre versões.

DireçãoRequisito
RetroativaEscritores antigos → Leitores novos (versão atual lê dados antigos)
AvançadaEscritores novos → Leitores antigos (versão antiga lê dados novos)

Ambos são necessários para upgrades contínuos sem downtime.

Evoluindo Formatos de Wire com Segurança

Fase 1: Adicionar suporte de leitura (opt-in)

// Novo tipo de mensagem - leitores implantados primeiro
public sealed record HeartbeatV2(
    Address From,
    long SequenceNr,
    long CreationTimeMs);  // NOVO campo

// Desserializador lida com ambos antigo e novo
public object Deserialize(byte[] data, string manifest) => manifest switch
{
    "Heartbeat" => DeserializeHeartbeatV1(data),   // Formato antigo
    "HeartbeatV2" => DeserializeHeartbeatV2(data), // Formato novo
    _ => throw new NotSupportedException()
};

Fase 2: Ativar lado de escrita (opt-out, próxima versão menor)

// Config para habilitar novo formato (desligado por padrão inicialmente)
akka.cluster.use-heartbeat-v2 = on

Fase 3: Tornar padrão (versão futura)

Após a base instalada ter absorvido o código de leitura.

Serialização Baseada em Esquema

Prefira formatos baseados em esquema em vez de baseados em reflexão:

FormatoTipoCompatibilidade de Wire
Protocol BuffersBaseado em esquemaExcelente - números de campo explícitos
MessagePackBaseado em esquemaBom - com contratos
System.Text.JsonBaseado em esquema (com source gen)Bom - propriedades explícitas
Newtonsoft.JsonBaseado em reflexãoRuim - nomes de tipo no payload
BinaryFormatterBaseado em reflexãoTerrível - nunca use

Veja a skill dotnet/serialization para detalhes.


Padrões de Encapsulamento

APIs Internas

Marque APIs não-públicas explicitamente:

// Atributo para documentação
[InternalApi]
public class ActorSystemImpl { }

// Convenção de namespace
namespace MyLibrary.Internal
{
    public class InternalHelper { }  // Público para extensibilidade, não para usuários
}

Documente claramente:

Tipos em namespaces .Internal ou marcados com [InternalApi] podem mudar entre qualquer versão sem aviso.

Selagem de Classes

// FAÇA: Sele classes não projetadas para herança
public sealed class OrderProcessor { }

// NÃO FAÇA: Deixar sem selo por acidente
public class OrderProcessor { }  // Usuários podem herdar, bloqueando mudanças

Segregação de Interfaces

// FAÇA: Interfaces pequenas e focadas
public interface IOrderReader
{
    Order? GetById(OrderId id);
}

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

// NÃO FAÇA: Interfaces monolíticas (não pode adicionar métodos sem quebrar)
public interface IOrderRepository
{
    Order? GetById(OrderId id);
    Task SaveAsync(Order order);
    // Adicionar novos métodos quebra todas as implementações!
}

Estratégia de Versionamento

Versionamento Semântico (Prático)

VersãoMudanças Permitidas
Patch (1.0.x)Correções de bugs, patches de segurança
Menor (1.x.0)Novas funcionalidades, depreciações, remoção de obsoletos
Principal (x.0.0)Mudanças que quebram, remoção de API antiga

Princípios Chave

  1. Sem quebras surpresa - Mesmo versões principais devem ser anunciadas e planejadas
  2. Extensões a qualquer momento - Novas APIs podem ser lançadas em qualquer versão
  3. Depreciar antes de remover - [Obsolete] por pelo menos uma versão menor
  4. Comunicar prazos - Usuários precisam planejar upgrades

A Cerca de Chesterton

Antes de remover ou mudar algo, entenda por que existe.

Assuma que toda API pública é usada por alguém. Se quiser mudá-la:

  1. Socialize a proposta no GitHub
  2. Documente o caminho de migração
  3. Forneça período de depreciação
  4. Lance em uma versão planejada

Checklist de Pull Request

Ao revisar PRs que tocam em APIs públicas:

  • Nenhum membro público removido (use [Obsolete] em vez disso)
  • Nenhuma assinatura alterada (adicione sobrecargas em vez disso)
  • Nenhum novo parâmetro obrigatório (use padrões)
  • Teste de aprovação de API atualizado (mudanças em .verified.txt revisadas)
  • Mudanças no formato de wire são opt-in (lado de leitura primeiro)
  • Mudanças que quebram documentadas (notas de versão, guia de migração)

Anti-Padrões

Mudanças que Quebram Disfarçadas de Correções

// "Correção de bug" que quebra usuários
public async Task<Order> GetOrderAsync(OrderId id)  // Era síncrono!
{
    // "Corrigido" para ser assíncrono - mas quebra todos os chamadores
}

// Correto: Adicionar novo método, depreciar antigo
[Obsolete("Use GetOrderAsync em vez disso")]
public Order GetOrder(OrderId id) => GetOrderAsync(id).Result;

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

Mudanças Silenciosas de Comportamento

// Mudar padrões quebra usuários que confiavam no comportamento antigo
public void Configure(bool enableCaching = true)  // Era: false!

// Correto: Novo parâmetro com novo nome
public void Configure(
    bool enableCaching = false,  // Padrão original preservado
    bool enableNewCaching = true)  // Novo comportamento opt-in

Serialização Polimórfica

// EVITAR: Nomes de tipo no formato de wire
{ "$type": "MyApp.Order, MyApp", "Id": 123 }

// Renomear classe Order = quebra de wire!

// PREFERIR: Discriminadores explícitos
{ "type": "order", "id": 123 }

Recursos

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.

Habilidades Relacionadas