Padrões de comunicação assíncrona entre bounded contexts
1. Fundamentos da comunicação entre bounded contexts
Em uma arquitetura baseada em Domain-Driven Design (DDD), um bounded context representa um limite explícito onde um modelo de domínio específico é aplicado. Cada contexto possui sua própria linguagem ubíqua, regras e responsabilidades, o que exige mecanismos de comunicação bem definidos para integração.
A comunicação entre bounded contexts pode ser síncrona ou assíncrona. A comunicação síncrona (como chamadas HTTP diretas) é simples de implementar, mas introduz acoplamento temporal e fragilidade: se um contexto falha, o outro também falha. A comunicação assíncrona, por outro lado, permite desacoplamento, resiliência e escalabilidade, sendo ideal para cenários onde a consistência imediata não é obrigatória.
Os princípios fundamentais incluem:
- Eventos de domínio: notificações sobre mudanças de estado relevantes
- Mensagens: estruturas de dados que transitam entre contextos
- Consistência eventual: aceitar que dados estejam temporariamente inconsistentes
2. Eventos de domínio como espinha dorsal da integração
Eventos de domínio representam fatos ocorridos no domínio que são relevantes para outros contextos. A modelagem adequada inclui:
// Exemplo de evento de domínio (JSON)
{
"eventId": "e7c3f8a1-9b2d-4e5f-8a6c-1d2e3f4a5b6c",
"eventType": "PedidoCriado",
"version": 1,
"timestamp": "2025-03-20T14:30:00Z",
"data": {
"pedidoId": "PED-001",
"clienteId": "CLI-123",
"valorTotal": 250.00,
"itens": [
{"produtoId": "PROD-456", "quantidade": 2, "precoUnitario": 125.00}
]
}
}
A publicação e assinatura de eventos utilizam message brokers como RabbitMQ ou Kafka:
// Publicação de evento no RabbitMQ (pseudo-código)
channel.basicPublish(
exchange: "pedidos.exchange",
routingKey: "pedido.criado",
body: JSON.stringify(pedidoCriadoEvent),
properties: {
deliveryMode: 2, // persistente
contentType: "application/json"
}
);
Para garantir idempotência no consumo, cada evento deve conter um identificador único que permita detectar duplicatas:
// Handler com idempotência
function handlePedidoCriado(event) {
if (processedEvents.has(event.eventId)) {
return; // já processado
}
processedEvents.add(event.eventId);
// lógica de negócio
}
3. Padrão Event-Driven com filas de mensagens
A arquitetura publish/subscribe permite que um contexto publique eventos sem conhecer os consumidores. As filas de mensagens oferecem recursos como roteamento, tópicos e filas de dead-letter para mensagens que falharam repetidamente:
// Configuração de fila com dead-letter no RabbitMQ
channel.assertQueue("pedidos.fila", {
durable: true,
deadLetterExchange: "pedidos.dlx",
deadLetterRoutingKey: "pedidos.falha",
messageTtl: 30000 // 30 segundos
});
Para tratamento de falhas, implementa-se políticas de retry com backoff exponencial:
// Retry policy
async function processarMensagem(mensagem) {
try {
await processar(mensagem);
channel.ack(mensagem);
} catch (error) {
const retryCount = (mensagem.properties.headers['x-retry-count'] || 0) + 1;
if (retryCount <= 3) {
mensagem.properties.headers['x-retry-count'] = retryCount;
setTimeout(() => channel.nack(mensagem, false, true), retryCount * 5000);
} else {
channel.nack(mensagem, false, false); // para dead-letter
}
}
}
4. Saga coreografada para transações distribuídas
Uma saga coreografada coordena transações distribuídas através de eventos encadeados entre contextos. Cada contexto executa sua ação local e publica um evento que dispara a próxima etapa:
// Saga de criação de pedido
1. Contexto Pedidos: Cria pedido (status: pendente)
→ Publica: PedidoCriado
2. Contexto Estoque: Reserva itens
→ Publica: EstoqueReservado ou EstoqueInsuficiente
3. Contexto Pagamento: Processa pagamento
→ Publica: PagamentoAprovado ou PagamentoRecusado
4. Contexto Pedidos: Atualiza status do pedido
→ Publica: PedidoConfirmado ou PedidoCancelado
Para ações de compensação:
// Handler de compensação no contexto Estoque
function handlePagamentoRecusado(event) {
// Libera itens reservados
liberarEstoque(event.data.pedidoId);
// Publica evento de compensação
publishEvent("EstoqueLiberado", { pedidoId: event.data.pedidoId });
}
O monitoramento de sagas é essencial:
// Tabela de monitoramento de saga
sagaId: "SAGA-001"
estado: "EM_ANDAMENTO"
etapaAtual: "RESERVANDO_ESTOQUE"
timeout: "2025-03-20T15:00:00Z"
eventosRecebidos: ["PedidoCriado"]
eventosPendentes: ["EstoqueReservado", "PagamentoAprovado"]
5. CQRS e separação de comandos e consultas
O padrão CQRS (Command Query Responsibility Segregation) separa operações de escrita (comandos) e leitura (consultas). A sincronização entre os modelos de escrita e leitura ocorre via eventos assíncronos:
// Comando (escrita) - processado no contexto de origem
command: CriarPedido
handler: {
valida dados do pedido,
salva no banco de escrita,
publica evento PedidoCriado
}
// Projeção (leitura) - atualizada assincronamente
projeção: PedidosResumo
handler: {
recebe evento PedidoCriado,
atualiza materialized view no banco de leitura
}
As materialized views otimizam consultas frequentes:
// Materialized view para consultas de pedidos
CREATE MATERIALIZED VIEW pedidos_resumo AS
SELECT
p.id,
p.cliente_nome,
p.valor_total,
p.status,
COUNT(pi.id) AS quantidade_itens
FROM pedidos p
JOIN pedidos_itens pi ON p.id = pi.pedido_id
GROUP BY p.id;
6. Integração com APIs assíncronas e callbacks
Webhooks permitem que um contexto notifique outro via HTTP, sem polling constante:
// Configuração de webhook
POST /webhooks/registrar
{
"contexto": "estoque",
"eventos": ["EstoqueBaixo", "ProdutoIndisponivel"],
"callbackUrl": "https://pedidos.api/webhooks/estoque"
}
// Notificação via webhook
POST https://pedidos.api/webhooks/estoque
{
"eventType": "EstoqueBaixo",
"data": {
"produtoId": "PROD-456",
"quantidadeAtual": 5,
"quantidadeMinima": 10
}
}
Para request-response assíncrono, utiliza-se correlação de mensagens:
// Envio de requisição assíncrona
const correlationId = uuid.v4();
const replyQueue = await channel.assertQueue('', { exclusive: true });
channel.consume(replyQueue.queue, (msg) => {
if (msg.properties.correlationId === correlationId) {
resolve(JSON.parse(msg.content.toString()));
}
});
channel.sendToQueue('estoque.requests', Buffer.from(JSON.stringify(request)), {
correlationId: correlationId,
replyTo: replyQueue.queue
});
7. Tratamento de consistência e conflitos
A consistência eventual é um pilar da comunicação assíncrona. Estratégias para resolução de conflitos incluem:
// Última escrita vence (Last Write Wins)
if (event.timestamp > currentData.lastUpdated) {
aplicarAtualizacao(event.data);
}
// Versionamento otimista
if (event.version === currentData.version + 1) {
aplicarAtualizacao(event.data);
} else {
registrarConflito(event);
// pode ser resolvido manualmente ou com merge automático
}
A garantia de entrega varia entre exactly-once (ideal, mas difícil) e at-least-once (mais comum, exige idempotência):
// At-least-once com idempotência
function processarEvento(event) {
if (eventosProcessados.has(event.eventId)) {
return; // duplicata ignorada
}
eventosProcessados.add(event.eventId);
// lógica de processamento
eventosProcessados.persist(); // salva estado após processamento
}
8. Monitoramento e governança da comunicação assíncrona
O rastreamento distribuído utiliza correlation IDs para rastrear o fluxo de mensagens entre contextos:
// Correlation ID propagado em todas as mensagens
{
"correlationId": "CORR-001",
"spanId": "SPAN-A",
"parentSpanId": null,
"eventType": "PedidoCriado",
"data": { ... }
}
Métricas essenciais para monitoramento:
// Métricas a serem coletadas
- Latência média de processamento por evento
- Throughput (eventos/segundo)
- Tamanho da fila de mensagens pendentes
- Taxa de erro por tipo de evento
- Número de mensagens em dead-letter
O versionamento de contratos permite evolução segura:
// Versionamento de evento
eventType: "PedidoCriado"
version: 2
// Campo antigo removido, novo campo adicionado
data: {
"pedidoId": "PED-001",
"cliente": { "id": "CLI-123", "nome": "João" }, // novo formato
"valorTotal": 250.00,
"moeda": "BRL" // novo campo
}
Para backward compatibility, consumidores devem aceitar múltiplas versões:
function handlePedidoCriado(event) {
if (event.version === 1) {
// compatibilidade com versão antiga
const clienteId = event.data.clienteId;
// ...
} else if (event.version === 2) {
// novo formato
const clienteId = event.data.cliente.id;
// ...
}
}
Referências
- Microsoft - Comunicação assíncrona em microsserviços — Documentação oficial da Microsoft sobre padrões de comunicação assíncrona entre microsserviços
- RabbitMQ - Tutorial de filas de mensagens — Guia prático para implementação de filas de mensagens com RabbitMQ
- Apache Kafka - Documentação de Event-Driven Architecture — Documentação oficial do Apache Kafka sobre processamento de eventos
- Martin Fowler - Event Sourcing e CQRS — Artigo do Martin Fowler sobre padrões de Event Sourcing e CQRS
- AWS - Padrão Saga para transações distribuídas — Guia da AWS para implementação do padrão Saga em arquiteturas distribuídas
- DDD Community - Bounded Context Integration — Comunidade DDD com recursos sobre integração entre bounded contexts