🇧🇷 Bom ponto que o @rponte traz aqui. Existem algumas ferramentas possíveis para resolver isso em arquiteturas distribuídas e orientadas à mensagens. Uma delas é um pattern fundamental nesse tipo de ambiente chamado Transactional Outbox.
twitter.com/rponte/status/1505745458878103557
O Transactional Outbox Consiste em:
1) Numa mesma transação na BD salvar um registro na tabela correspondente e a mensagem produzida por essa ação numa segunda tabela chamada Outbox;
2) Ter uma thread em background a fazer polling no Outbox pra publicar a mensagem no broker;
Esse pattern traz a garantia de que mensagens - comumente eventos - são produzidas de maneira consistente à alterações de estados em registros.
Problemas? Esse pattern assume uma garantia de entrega de mensagens chamada de at-least-once. Isso quer dizer que a mensagem produzida, vai ser publicada ao menos uma vez no broker e pode - excepcionalmente - ser publicada mais de uma vez.
Como a mensagem pode ser publicada mais de uma vez? Imagine que o Message Relay leu a mensagem, enviou para o broker mas antes de salvar na Outbox que a mensagem foi publicada, a aplicação falha. No restart da app, o Relay vai ver a mensagem ainda no outbox e republicá-la.
Como prevenir isso? Ao trabalhar com at-least-once, é importante que os seus consumidores de mensagens sem idempotentes: isso é, caso eles recebam a mesma mensagem mais de uma vez, o estado não pode se alterar / o comportamento tem de ser o mesmo.
Por exemplo: uma mensagem de UsuarioCriado é produzida junto com um novo usuário criado, e existe um consumidor pra essa mensagem que dispara um e-mail de boas vindas. Um consumidor idempotente poderia receber a mesma mensagem n vezes, mas não dispararia e-mail mais de uma vez.
Building blocks do outbox:
Na infraestrutura: uma BD com suporte à transações e um message broker.
Nas aplicações produtoras: background thread pra fazer polling, publicar a mensagem e gerenciar o outbox.
Nas aplicações consumidoras: consumidores idempotentes.
Existem várias libs hoje que já facilitam esse trabalho de implementação do outbox nas mais diversas linguagens. No C#, por exemplo, temos o CAP - cap.dotnetcore.xyz/ - que é um framework com outbox/inbox já implementado pra produzir/consumir mensagens.
Sistemas distribuídos são lindos - eu acho pelo menos - mas cheios de pequenos detalhes. Não basta adicionar um message broker e achar que todos seus problemas estão resolvidos. Ao mesmo tempo, um mundo incrível de se estudar!