Como projetar APIs internas e externas com contratos distintos

1. Fundamentos da Diferenciação entre APIs Internas e Externas

Contratos de API definem a interface entre sistemas — especificam os endpoints, formatos de dados, códigos de erro e comportamentos esperados. APIs internas são consumidas por serviços dentro da mesma organização ou ecossistema confiável, enquanto APIs externas são expostas a parceiros, clientes e desenvolvedores terceiros.

A principal razão para separar contratos é a diferença nos requisitos de estabilidade e evolução. APIs externas exigem contratos estáveis, versionamento explícito e garantias de backward compatibility. APIs internas podem evoluir rapidamente, com menos burocracia. Compartilhar o mesmo contrato gera acoplamento perigoso: uma mudança interna vira uma breaking change externa, e detalhes de implementação (como nomes de campos internos ou estruturas de banco de dados) vazam para consumidores externos.

Riscos de contratos únicos:
- Quebras em cascata quando um serviço interno modifica seu contrato
- Vazamento de informações sensíveis (IDs internos, estruturas de dados não sanitizadas)
- Impossibilidade de versionar internamente sem afetar externamente

2. Estratégias de Modelagem de Contratos Distintos

BFF (Backend for Frontend): Cada consumidor externo (web, mobile, parceiro) tem seu próprio BFF que adapta a API interna para suas necessidades específicas. O BFF faz agregação, transformação e redução de dados.

APIs internas focadas em eficiência: Retornam dados completos, permitem consultas complexas e expõem entidades de domínio diretamente. Exemplo:

POST /api/internal/orders/batch
{
  "orderIds": ["ord-001", "ord-002"],
  "includeLineItems": true,
  "includePayments": true
}

APIs externas com abstração: Retornam apenas dados necessários, usam identificadores opacos e agregam múltiplas chamadas internas em uma única resposta:

GET /api/v1/orders/ord-001
{
  "id": "ord-001",
  "status": "shipped",
  "total": 299.90,
  "estimatedDelivery": "2025-03-20"
}

3. Padrões de Versionamento e Compatibilidade

APIs externas seguem versionamento semântico: v1, v2. Cada versão tem seu próprio contrato estável. Mudanças incompatíveis exigem nova versão.

APIs internas podem usar estratégias mais flexíveis:
- Backward compatibility: nunca remover campos, apenas adicionar opcionais
- Envelopes: { "data": {...}, "meta": {...} } permitem adicionar metadados sem quebrar consumidores
- Campos opcionais: novos campos são adicionados como opcionais por padrão

Exemplo de evolução interna sem quebra:

// Versão inicial
{ "id": "123", "name": "Produto A" }

// Versão evoluída (compatível)
{ "id": "123", "name": "Produto A", "category": "eletrônicos" }

4. Segurança e Autenticação Diferenciada

APIs externas exigem autenticação robusta:
- OAuth2 com scopes granulares: cada endpoint tem permissões específicas
- API keys com rate limiting: chaves individuais para cada consumidor
- Validação de tokens JWT com claims de escopo e expiração

APIs internas podem usar mecanismos mais leves:
- mTLS: certificados mútuos entre serviços
- Service mesh: autenticação gerenciada pela malha (Istio, Linkerd)
- Tokens de serviço compartilhados: tokens fixos para comunicação interna

Exemplo de configuração de rate limiting por contrato:

# API externa: 100 requisições/minuto por chave
rate_limit: 100/min
scope: external

# API interna: 1000 requisições/minuto por serviço
rate_limit: 1000/min
scope: internal

5. Governança e Documentação de Contratos

Contratos externos são documentados com OpenAPI/Swagger e publicados em um portal de desenvolvedores. Cada versão tem sua própria spec versionada. Mudanças breaking passam por revisão de comitê de arquitetura.

Contratos internos são documentados em repositórios privados, com foco em desenvolvedores da equipe. Podem usar formatos mais simples como Protocol Buffers ou gRPC definitions.

Processos de revisão:
- Breaking changes externas: exigem RFC, aprovação de arquitetura, sunset period de 6 meses
- Breaking changes internas: podem ser feitas com comunicação direta entre equipes e migração coordenada

6. Estratégias de Implementação e Deploy

Separação física: microsserviços dedicados para API externa e interna. Cada um tem seu próprio ciclo de deploy, escalabilidade e monitoramento.

Separação lógica no mesmo serviço: camadas separadas que compartilham a lógica de negócio mas expõem contratos diferentes:

/srv
  /internal
    /orders.go    # Lógica interna (dados completos)
  /external
    /orders.go    # Lógica externa (dados sanitizados)
  /shared
    /domain.go    # Domínio compartilhado

Gateways de API: ponto único de tradução entre contratos. O gateway recebe requisições externas, chama serviços internos e transforma as respostas:

Gateway API → Serviço Interno → Gateway API → Cliente Externo
    (contrato v1)   (contrato interno)   (contrato v1 transformado)

Testes de contrato (consumer-driven): Ferramentas como Pact garantem que mudanças no contrato interno não quebrem consumidores externos. Testes são executados em CI/CD.

7. Monitoramento e Observabilidade com Contratos Distintos

Métricas específicas por tipo de contrato:

# Métricas externas
api_external_requests_total{endpoint="/v1/orders", status="200"} 1500
api_external_latency_seconds{endpoint="/v1/orders", p99="0.45"}

# Métricas internas
api_internal_requests_total{endpoint="/internal/orders/batch", status="200"} 4500
api_internal_latency_seconds{endpoint="/internal/orders/batch", p99="0.12"}

Logs incluem contexto de contrato para troubleshooting rápido:

{"level":"error","contract":"external","endpoint":"/v1/orders","status":429,"message":"rate limit exceeded","consumer":"partner-x"}
{"level":"info","contract":"internal","endpoint":"/internal/orders/batch","status":200,"duration_ms":45}

Alertas diferenciados: quebras de contrato externo são prioridade máxima (P0), enquanto problemas internos podem ter prioridade menor (P2).

8. Casos de Migração e Evolução de Contratos

Strangler Fig: substituir gradualmente contratos internos sem afetar externos. O gateway roteia requisições para a versão antiga ou nova:

# Gateway decide qual serviço chamar
if request.version == "v1":
    call_legacy_service()
else:
    call_new_service()

Deprecação gradual: endpoints externos obsoletos recebem header Sunset e retornam warnings:

HTTP/1.1 200 OK
Sunset: Sat, 01 Jun 2025 23:59:59 GMT
Warning: 299 - "This endpoint will be removed. Use /v2/orders instead."

Exemplo prático de migração: Sistema monolítico com API única. Separa-se em gateway + microsserviços:

  1. Gateway expõe API externa com contrato v1 (idêntico ao original)
  2. Serviços internos são extraídos com contratos próprios
  3. Gateway faz tradução entre contrato externo v1 e contratos internos
  4. Quando serviço interno muda, apenas o gateway precisa ser atualizado
  5. API externa evolui para v2 com novo contrato, enquanto v1 permanece estável

Referências