Estratégias de versionamento de contrato em APIs GraphQL
1. Fundamentos do versionamento em APIs GraphQL
No ecossistema REST, o versionamento tradicionalmente é feito através de URLs (/v1/, /v2/) ou headers customizados (Accept: application/vnd.api.v1+json). Essas abordagens criam múltiplas superfícies de API que precisam ser mantidas simultaneamente, aumentando a complexidade operacional.
GraphQL introduz uma mudança fundamental: o cliente especifica exatamente quais dados precisa, e o servidor responde apenas com esses dados. O contrato da API é definido pelo schema — um documento que descreve todos os tipos, campos e argumentos disponíveis. Esse contrato é fortemente tipado e auto-documentado.
A abordagem ideal no GraphQL não é criar múltiplas versões da API, mas sim evoluir o schema existente de forma que mudanças sejam sempre compatíveis com versões anteriores. Versões de URL ou header são consideradas antipadrão porque fragmentam o ecossistema de clientes e dificultam a manutenção.
2. Versionamento evolutivo com deprecação de campos
A estratégia mais fundamental é usar a diretiva @deprecated nativa do GraphQL. Ela permite marcar campos que devem ser removidos no futuro, dando tempo para os consumidores migrarem.
type User {
id: ID!
name: String!
username: String @deprecated(reason: "Use 'name' instead. Removed after 2025-06-01")
email: String!
oldEmail: String @deprecated(reason: "Use 'email' field. Will be removed in next major version")
}
A migração gradual pode ser feita em três fases:
- Anúncio: Marcar campo como
@deprecatedcom razão clara - Convivência: Manter ambos os campos por um período definido
- Remoção: Após monitoramento, remover o campo obsoleto
Para monitorar o uso de campos obsoletos, é possível analisar queries reais:
# Exemplo de query que ainda usa campo deprecated
query GetUser {
user(id: "123") {
name
username # campo deprecated
}
}
Ferramentas como Apollo Studio permitem rastrear quais campos estão sendo consultados, ajudando a decidir quando é seguro remover um campo.
3. Estratégia de extensão sem quebra (Backward-Compatible)
A regra de ouro do versionamento GraphQL é: nunca remova, apenas adicione. Toda mudança deve ser adicionada de forma que clientes antigos continuem funcionando.
Exemplo de adição segura de novo campo opcional:
type Product {
id: ID!
name: String!
price: Float!
# Novo campo opcional adicionado na versão 2.0
discountPrice: Float
# Novo argumento com valor default
description(language: String = "en"): String!
}
Clientes podem usar aliases para compatibilidade:
# Cliente antigo continua funcionando
query GetProduct {
product(id: "456") {
name
price
}
}
# Cliente novo usa o novo campo
query GetProduct {
product(id: "456") {
name
price
discountPrice
}
}
Fragments também ajudam na transição:
fragment ProductV1 on Product {
name
price
}
fragment ProductV2 on Product {
...ProductV1
discountPrice
}
4. Versionamento por camada de schema (Schema Stitching)
Para cenários onde mudanças significativas são necessárias, o schema stitching permite criar subschemas independentes que são combinados em um gateway.
# Schema v1 (gateway principal)
type Query {
user(id: ID!): UserV1
}
# Schema v2 (subschema separado)
type Query {
user(id: ID!): UserV2
}
O gateway pode rotear queries baseado em regras:
# Configuração de roteamento no gateway
const gatewayConfig = {
schemas: [
{ name: "v1", schema: schemaV1, matcher: (query) => query.includes("UserV1") },
{ name: "v2", schema: schemaV2, matcher: (query) => true }
]
}
Com Apollo Federation, o versionamento distribuído funciona naturalmente:
# Serviço de usuários versão 1
extend type Query {
userV1(id: ID!): UserV1 @external
}
# Serviço de usuários versão 2
extend type Query {
userV2(id: ID!): UserV2 @external
}
5. Versionamento por diretivas personalizadas
Diretivas customizadas oferecem controle granular sobre versões de campos e argumentos.
# Definição da diretiva @version
directive @version(from: String!, until: String) on FIELD_DEFINITION | ARGUMENT_DEFINITION
# Uso no schema
type User {
id: ID!
name: String!
email: String! @version(from: "1.0.0")
phone: String @version(from: "2.0.0", until: "3.0.0")
}
Implementação de middleware que filtra campos por versão:
# Middleware que processa a diretiva @version
function versionMiddleware(resolve, parent, args, context, info) {
const clientVersion = context.headers["x-api-version"] || "1.0.0"
const fieldVersion = getVersionFromDirective(info.fieldDefinition)
if (fieldVersion && !isVersionCompatible(clientVersion, fieldVersion)) {
return null # Campo não disponível para esta versão
}
return resolve(parent, args, context, info)
}
6. Estratégias de quebra controlada (Breaking Changes)
Mesmo com boas práticas, algumas mudanças quebram o contrato. As principais são:
- Remoção de campo: Remove um campo que clientes podem estar usando
- Tornar campo não-nulo: Quebra clientes que não esperam o campo
- Alterar tipo de argumento: Incompatibilidade de tipos
Para gerenciar quebras, use sunset headers:
# Resposta HTTP com sunset header
HTTP/1.1 200 OK
Sunset: Sat, 01 Jun 2025 23:59:59 GMT
Deprecation: true
Link: <https://api.example.com/schema/v2>; rel="successor-version"
Processo de comunicação recomendado:
- Anúncio: Comunicar a mudança com 6 meses de antecedência
- Transição: Manter ambos os comportamentos por 3 meses
- Remoção: Remover após período de transição
Changelog automático com schema diff:
# Comando para gerar diff entre schemas
graphql-inspector diff schema-v1.graphql schema-v2.graphql
# Saída: "Field 'User.oldEmail' was removed"
# Saída: "Field 'User.email' changed type from String to String!"
7. Ferramentas e boas práticas para gerenciamento de contrato
graphql-inspector é a ferramenta mais importante para validação de mudanças:
# Verificar se mudanças são compatíveis
graphql-inspector validate schema-v1.graphql schema-v2.graphql --rules ./rules.yml
# Regras de validação personalizadas
rules:
- no-breaking-changes: true
- no-schema-removal: true
- require-deprecation-reason: true
Testes de contrato com schema snapshot:
# Jest test para snapshot do schema
import { buildSchema } from 'graphql'
test('schema snapshot', () => {
const schema = buildSchema(schemaSDL)
expect(printSchema(schema)).toMatchSnapshot()
})
Integração contínua com CI/CD:
# GitHub Actions para validar schema
name: Schema Validation
on: pull_request
steps:
- uses: actions/checkout@v3
- run: npx graphql-inspector validate ./schema/*.graphql
- run: npx graphql-inspector diff ./schema/production.graphql ./schema/pr.graphql
Versionamento de schema com Git:
# Estrutura de diretórios recomendada
schemas/
v1/
user.graphql
product.graphql
v2/
user.graphql
product.graphql
changelog.md
Revisão de pull requests deve incluir checklist específico:
- [ ] Mudança é backward-compatible?
- [ ] Campos removidos estão deprecated há pelo menos 3 meses?
- [ ] Novos campos têm valores default?
- [ ] Documentação foi atualizada?
- [ ] Changelog foi atualizado?
Conclusão
O versionamento de contrato em APIs GraphQL não precisa ser complexo. A chave está em adotar uma mentalidade evolutiva: adicionar em vez de remover, deprecar em vez de deletar, e comunicar mudanças com transparência. Ferramentas como graphql-inspector, testes de contrato e CI/CD garantem que mudanças sejam seguras e rastreáveis.
Lembre-se: no GraphQL, o contrato é seu schema. Cuide dele como cuidaria de qualquer contrato legal — com cláusulas claras, períodos de transição e documentação adequada.
Referências
- GraphQL Specification - Deprecation — Documentação oficial sobre a diretiva @deprecated no schema GraphQL
- Apollo Federation Documentation — Guia completo sobre versionamento distribuído com Apollo Federation
- graphql-inspector Documentation — Ferramenta oficial para validação, diff e linting de schemas GraphQL
- GraphQL Best Practices - Versioning — Artigo oficial do GraphQL Foundation sobre práticas de versionamento
- Breaking Changes in GraphQL — Guia da Apollo sobre identificação e gerenciamento de mudanças que quebram contratos
- Schema Stitching with GraphQL Tools — Tutorial prático sobre schema stitching para versionamento
- GraphQL Custom Directives — Documentação sobre criação de diretivas personalizadas para controle de versão