Introdução ao padrão event-carried state transfer
1. Fundamentos do Event-Carried State Transfer (ECST)
O padrão Event-Carried State Transfer (ECST) surge como uma evolução natural das arquiteturas orientadas a eventos, resolvendo um problema crítico: como distribuir dados entre microsserviços sem criar dependências síncronas de banco de dados centralizado.
Diferentemente dos eventos de notificação tradicionais — que carregam apenas um identificador e exigem que o consumidor busque dados adicionais — o ECST embute no próprio evento o estado completo do agregado no momento da publicação. Isso elimina a necessidade de consultas HTTP ou queries em bancos remotos.
A origem do padrão está ligada à popularização de sistemas distribuídos que priorizam disponibilidade e tolerância a partições (teorema CAP). Em vez de sacrificar a consistência imediata, o ECST aceita consistência eventual em troca de resiliência e baixa latência.
2. Estrutura de um Evento com Estado Transportado
Um evento ECST típico contém:
{
"eventId": "e7b8c9d0-1234-5678-abcd-ef0123456789",
"eventType": "OrderPlaced",
"timestamp": "2025-04-01T14:30:00Z",
"version": 2,
"payload": {
"orderId": "ORD-98765",
"customer": {
"id": "CUST-123",
"name": "Maria Silva",
"email": "maria@exemplo.com",
"shippingAddress": "Rua A, 100"
},
"items": [
{
"productId": "PROD-456",
"name": "Teclado Mecânico",
"quantity": 1,
"unitPrice": 249.90
}
],
"total": 249.90,
"status": "confirmed"
}
}
O versionamento do payload é essencial. Recomenda-se:
- Adicionar campo
versionno envelope do evento - Usar Avro ou Protobuf para schemas evolutivos
- Manter compatibilidade retroativa por pelo menos 3 versões
3. Comparação com Padrões Alternativos
| Característica | ECST | Event Notification | Event Sourcing |
|---|---|---|---|
| Dados no evento | Estado completo | Apenas ID | Eventos de mudança |
| Consulta necessária | Não | Sim (HTTP/DB) | Sim (reconstrução) |
| Latência de leitura | Baixa | Alta | Alta |
| Complexidade | Média | Baixa | Alta |
ECST vs Event Notification: O primeiro elimina a viagem de ida-e-volta para buscar dados. No cenário de notificação, o consumidor recebe apenas "orderId": "ORD-98765" e precisa chamar um endpoint para obter os detalhes.
ECST vs Event Sourcing: No Event Sourcing puro, cada mudança é um evento atômico e o estado atual é reconstruído pelo log. No ECST, o evento já carrega o estado final, simplificando consumidores que precisam apenas de leitura.
4. Cenários de Uso Típicos
Sincronização entre microsserviços: Um serviço de catálogo publica ProductUpdated com todos os atributos. O serviço de busca e o serviço de front-end consomem o mesmo evento sem depender de um banco central.
Read models customizados: Cada cliente pode construir sua própria projeção dos dados. Um serviço de recomendações pode filtrar apenas produtos com estoque > 0 a partir dos eventos recebidos.
Cache distribuído: Dados de referência (categorias, perfis de usuário) são replicados localmente em cada serviço consumidor, eliminando chamadas de rede para leitura.
5. Desafios e Armadilhas na Implementação
Inflação do tamanho do evento: Se um agregado tem 50 campos, o evento pode chegar a 10 KB. Em alta frequência (1000 eventos/s), o throughput pode chegar a 10 MB/s, impactando rede e armazenamento.
Consistência eventual: Se o consumidor processa eventos fora de ordem, pode exibir dados desatualizados. Estratégias como timestamps monotônicos e detecção de versões antigas são necessárias.
Duplicação de dados: Cada serviço mantém sua própria cópia do estado. Isso aumenta o armazenamento total e exige coordenação para atualizações.
6. Estratégias de Versionamento e Evolução
Adição de campos opcionais: Novos campos devem ter defaults definidos no consumidor:
{
"productId": "PROD-456",
"name": "Teclado Mecânico",
"price": 249.90,
"stock": 100,
"category": null, // novo campo opcional
"rating": null // novo campo opcional
}
Schemas evolutivos com Protobuf:
syntax = "proto3";
message ProductUpdated {
string product_id = 1;
string name = 2;
double price = 3;
int32 stock = 4;
optional string category = 5; // campo adicionado
optional double rating = 6; // campo adicionado
}
Migração gradual: Publicar eventos com ambas as versões do schema por um período de transição, permitindo que consumidores antigos e novos coexistam.
7. Boas Práticas de Design e Operação
Contrato compartilhado: Manter os schemas de eventos em um repositório Git centralizado, versionado e revisado por todas as equipes.
Monitoramento: Rastrear métricas como:
- Tamanho médio do evento (alerta > 50 KB)
- Frequência de publicação por tipo
- Taxa de reprocessamento
Padrão Outbox: Garantir que o evento seja publicado atomicamente com a transação do banco de dados:
1. Iniciar transação
2. Atualizar estado no banco
3. Inserir evento na tabela outbox
4. Comitar transação
5. Publicar evento a partir da outbox
8. Exemplo Prático: Catálogo de Produtos Distribuído
Serviço Produtor (catálogo) publica:
{
"eventId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"eventType": "ProductUpdated",
"timestamp": "2025-04-01T15:00:00Z",
"version": 3,
"payload": {
"productId": "PROD-789",
"name": "Mouse Gamer",
"price": 189.90,
"stock": 50,
"category": "Periféricos",
"imageUrl": "https://cdn.exemplo.com/mouse.jpg",
"description": "Mouse RGB 6 botões",
"rating": 4.5
}
}
Serviço Consumidor (front-end) mantém cache local:
// Exemplo de lógica do consumidor
function handleProductUpdated(event) {
const product = event.payload;
// Verificar versão para evitar dados antigos
if (product.version < localCache.getVersion(product.productId)) {
return; // Ignorar evento desatualizado
}
// Atualizar cache local sem consulta ao banco
localCache.set(product.productId, {
name: product.name,
price: product.price,
stock: product.stock,
imageUrl: product.imageUrl
});
// Atualizar interface do usuário
renderProductCard(product);
}
Tratamento de falhas: O consumidor deve ser idempotente. Se o mesmo evento for processado duas vezes, o resultado deve ser o mesmo. Isso é garantido usando o productId como chave e verificando a versão.
Reprocessamento: Em caso de falha, o consumidor pode buscar eventos perdidos em um tópico de replay, desde que o produtor mantenha um log de eventos publicados.
O padrão Event-Carried State Transfer é uma ferramenta poderosa para arquiteturas de microsserviços que precisam de leituras rápidas e baixo acoplamento. Quando combinado com boas práticas de versionamento, idempotência e monitoramento, ele oferece um equilíbrio entre simplicidade e robustez.
Referências
- Martin Fowler: Event-Carried State Transfer — Artigo original de Martin Fowler descrevendo o padrão e suas variações.
- Microsoft Azure: Event-driven architecture style — Guia oficial da Microsoft sobre arquiteturas orientadas a eventos.
- Confluent: Event Carried State Transfer with Kafka — Tutorial prático de implementação do ECST usando Apache Kafka.
- Uber Engineering: Schemas evolutivos com Protobuf — Artigo técnico sobre versionamento de schemas em sistemas distribuídos.
- AWS: Event sourcing and CQRS patterns — Documentação da AWS sobre padrões de eventos e CQRS.