DevGuideTK
← Trilha Backend

Versionamento de API

Como evoluir um contrato sem quebrar quem já o usa, e quais mudanças quebram de verdade.

Os clientes desta página foram executados nas cinco linguagens contra as duas respostas do teste do nível Conceito: uma com o campo novo prazoEntrega e outra com o status novo em_separacao.

Um cliente tolerante, em cada linguagem

Cada versão ignora campos desconhecidos e transforma um status desconhecido em desconhecido, em vez de lançar exceção.

import { z } from "zod";

export const Pedido = z.object({
  id: z.string(),
  // valores novos viram "desconhecido" em vez de quebrar a leitura
  status: z
    .enum(["aberto", "pago", "enviado", "desconhecido"])
    .catch("desconhecido"),
}); // campos desconhecidos são descartados por padrão (não use .strict())

export type Pedido = z.infer<typeof Pedido>;

export async function buscarPedido(id: string): Promise<Pedido> {
  const resposta = await fetch(`https://api.loja.com.br/v1/pedidos/${id}`);
  if (!resposta.ok) throw new Error(`HTTP ${resposta.status}`);
  return Pedido.parse(await resposta.json());
}

O resultado, nas cinco linguagens

Cliente toleranteResposta com campo novoResposta com status novo
TypeScript (Zod com .catch)Leu; campo ignoradoLeu; status desconhecido
Python (Pydantic com _missing_)Leu; campo ignoradoLeu; status desconhecido
Go (encoding/json)Leu; campo ignoradoLeu; status em_separacao, com Conhecido() falso
Java (Jackson configurado)Leu; campo ignoradoLeu; status desconhecido
C# (conversor próprio)Leu; campo ignoradoLeu; status Desconhecido

O que muda de uma linguagem para outra

Campos desconhecidos por padrãoEnumeração desconhecida por padrãoComo tolerar
TypeScript (Zod)DescartadosErro.catch() com um valor padrão
Python (Pydantic)IgnoradosErro_missing_ na enumeração
GoIgnoradosNão se aplica: o status é textoTratar o valor desconhecido no switch
Java (Jackson)ErroErroignoreUnknown, @JsonEnumDefaultValue e a opção do mapeador
C# (System.Text.Json)IgnoradosErroUm conversor próprio, como o do exemplo

Duas observações sobre a tabela:

  • No Go, o valor novo chega intacto, o que é ótimo para registrar em log e repassar, mas o código que decide algo pelo status precisa de um caso para valores que não conhece, e não pode assumir que o switch cobre tudo.
  • No C#, o JsonStringEnumConverter padrão lança exceção com valores desconhecidos, e não há opção para mudar isso. O conversor do exemplo resolve com poucas linhas. Uma alternativa simples é modelar o status como string no cliente.

Tolerar não é ignorar: o cliente deve registrar quando recebe um valor desconhecido. Esse registro avisa que a API evoluiu e que o cliente precisa ser atualizado para tratar o caso novo.

Acompanhar o uso das versões no servidor

Para desligar uma versão antiga com segurança, é preciso saber quem ainda a usa. Registre a versão e o cliente em cada pedido:

// Middleware: registra a versão usada e marca as rotas obsoletas
app.use((req, res, next) => {
  const versao = req.path.startsWith("/v1/") ? "v1" : "v2";
  metricas.contador("api_pedidos_total", { versao, cliente: req.clienteId ?? "anonimo" }).inc();

  if (versao === "v1") {
    res.setHeader("Deprecation", "@1767225600");
    res.setHeader("Sunset", "Thu, 01 Oct 2026 00:00:00 GMT");
    res.setHeader("Link", '<https://docs.loja.com.br/migracao-v2>; rel="deprecation"');
  }
  next();
});

Com essa métrica por cliente, a etapa de aviso deixa de ser um e-mail para todos e passa a ser uma conversa com quem realmente ainda depende da versão antiga.

Comparar o contrato no CI

# Falha se o OpenAPI novo tiver mudanças incompatíveis em relação ao da main
oasdiff breaking openapi-main.yaml openapi.yaml --fail-on ERR

Ferramentas assim apontam campos removidos, tipos alterados, parâmetros que passaram a ser obrigatórios e valores removidos de enumerações. Acrescentar valores a uma enumeração da resposta nem sempre é apontado como incompatível, e, como o teste deste tópico mostrou, para clientes tipados ele é. Trate essa mudança com o mesmo cuidado.

Lista de revisão

Servidor

  • A mudança pode ser feita de forma compatível, com expandir e contrair?
  • Campos, enumerações e códigos de status existentes continuam iguais?
  • As enumerações que podem crescer estão documentadas como abertas?
  • Há comparação de contrato no CI?
  • Versões obsoletas devolvem Deprecation e Sunset, e o uso delas é medido por cliente?

Cliente

  • O cliente ignora campos desconhecidos?
  • Valores desconhecidos de enumerações viram um valor padrão, registrado em log?
  • Modos estritos de leitura estão desligados para respostas da API?
  • A versão da API está fixada nas chamadas?

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Testar meu cliente contra a evolução da API

    Quero testar se o cliente da minha API sobrevive à evolução do servidor.
    
    Pergunte qual linguagem e biblioteca de JSON eu uso.
    
    Vamos em etapas:
    1. Escrever um modelo tipado para a resposta de um pedido, com o status como enumeração.
    2. Ler uma resposta com um campo novo e ver o que acontece na configuração padrão.
    3. Ler uma resposta com um status novo e ver o que acontece.
    4. Ativar o modo estrito da biblioteca e repetir.
    5. Tornar o cliente tolerante aos dois casos, registrando em log o que não conhece.
    6. Transformar tudo em testes automatizados.
    
    Em cada etapa, peça que eu preveja o resultado antes de rodar.
  • Comparar contratos no CI

    Quero que o CI do meu projeto detecte mudanças incompatíveis na API antes do deploy.
    
    Pergunte se o projeto gera um documento OpenAPI e como, e qual plataforma de CI eu uso.
    
    Explique, com a configuração para o meu caso:
    1. Como gerar o OpenAPI da branch atual e obter o da branch principal.
    2. Como usar uma ferramenta de comparação, como o oasdiff, para apontar mudanças incompatíveis.
    3. O que a ferramenta detecta e o que ela pode deixar passar, como valores novos em enumerações da resposta.
    4. Como aprovar uma mudança incompatível intencional.
  • Revisar uma mudança na API

    Vou colar abaixo a diferença entre a versão atual e a nova de um documento OpenAPI, ou o código de uma rota antes e depois de uma mudança. Avalie o impacto nos clientes.
    
    Para cada mudança encontrada, diga:
    1. Se é compatível ou incompatível, e para que tipo de cliente.
    2. Se poderia ser feita de forma compatível, e como.
    3. Se exige versão nova, e como conduzir a migração.
    
    No final, diga se a mudança pode ir para produção como está.
    
    Diferença:
    [cole aqui]