Event versioning: evoluindo schemas sem quebrar consumidores
1. Fundamentos da Evolução de Schemas
1.1. O dilema da mudança: acoplamento temporal entre produtores e consumidores
Em sistemas orientados a eventos, produtores e consumidores evoluem em ritmos diferentes. Um produtor pode precisar adicionar campos para atender novos requisitos de negócio, enquanto consumidores podem levar semanas ou meses para serem atualizados. Esse desalinhamento temporal cria um acoplamento frágil: qualquer mudança no schema do evento pode quebrar consumidores que ainda esperam a estrutura antiga.
1.2. Compatibilidade direta (forward) vs. compatibilidade reversa (backward)
- Compatibilidade backward: consumidores antigos conseguem processar eventos novos. Exige que campos adicionados sejam opcionais ou tenham valores default.
- Compatibilidade forward: consumidores novos conseguem processar eventos antigos. Exige que campos desconhecidos sejam ignorados.
1.3. Implicações da falta de versionamento
Sem versionamento adequado, os sintomas são clássicos:
- Filas mortas acumulando eventos não processáveis
- Falhas silenciosas onde consumidores ignoram campos críticos
- Retrabalho manual para reprocessar eventos corrompidos
- Rollbacks emergenciais que afetam todo o pipeline
2. Estratégias de Versionamento de Eventos
2.1. Versionamento no schema
A abordagem mais explícita: criar tipos separados para cada versão.
event OrderPlacedV1 {
orderId: string
customerId: string
totalAmount: decimal
}
event OrderPlacedV2 {
orderId: string
customerId: string
totalAmount: decimal
discountCode: string?
}
Prós: clareza total sobre qual versão está sendo usada. Contras: duplicação de código e proliferação de tipos.
2.2. Versionamento no campo de metadados
Usar um campo eventVersion dentro de um envelope padronizado.
{
"eventType": "OrderPlaced",
"eventVersion": 2,
"timestamp": "2024-01-15T10:30:00Z",
"payload": {
"orderId": "ORD-123",
"customerId": "CUST-456",
"totalAmount": 299.90,
"discountCode": "PROMO10"
}
}
Prós: flexibilidade para evoluir sem criar novos tipos. Contras: consumidores precisam interpretar a versão e aplicar lógica condicional.
2.3. Versionamento semântico de eventos
Aplicar regras de major/minor/patch:
- Major (
v2.0.0): mudança incompatível (remoção de campo obrigatório) - Minor (
v1.1.0): adição backward-compatível (campo opcional) - Patch (
v1.0.1): correção sem alteração de schema
3. Compatibilidade Backward (Consumidores Antigos com Eventos Novos)
3.1. Adição segura de campos opcionais
// Schema V1 (original)
event OrderPlaced {
orderId: string
customerId: string
totalAmount: decimal
}
// Schema V2 (backward compatível)
event OrderPlaced {
orderId: string
customerId: string
totalAmount: decimal
discountCode: string? // novo campo opcional
}
Consumidores V1 simplesmente ignoram discountCode.
3.2. Remoção gradual de campos
// Schema V2 (depreciação)
event OrderPlaced {
orderId: string
customerId: string
totalAmount: decimal
discountCode: string?
shippingAddress: string? // deprecated, manter por 60 dias
}
// Schema V3 (remoção completa)
event OrderPlaced {
orderId: string
customerId: string
totalAmount: decimal
discountCode: string?
}
3.3. Renomeação e mudança de tipo
Usar aliasing para renomear campos sem quebrar consumidores:
// Schema V1 (original)
event UserCreated {
userName: string
}
// Schema V2 (aliasing)
event UserCreated {
userName: string // manter por compatibilidade
displayName: string // novo nome
}
4. Compatibilidade Forward (Consumidores Novos com Eventos Antigos)
4.1. Uso de schemas abertos
Schemas que permitem campos adicionais não declarados:
event FlexibleEvent {
eventType: string
eventVersion: int
// open content: campos adicionais são permitidos
additionalProperties: true
}
4.2. Versionamento de envelope
Estrutura que separa metadados do payload:
{
"envelope": {
"eventType": "OrderPlaced",
"eventVersion": 1,
"schemaId": "order-placed-v1"
},
"data": {
"orderId": "ORD-123",
"customerId": "CUST-456"
}
}
Consumidores novos usam schemaId para buscar o schema correto.
4.3. Estratégias de upgrade
- Blue/green deployment: manter consumidores antigos rodando até que todos migrem
- Canary release: direcionar 10% do tráfego para consumidores novos, monitorar erros
5. Ferramentas e Governança com Schema Registry
5.1. Papel do schema registry
O schema registry atua como autoridade central que valida automaticamente a compatibilidade entre versões:
# Registro do schema V1
schema-registry register --subject OrderPlaced-value --schema order-placed-v1.avsc
# Tentativa de registro do schema V2
schema-registry register --subject OrderPlaced-value --schema order-placed-v2.avsc
# Validação automática: backward compatível? Sim -> Registro aprovado
5.2. Configuração de regras de evolução
BACKWARD: consumidores antigos processam eventos novos (default)FORWARD: consumidores novos processam eventos antigosFULL: ambas as direçõesNONE: sem validação (uso interno, temporário)
5.3. Ciclo de vida de schemas
1. Registro: schema V1 ativo
2. Evolução: schema V2 registrado com regra BACKWARD
3. Depreciação: schema V1 marcado como deprecated (data de expiração)
4. Remoção: schema V1 removido após janela de transição (ex.: 90 dias)
6. Casos Complexos e Anti-Padrões
6.1. Split de evento
Quando um evento precisa ser dividido em múltiplos eventos:
// Evento original
event OrderPlaced {
orderId: string
customerId: string
paymentInfo: PaymentDetails
shippingInfo: ShippingDetails
}
// Eventos resultantes
event OrderPlaced {
orderId: string
customerId: string
}
event PaymentProcessed {
orderId: string
paymentInfo: PaymentDetails
}
event OrderShipped {
orderId: string
shippingInfo: ShippingDetails
}
6.2. Fusão de eventos
Correlacionar eventos usando correlationId e janelas temporais:
// Eventos separados
event PaymentReceived {
correlationId: "CORR-001"
amount: 299.90
}
event OrderConfirmed {
correlationId: "CORR-001"
orderId: "ORD-123"
}
// Fusão via stream processing (janela de 5 minutos)
SELECT correlationId, amount, orderId
FROM PaymentReceived.window(5 minutes) AS p
JOIN OrderConfirmed.window(5 minutes) AS o
ON p.correlationId = o.correlationId
6.3. Anti-padrões
- Versionamento no nome do tópico:
orders-v1,orders-v2— duplica infraestrutura - Ausência de metadados: sem
eventVersionouschemaId, impossível rastrear versão - Quebra silenciosa: remover campos sem depreciação prévia
7. Exemplo Prático: Evolução de um Evento de Pedido
7.1. Cenário inicial
// Schema V1 - OrderPlaced
event OrderPlaced {
orderId: string (obrigatório)
customerId: string (obrigatório)
totalAmount: decimal (obrigatório)
shippingAddress: string (obrigatório)
}
7.2. Evolução 1: adição de discountCode
// Schema V2 - OrderPlaced (backward compatível)
event OrderPlaced {
orderId: string (obrigatório)
customerId: string (obrigatório)
totalAmount: decimal (obrigatório)
shippingAddress: string (obrigatório)
discountCode: string? (opcional, novo campo)
}
Consumidores V1 continuam funcionando — ignoram discountCode.
7.3. Evolução 2: remoção gradual de shippingAddress
// Schema V3 - OrderPlaced (depreciação de shippingAddress)
event OrderPlaced {
orderId: string (obrigatório)
customerId: string (obrigatório)
totalAmount: decimal (obrigatório)
shippingAddress: string? (deprecated - manter por 60 dias)
discountCode: string? (opcional)
}
// Schema V4 - OrderPlaced (remoção completa)
event OrderPlaced {
orderId: string (obrigatório)
customerId: string (obrigatório)
totalAmount: decimal (obrigatório)
discountCode: string? (opcional)
}
Cronograma de migração:
- Dia 0: registrar schema V3 com shippingAddress deprecated
- Dia 30: notificar consumidores sobre a remoção iminente
- Dia 60: registrar schema V4 sem shippingAddress
- Dia 90: remover schema V3 do registry
8. Considerações Finais e Boas Práticas
8.1. Prefira adições a remoções
Cada remoção de campo é uma potencial quebra. Sempre que possível, adicione campos opcionais em vez de modificar ou remover existentes.
8.2. Documente mudanças em changelogs
## Changelog de Schemas
### 2024-01-15 - OrderPlaced V2
- Adicionado campo opcional: discountCode (string?)
- Compatibilidade: backward
### 2024-03-01 - OrderPlaced V3
- Marcado como deprecated: shippingAddress
- Data de remoção prevista: 2024-05-01
8.3. Automatize validação no CI/CD
# pipeline.yml
steps:
- name: Validate Schema Compatibility
run: |
schema-registry compatibility-check \
--subject OrderPlaced-value \
--schema order-placed-v3.avsc \
--type BACKWARD
A automação evita que mudanças incompatíveis cheguem à produção.
Referências
- Confluent Schema Registry Documentation — Documentação oficial do schema registry da Confluent, incluindo regras de compatibilidade e ciclo de vida de schemas.
- Avro Schema Evolution Guide — Especificação oficial do Apache Avro sobre evolução de schemas, com exemplos de compatibilidade backward e forward.
- Event Versioning Strategies by Martin Fowler — Artigo clássico de Martin Fowler sobre estratégias de versionamento de eventos em sistemas orientados a eventos.
- Kafka Streams: Schema Evolution with Avro — Guia oficial do Apache Kafka sobre evolução de schemas com Avro e schema registry.
- Event Sourcing and Event Versioning by Chris Richardson — Artigo técnico sobre versionamento de eventos no contexto de event sourcing, com exemplos práticos de migração.