Atualização editorial em : atribuições e orientações corrigidas com fontes; a data da publicação original foi preservada.
Em um fluxo orientado a eventos, receber uma mensagem não basta para decidir se ela ainda representa o estado válido. Antes de escolher cache, broker ou número de consumidores, vale definir o que fazer com uma atualização atrasada, repetida ou contraditória.
O relato de origem e o que ele não demonstra
Sagar Deepak Joshi descreve mudanças em uma plataforma de contact center no artigo Tradeoffs in Event-Driven Design, publicado em 30 de junho de 2026. O autor relata a troca de uma estratégia de cache local por estado compartilhado e uma redução de 60% no tempo de inicialização daquele sistema. É um resultado relatado pelo autor, não um teste deste site nem uma comparação controlada que determine a escolha para outros sistemas.
Kafka e Redis não precisam disputar uma única função. Na avaliação de uma arquitetura, separe a necessidade de manter histórico e reprocessar mensagens da necessidade de consultar o estado atual. Depois registre quem pode atualizar esse estado, qual inconsistência é aceitável e como recuperar uma atualização perdida. A presença de um cache compartilhado, por si só, não responde a essas perguntas.
Timeout de leitura não é atraso mínimo obrigatório
A documentação do KafkaConsumer 4.2 especifica que poll(Duration) retorna imediatamente quando há registros disponíveis. O timeout determina a espera quando não há registros; callbacks de rebalanceamento podem ultrapassá-lo. Portanto, passar 100 ms não impõe um atraso mínimo de 100 ms a cada leitura. A mesma documentação informa que o consumidor não é thread-safe.
Isso não promete baixa latência. Para investigar um atraso real, meça separadamente produção, disponibilidade para consumo, processamento e atualização da consulta. Não atribua todo o intervalo ao broker apenas pela configuração de timeout.
Um contrato pequeno para estados atrasados
O exemplo abaixo é uma proposta didática independente do caso citado. Cada mensagem traz um snapshot completo de um agente: identificador, versão e estado. A versão vem de uma única autoridade por entidade, cresce de forma monotônica e não é o horário do computador.
- Versão maior: substituir o snapshot.
- Versão menor: preservar o atual e informar atraso.
- Mesma versão e mesmo estado: informar duplicação.
- Mesma versão e estado diferente: sinalizar conflito sem sobrescrever.
Um salto de versão é aceito somente porque o snapshot contém o estado completo deste contrato. Não use a regra para incrementos de saldo, deltas de estoque ou eventos que precisam produzir um efeito a cada ocorrência.
'use strict';
function validar(snapshot) {
if (!snapshot || Object.getPrototypeOf(snapshot) !== Object.prototype ||
Object.keys(snapshot).sort().join(',') !== 'estado,id,versao' ||
typeof snapshot.id !== 'string' || !snapshot.id.trim() ||
snapshot.id !== snapshot.id.trim() ||
!Number.isSafeInteger(snapshot.versao) || snapshot.versao < 0 ||
!['disponivel', 'em-chamada', 'indisponivel'].includes(snapshot.estado)) {
throw new TypeError('Snapshot invalido');
}
}
// Contrato: snapshots completos, sequencia monotona por entidade e um produtor.
function aplicarSnapshot(atual, recebido) {
validar(recebido);
if (atual !== null) {
validar(atual);
if (atual.id !== recebido.id) throw new Error('Entidades diferentes');
if (recebido.versao < atual.versao) {
return { resultado: 'atrasado', snapshot: { ...atual } };
}
if (recebido.versao === atual.versao) {
return {
resultado: recebido.estado === atual.estado ? 'duplicado' : 'conflito',
snapshot: { ...atual }
};
}
}
return { resultado: 'aplicado', snapshot: { ...recebido } };
}
module.exports = { aplicarSnapshot };
Entradas e resultado reproduzível
| Entrada | Estado recebido | Resultado |
|---|---|---|
| Versão 2 | em-chamada | aplicado |
| Versão 1 | disponivel | atrasado |
| Versão 2 | em-chamada | duplicado |
| Versão 3 | disponivel | aplicado |
| Versão 3 | indisponivel | conflito |
O estado final permanece na versão 3, disponível. O conflito é retornado ao chamador; em uma aplicação, seria necessário decidir como registrá-lo e reconciliar a origem.
Arquivos: função, entradas, execução e testes. Com os quatro arquivos na mesma pasta, execute node executar.js e node --test test.js.
Codex executou este exemplo localmente em Node.js: os oito testes passaram. Eles cobrem versões, conflito, validação, preservação das entradas e permutações sem conflito. Não foram executados Kafka, Redis ou um benchmark de produção.
Limites antes de levar a regra a um sistema
A função só compara com a versão atual; não detecta divergências históricas já superadas. Havendo duas mensagens contraditórias da mesma versão, a primeira recebida pode determinar o estado mantido. A convergência testada pressupõe snapshots consistentes.
O exemplo não persiste dados, não coordena consumidores concorrentes e não garante entrega exatamente uma vez. Em armazenamento compartilhado, a comparação e a escrita precisariam ser atômicas. Reinício, perda de estado e efeitos externos exigem testes próprios. O texto é uma análise baseada em fontes com um exercício sintético, não uma experiência profissional atribuída a Anderson Damasio.