API Versioning
O versionamento de APIs representa um desafio crítico no ciclo de vida de qualquer serviço web que busca evoluir continuamente sem quebrar integrações existentes com clientes. À medida que requisitos de negócio mudam, bugs são corrigidos e novas funcionalidades são adicionadas, a API precisa evoluir de forma que permita inovação sem forçar todos os consumidores a atualizarem simultaneamente. Uma estratégia inadequada de versionamento pode resultar em cenários catastróficos onde mudanças aparentemente inofensivas quebram aplicações móveis já distribuídas que não podem ser atualizadas forçosamente, sistemas de parceiros que dependem de contratos específicos da API, ou integrações críticas de negócios que processam transações financeiras. O problema se torna ainda mais complexo em arquiteturas de microserviços, onde múltiplas APIs interdependentes precisam evoluir de forma coordenada, e em APIs públicas onde milhares de desenvolvedores de terceiros construíram soluções sobre sua infraestrutura. Este artigo explora as principais estratégias de versionamento - incluindo URI versioning, header versioning e content negotiation - analisando vantagens, desvantagens e casos de uso apropriados para cada abordagem, além de estabelecer políticas de deprecação, estratégias de backward compatibility e padrões de comunicação de mudanças que permitem evolução sustentável da API sem comprometer a estabilidade do ecossistema dependente.
Por Que Versionar APIs
- Breaking changes inevitáveis: Mudanças em modelos de dados, autenticação, comportamento
- Clientes heterogêneos: Mobile apps, web apps, partners com diferentes ciclos de atualização
- Backward compatibility: Manter versões antigas funcionando durante transição
- Contratos estáveis: Garantir previsibilidade para integradores
- Deprecação planejada: Sunset de versões antigas de forma controlada
Estratégias de Versionamento
1. URI Versioning (Mais Comum)
# Versão na URI path
GET /api/v1/users
GET /api/v2/users
# Vantagens:
- Extremamente visível e explícito
- Fácil de testar diferentes versões
- Cache-friendly (URLs diferentes)
- Simples de rotear em proxies/gateways
# Desvantagens:
- Duplicação de recursos (v1/users, v2/users)
- Pode levar a code duplication
- Mudança de URL para mesma resource
2. Header Versioning
# Custom header
GET /api/users
API-Version: 2.0
# Accept header (vendor MIME type)
GET /api/users
Accept: application/vnd.myapi.v2+json
# Vantagens:
- URI permanece limpa e consistente
- Mais RESTful (mesma resource, diferentes representations)
- Flexibilidade para versionar por recurso
# Desvantagens:
- Menos visível (requer inspeção de headers)
- Dificulta testes manuais
- Cache complexo (varia por header)
3. Query Parameter Versioning
# Query string
GET /api/users?version=2
GET /api/users?api-version=2.0
# Vantagens:
- Fácil de adicionar a requests existentes
- Mantém URI base estável
- Simples para clientes HTTP
# Desvantagens:
- Pode poluir query parameters
- Menos semântico (versão não é filtro)
- Problemas com routing/caching
4. Content Negotiation
# Media type versioning
GET /api/users
Accept: application/vnd.company.user-v2+json
# Schema versioning
POST /api/users
Content-Type: application/vnd.company.user.v2+json
# Vantagens:
- Mais RESTful e HTTP-compliant
- Permite versionar request/response separadamente
- Granularidade por resource type
# Desvantagens:
- Complexidade de implementação
- Requer entendimento de HTTP content negotiation
- Debugging mais difícil
Semantic Versioning para APIs
# MAJOR.MINOR.PATCH (Semver adaptado para APIs)
MAJOR: Breaking changes
- Remover endpoints
- Mudar estrutura de response
- Alterar autenticação
- Exemplo: v1.0.0 → v2.0.0
MINOR: Backward-compatible additions
- Novos endpoints
- Novos campos opcionais em responses
- Novos query parameters opcionais
- Exemplo: v2.0.0 → v2.1.0
PATCH: Bug fixes
- Correções sem mudança de contrato
- Performance improvements
- Exemplo: v2.1.0 → v2.1.1
# Comunicação
GET /api/v2/info
{
"version": "2.3.1",
"deprecatedAt": "2025-06-01",
"sunsetAt": "2025-12-01"
}
Backward Compatibility
Mudanças Backward-Compatible
- [OK] Adicionar novos endpoints
- [OK] Adicionar campos opcionais em requests
- [OK] Adicionar novos campos em responses (clientes devem ignorar)
- [OK] Tornar campos required em optional
- [OK] Adicionar novos valores em enums existentes
- [OK] Relaxar validações (aceitar mais inputs)
Mudanças Breaking (Requerem Nova Versão)
- [X] Remover ou renomear endpoints
- [X] Remover ou renomear campos em responses
- [X] Mudar tipos de dados (string → number)
- [X] Adicionar campos required em requests
- [X] Restringir validações (rejeitar inputs antes aceitos)
- [X] Mudar comportamento de autenticação/autorização
Deprecation Policy
# 1. Anúncio de Deprecação (6-12 meses antes)
{
"data": [...],
"deprecated": true,
"deprecation": {
"date": "2025-01-01",
"sunset": "2025-07-01",
"alternativeVersion": "v3",
"migrationGuide": "https://docs.api.com/migrate-v2-to-v3"
}
}
# 2. Headers de Deprecação
Deprecation: true
Sunset: Wed, 01 Jul 2025 00:00:00 GMT
Link: <https://docs.api.com/migrate>; rel="deprecation"
# 3. Monitoring de Uso
- Log requests por versão
- Identificar clientes ainda usando versões deprecated
- Notificações proativas para desenvolvedores
# 4. Período de Overlap
v2 Launch ─────────────────────────────►
v3 Launch ─────────────►
v2 Deprecated ─────►
v2 Sunset
Implementação com Express.js
// Router-based versioning
const express = require('express');
const app = express();
// V1 routes
const v1Router = express.Router();
v1Router.get('/users', (req, res) => {
res.json({ version: 'v1', users: [...] });
});
app.use('/api/v1', v1Router);
// V2 routes
const v2Router = express.Router();
v2Router.get('/users', (req, res) => {
res.json({
version: 'v2',
users: [...],
metadata: { ... } // New in v2
});
});
app.use('/api/v2', v2Router);
// Header-based versioning
app.get('/api/users', (req, res) => {
const version = req.headers['api-version'] || '1';
if (version === '2') {
return res.json({ version: 'v2', users: [...] });
}
res.json({ version: 'v1', users: [...] });
});
GraphQL Versioning
# GraphQL não precisa de versioning tradicional
# Use schema evolution e @deprecated directive
type User {
id: ID!
name: String!
email: String!
username: String! @deprecated(reason: "Use 'name' field instead")
}
# Field-level deprecation
type Query {
users: [User!]!
getUsers: [User!]! @deprecated(reason: "Use 'users' query instead")
}
# Additive changes são naturalmente backward-compatible
# Clientes solicitam apenas campos que conhecem
Best Practices
- Escolha uma estratégia e seja consistente
- Documente políticas de versionamento claramente
- Use semantic versioning para comunicar impacto de mudanças
- Mantenha pelo menos 2 versões ativas simultaneamente
- Implemente deprecation warnings em responses
- Forneça migration guides detalhados
- Monitor usage metrics por versão
- Automatize testes cross-version
- Comunique mudanças com antecedência (changelog, emails)
- Considere clientes que não podem atualizar rapidamente
Recomendação Final
Para APIs públicas e de longa duração, URI versioning (/api/v1/) é geralmente a melhor escolha pela clareza e facilidade de uso. Combine com semantic versioning para comunicar impacto de mudanças. Para APIs internas de microserviços, considere header versioning para maior flexibilidade. Sempre mantenha backward compatibility quando possível e estabeleça políticas claras de deprecação com períodos generosos de transição (6-12 meses mínimo).
