Versionamento de API
Como evoluir um contrato sem quebrar quem já o usa, e quais mudanças quebram de verdade.
O problema: clientes que você não controla
Enquanto uma API tem um único cliente, mantido pela mesma equipe, mudar o contrato é simples: muda-se o servidor e o cliente juntos. Isso deixa de ser verdade rapidamente:
- o aplicativo móvel tem versões antigas instaladas em celulares que não atualizam;
- parceiros integraram a API e atualizam o código uma vez por ano, se tanto;
- outros serviços da empresa dependem da API e têm os seus próprios prazos.
Cada mudança no contrato precisa responder: o que acontece com quem ainda usa o formato antigo?
Mudanças compatíveis e incompatíveis
Uma mudança é compatível quando os clientes existentes continuam funcionando sem alteração. É incompatível (breaking change) quando algum deles quebra.
| Mudança | Compatível? |
|---|---|
| Acrescentar uma rota | Sim |
| Acrescentar um campo opcional no pedido | Sim |
| Acrescentar um campo na resposta | Quase sempre, mas não para todo cliente (veja abaixo) |
| Acrescentar um valor numa enumeração da resposta (um status novo) | Muitas vezes não |
| Remover ou renomear um campo | Não |
| Mudar o tipo de um campo (número para texto) | Não |
| Tornar obrigatório um campo que era opcional no pedido | Não |
| Mudar o significado de um campo, mantendo nome e tipo | Não, e é a pior: nada falha, os dados ficam errados |
| Mudar um código de status ou o formato dos erros | Não |
O que "acrescentar é seguro" esconde
A regra de que acrescentar campos e valores é seguro depende de como o cliente lê a resposta. Testamos clientes tipados nas cinco linguagens, com as bibliotecas mais comuns e na configuração padrão, contra duas respostas novas de uma API de pedidos:
- uma com um campo novo,
prazoEntrega; - uma com um status novo,
em_separacao, que o cliente não conhecia.
| Cliente | Campo novo | Status novo |
|---|---|---|
| TypeScript com Zod | Aceitou | Falhou (invalid_enum_value) |
| Python com Pydantic | Aceitou | Falhou (enum) |
Go com encoding/json | Aceitou | Aceitou: o status é um texto comum |
Java com Jackson (ObjectMapper sem configuração) | Falhou (UnrecognizedPropertyException) | Falhou (InvalidFormatException) |
C# com System.Text.Json | Aceitou | Falhou (JsonException) |
Dois resultados contrariam a intuição:
- Um status novo quebrou quatro dos cinco clientes. Quem modela o status como uma enumeração fechada recusa qualquer valor fora da lista. Para essa API, acrescentar
em_separacaoé uma mudança incompatível, mesmo sendo "só um valor a mais". - Um campo novo quebrou o cliente Java. O
ObjectMapperdo Jackson, sem configuração, recusa campos que não conhece. O Spring Boot muda esse padrão no mapeador que ele configura, mas qualquer cliente que crie o seu próprioObjectMapperrecebe o comportamento estrito.
E os modos estritos que existem em cada biblioteca (.strict() no Zod, extra="forbid" no Pydantic, DisallowUnknownFields no Go, UnmappedMemberHandling.Disallow no C#) recusaram o campo novo nos testes, como esperado.
Clientes tolerantes
O princípio da robustez, aplicado a APIs: seja rigoroso no que envia e tolerante no que recebe. Um cliente tolerante:
- ignora campos que não conhece;
- trata valores desconhecidos numa enumeração como "desconhecido", em vez de falhar;
- não depende da ordem dos campos nem de campos opcionais estarem presentes.
Reescrevemos os cinco clientes do teste dessa forma. Os cinco passaram a ler as duas respostas sem erro: o campo novo foi ignorado, e o status novo virou desconhecido. O nível Implementação mostra o código de cada um.
O servidor, por sua vez, deve tratar o que recebe com rigor: validar os pedidos e recusar dados inválidos com erros claros, como mostra o tópico de design de APIs.
Quando uma versão nova é inevitável
Às vezes a mudança incompatível é necessária: um modelo de dados errado desde o início, uma regra de negócio que mudou, um formato que não escala. Aí a API ganha uma versão nova, e a antiga continua funcionando por um tempo.
Manter duas versões tem custo: duas rotas, dois formatos, testes para ambos, correções de segurança nas duas. Por isso uma versão nova deve ser rara, e cada versão antiga precisa de data para ser desligada, comunicada com antecedência.
Por que isso importa quando a IA escreve o código
Um assistente que altera uma API raramente pensa em quem já a consome:
- Renomeia um campo para um nome "melhor", quebrando todos os clientes.
- Acrescenta um valor a uma enumeração sem considerar os clientes tipados.
- Gera clientes com enumerações fechadas e modos estritos, que quebram na primeira evolução do servidor.
- Cria uma
/v2para uma mudança que poderia ser compatível. - Remove uma rota antiga sem aviso, porque "ninguém deve usar mais".
O próximo nível compara as formas de versionar e de conduzir uma mudança incompatível com o menor impacto.
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.
Versionamento de API em cinco minutos
Explique em no máximo cinco minutos de leitura como evoluir uma API sem quebrar quem já a usa, para alguém que nunca manteve uma API com clientes externos. Cubra só o essencial: o que é uma mudança incompatível, por que acrescentar um valor numa enumeração pode quebrar clientes, o que é um cliente tolerante e quando criar uma versão nova. No final, me faça três perguntas curtas para eu verificar se entendi, e corrija as minhas respostas.
Quais mudanças quebram clientes
Quero aprender a identificar mudanças incompatíveis numa API. Aja como um mentor. Use uma API de pedidos como exemplo. Apresente doze mudanças, uma por vez, e pergunte se cada uma é compatível ou não, antes de explicar. Inclua: acrescentar uma rota, acrescentar um campo na resposta, acrescentar um campo opcional no pedido, tornar um campo obrigatório, renomear um campo, mudar um tipo, acrescentar um valor numa enumeração da resposta, remover um valor de enumeração, mudar o formato de uma data, mudar um código de status, mudar o significado de um campo sem mudar o nome, e mudar a ordem dos itens de uma lista. Para cada uma, explique que tipo de cliente quebra e como.
Clientes tolerantes
Quero entender o princípio da robustez aplicado a APIs e como escrever clientes tolerantes. Pergunte qual linguagem e qual biblioteca de leitura de JSON eu uso. Explique: 1. O princípio de ser rigoroso no que envia e tolerante no que recebe. 2. O comportamento padrão da minha biblioteca com campos desconhecidos e com valores desconhecidos de enumerações. 3. Como configurá-la para tolerar os dois. 4. Por que tolerar não significa ignorar: o cliente deve registrar o que não conhece. 5. Os riscos de ser tolerante demais. Depois me ajude a escrever um teste que confirma que o meu cliente lê uma resposta com um campo novo e um status novo.
Por que versões novas custam caro
Quero entender o custo real de manter várias versões de uma API. Explique: 1. O que precisa ser duplicado ao manter duas versões: rotas, testes, documentação, correções de segurança. 2. Por que clientes demoram anos para migrar. 3. A técnica de expandir e contrair para evitar versões novas, com um exemplo de renomear um campo. 4. Quando uma versão nova é realmente inevitável. Depois me descreva três mudanças necessárias numa API e pergunte se eu criaria uma versão nova ou faria de forma compatível. Corrija as minhas respostas.