Event-Driven Architecture: As Partes que Você Precisa Conhecer!
Com a evolução dos sistemas distribuídos e a crescente necessidade de flexibilidade e escalabilidade, a arquitetura dirigida por eventos surge como uma abordagem poderosa. Em vez de depender de interações diretas e imediatas entre serviços, a EDA permite que eles operem de forma independente, reagindo a eventos conforme ocorrem no sistema. Mas o que torna essa arquitetura tão eficaz? Quais são os componentes que permitem que os sistemas fluam de maneira organizada, mesmo em cenários complexos e dinâmicos? Vamos explorar como a EDA redefine a forma como os sistemas se comunicam, mantendo a autonomia e a resiliência que as empresas modernas tanto buscam.
Por que se chama Arquitetura Dirigida por Eventos?
É porque os eventos são o ponto central. Mas o que são eventos? Eles são fatos que indicam que algo importante ocorreu no sistema. Pode ser uma interação do usuário com o sistema, como finalizar uma compra, ou uma operação técnica, como um sensor de IoT detectando uma mudança de temperatura.
Eventos indicam que algo significativo aconteceu no sistema. Geralmente, eles representam uma mudança de estado, mas também podem envolver outras ocorrências importantes que precisam ser comunicadas aos serviços interessados. No entanto, não forçam uma resposta. Cada serviço que consome o evento decide se vai reagir, como vai reagir, ou até se vai ignorar. Quando um evento é emitido, ele apenas informa que algo aconteceu, deixando para os outros serviços a decisão sobre o que fazer com essa informação.
Em outras palavras, o evento cria uma oportunidade para uma ação, mas não a impõe.
O que torna isso diferente de outras arquiteturas?
Em muitas arquiteturas tradicionais, como o modelo de requisição-resposta, um serviço precisa chamar diretamente outro serviço e esperar uma resposta imediata para continuar o seu processamento. Isso cria uma relação de dependência direta entre os serviços, gerando um acoplamento temporal. Ou seja, os serviços ficam presos uns aos outros: se um falha, o outro pode falhar também. Essa interdependência direta pode limitar a escalabilidade e a flexibilidade do sistema, especialmente em ambientes distribuídos.
Já na EDA, o cenário é bem diferente. Quando um evento ocorre, o serviço que gerou esse evento simplesmente dispara o evento em si e segue seu curso. Ele não precisa se preocupar em saber:
Quem irá consumir o evento.
Como os outros serviços vão processá-lo.
Quando o evento será processado.
Essa desconexão intencional entre os serviços é chamada de desacoplamento.
De onde surgiu o conceito de Arquitetura Dirigida por Eventos?
O conceito de Arquitetura Dirigida por Eventos não surgiu da noite para o dia — ele é fruto de uma evolução que remonta às primeiras tentativas de criar sistemas assíncronos e distribuídos, muito antes da era da computação em nuvem e dos microserviços. Para entender de onde essa abordagem vem, precisamos voltar no tempo e examinar como as necessidades da comunicação entre sistemas foram evoluindo ao longo das últimas décadas.
Raízes nas Arquiteturas Assíncronas e Sistemas de Mensageria
Nos anos 1960 e 1970, quando os computadores começaram a se comunicar entre si, a comunicação síncrona — onde um sistema faz uma solicitação e espera uma resposta imediata — era a abordagem padrão. No entanto, à medida que os sistemas se tornavam mais distribuídos e as redes se expandiam, essa abordagem começou a mostrar suas limitações. Redes lentas ou instáveis podiam interromper a comunicação, o que tornava os sistemas frágeis e difíceis de escalar.
Foi aí que os primeiros experimentos com arquiteturas assíncronas começaram a surgir. A ideia era permitir que os sistemas trocassem mensagens sem precisar esperar por uma resposta imediata, criando uma fila de mensagens entre eles. Esse conceito de troca de mensagens assíncronas serviu como a base para o que mais tarde seria chamado de arquitetura orientada a eventos.
Na década de 1980, empresas como a IBM começaram a trabalhar em soluções de mensageria corporativa que implementavam essas ideias. O IBM MQ (Message Queue), lançado em 1993, foi uma das primeiras ferramentas que tornaram possível a troca de mensagens entre sistemas de forma confiável e assíncrona, utilizando filas de mensagens. Ele se tornou rapidamente um padrão no mercado para sistemas corporativos que exigiam alta confiabilidade e resiliência. Essas filas garantiam que as mensagens (ou eventos) pudessem ser armazenadas e reprocessadas em caso de falha.
Adoção de Padrões de Mensageria como o JMS
Ainda nos anos 1990, a tecnologia continuou a evoluir. A Sun Microsystems lançou o Java Message Service (JMS) em 1998, que se tornou um dos padrões mais amplamente adotados para mensageria assíncrona. O JMS permitia que diferentes sistemas pudessem trocar mensagens de forma confiável, sem que os sistemas emissores precisassem saber o que os sistemas receptores faziam com essas mensagens. Esse era um primeiro passo em direção ao desacoplamento entre os componentes do sistema, um dos principais pilares da arquitetura orientada a eventos.
Esses avanços trouxeram soluções para problemas comuns em sistemas distribuídos, como perda de mensagens, gargalos de processamento, e a necessidade de escalabilidade horizontal. Isso preparou o terreno para que, nas décadas seguintes, a orientação a eventos se tornasse uma parte essencial das arquiteturas modernas.
A Revolução dos Microserviços e a Computação em Nuvem
Apesar desses avanços, o conceito de arquitetura orientada a eventos ganhou ainda mais força com o surgimento das arquiteturas de microserviços no início dos anos 2010, e com o crescimento explosivo da computação em nuvem.
Empresas como Netflix, Amazon, e Google começaram a adotar arquiteturas baseadas em microserviços para lidar com suas operações em larga escala. Com isso, a necessidade de comunicação assíncrona e desacoplada entre serviços independentes aumentou ainda mais. Esses gigantes da tecnologia perceberam que a comunicação síncrona (como as chamadas diretas de API REST) não era viável para sistemas globais que precisavam operar com alta escalabilidade, resiliência e disponibilidade. A arquitetura orientada a eventos surgiu como uma solução natural para esse cenário.
Eventos para todos os lados
Para entender a utilidade da EDA, imagine um cenário familiar: você está dormindo e, de repente, o alarme do despertador toca. Esse som é um evento — um sinal que algo ocorreu. No entanto, o toque em si não obriga nenhuma ação. Você tem a liberdade de decidir como reagir: pode optar por acordar imediatamente, ficar na cama por mais alguns minutos ou até ignorar o alarme completamente. Esse evento cria a oportunidade para uma série de ações – como levantar, ir ao banheiro, preparar o café – mas a escolha de como seguir é sua.
Então depois dessa pequena analogia, fica mais fácil entender que quando um cliente faz um pedido em uma loja online, o evento "pedido realizado" é gerado. Esse evento, por si só, apenas informa que o pedido foi feito e pode desencader várias ações: o serviço de pagamento processa a compra, o estoque verifica a disponibilidade e o cliente recebe uma notificação. Cada serviço reage ao evento de forma independente, sem depender diretamente dos outros, o que torna o sistema mais flexível.
É como se cada peça do sistema fosse um profissional especializado, que apenas age quando algo relacionado à sua especialidade acontece.
Além disso, eventos podem gerar novos eventos. Por exemplo, após o pagamento ser aprovado, o serviço de logística pode ser acionado para enviar o produto. Isso cria um fluxo contínuo de eventos, onde os serviços trabalham de forma desacoplada, reagindo apenas aos eventos que são relevantes para eles.
Eventos são cidadãos de primeira classe na EDA
Na engenharia de software, o conceito de "cidadão de primeira classe" refere-se a algo que é considerado central e essencial dentro de um sistema. Um "cidadão de primeira classe" tem os mesmos privilégios e importância que qualquer outra entidade crítica no sistema. Ele é tratado com prioridade, o que significa que possui um papel claro e decisivo, sem ser secundário ou descartável.
Os eventos, em uma arquitetura dirigida por eventos, são as principais fontes de informação que dirigem o comportamento do sistema. Eles não são apenas "gatilhos" ocasionais – eles são o centro de tudo. Como mencionei no exemplo do despertador, o toque do alarme pode parecer algo pequeno, mas é ele quem dá início, dá o ponta pé inicial para que outros possam reagiar.
Essa abordagem dá ao evento o status de primeira classe porque todo o funcionamento do sistema depende de como os serviços reagem a esses eventos. Sem o evento, não haveria comunicação, não haveria reação, e, portanto, o sistema ficaria estagnado. Quando um evento é emitido, ele carrega informações importantes, vamos falar em breve sobre isso.
Além disso, os eventos são explícitos. Isso quer dizer que, no momento em que um evento é emitido, todos os serviços que precisam daquela informação podem processá-lo. Isso dá clareza e consistência ao sistema. Cada serviço sabe exatamente qual evento escutar e o que fazer com ele. Esse tipo de abordagem coloca os eventos no centro do design do sistema, e isso os transforma em cidadãos de primeira classe, porque são inegociáveis para o bom funcionamento da arquitetura.
Os Tipos de Desacoplamento em uma Arquitetura Dirigida por Eventos
Pense na seguinte situação: você está no trabalho e precisa enviar um e-mail para um colega pedindo informações. Você dispara o e-mail e segue fazendo suas tarefas, sem ficar parado esperando pela resposta. Seu colega pode responder na hora, ou daqui a algumas horas, mas isso não te impede de continuar com o seu dia. Esse cenário exemplifica bem o conceito de desacoplamento temporal.
Quando aplicamos esse conceito à engenharia de software, é exatamente isso que acontece. Um serviço pode "disparar" um evento e seguir em frente, sem depender de uma resposta imediata de outro serviço. Por exemplo, imagine que um sistema de e-commerce registra um pedido. O evento "pedido realizado" é disparado, mas o serviço de pagamento ou de estoque não precisa processá-lo no exato momento em que ele é emitido. Eles podem lidar com o evento quando estiverem prontos. Não há aquela pressão temporal típica das chamadas síncronas, onde um serviço precisa esperar o outro terminar para continuar.
Agora, além do tempo, vamos pensar na responsabilidade. Quando um serviço emite um evento, ele não precisa se preocupar com quem vai consumi-lo ou como isso será feito. Ele só precisa garantir que o evento seja emitido de forma clara. Vamos pegar aquele mesmo exemplo do e-commerce: o serviço de pedidos apenas dispara o evento "pedido realizado". Ele não precisa saber se o serviço de estoque vai atualizar a quantidade de produtos ou se o serviço de logística vai começar a preparar o envio. Quem estiver interessado naquele evento vai agir de acordo com o que faz sentido para sua função. Isso é o que chamamos de desacoplamento funcional. Cada serviço faz o seu trabalho sem depender de saber o que os outros estão fazendo.
Além disso, tem algo que eu gosto de chamar de desacoplamento de domínio. Esse é um ponto que traz muita flexibilidade. Isso é importante porque, se amanhã você decidir adicionar um novo serviço — por exemplo, um serviço de analytics para monitorar o comportamento dos clientes — você não precisa mexer no serviço de pedidos para que ele comece a enviar eventos para o novo serviço. Você pode passar essa responsabilidade para alguém centralizar esses eventos e disponibilizar, assim o novo serviço apenas começa a consumir os eventos.
Esse tipo de desacoplamento faz com que a arquitetura seja muito mais flexível e adaptável. Você pode evoluir o sistema, adicionar novos serviços ou alterar a forma como os eventos são processados sem precisar reestruturar tudo. É como montar um brinquedo de lego onde as peças podem ser trocadas ou reposicionadas
Desacoplamento de dados
Além dos tipos de desacoplamento que já falamos, temporal, funcional e de domínio, tem um outro tipo que vem ganhando muita relevância no desenvolvimento de sistemas modernos: o desacoplamento de dados.
Para entender isso, imagine como funcionam sistemas mais antigos ou monolíticos. Normalmente, todos os serviços ou funcionalidades compartilham um mesmo banco de dados. Isso significa que qualquer alteração nos dados em um lugar pode afetar diretamente várias outras partes do sistema. E mais: qualquer problema nesse banco de dados compartilhado pode impactar o sistema como um todo.
Em arquiteturas de microserviços, os serviços deveriam ser independentes em termos de dados. Mas sabemos que isso nem sempre ocorre!
Em uma arquitetura orientada a eventos, o cenário é diferente. Ao invés de realizar chamadas síncronas para consultar outro serviço, ou compartilhar os mesmos esquemas de dados, um serviço simplesmente escuta os eventos que ele precisa. Esses eventos transportam os dados relevantes para que cada serviço atualize seu próprio banco de dados, sem depender de uma consulta ao vivo ao banco de dados de outro serviço.
Em uma EDA, o Serviço de Pedidos dispara um evento de "Pedido Realizado", contendo todas as informações sobre o pedido (como os itens e quantidades compradas). O Serviço de Estoque escuta esse evento e usa essas informações para atualizar seu banco de dados, diminuindo a quantidade de produtos disponíveis. Da mesma forma, o Serviço de Logística pode ouvir o mesmo evento para iniciar o processo de envio.
Vantagens do Desacoplamento de Dados
1. Eliminação de Dependências Diretas: Os serviços não precisam fazer chamadas síncronas uns aos outros para obter dados. Em vez disso, eles escutam os eventos relevantes, garantindo que cada serviço seja autossuficiente em termos de dados.
2. Alta Resiliência: Se um serviço estiver fora do ar temporariamente, o evento ainda pode ser processado mais tarde, garantindo que o serviço não fique inativo por depender de outro serviço que está offline.
3. Melhor Escalabilidade: Como os serviços não dependem de um banco de dados central ou de consultas diretas a outros bancos de dados, eles podem ser escalados independentemente, sem criar gargalos em chamadas síncronas.
4. Sincronização Assíncrona: A comunicação entre os serviços é feita de forma assíncrona, via eventos, permitindo que cada serviço processe informações em seu próprio ritmo.
5. Redução de Ponto Único de Falha: Sem um banco de dados compartilhado ou dependência de consultas diretas entre serviços, cada serviço pode falhar ou ser atualizado independentemente, sem comprometer o funcionamento do restante do sistema.
Embora o desacoplamento de dados traga várias vantagens, ele também traz o desafio da consistência eventual. Como os serviços processam eventos de forma assíncrona, os dados entre os serviços podem ficar temporariamente desalinhados até que todos os eventos sejam processados.
Portanto, arquitetos de sistemas precisam considerar:
Como garantir que os eventos sejam entregues e processados de forma confiável.
Estratégias para lidar com erros ou falhas na entrega de eventos.
Garantias de consistência – saber quando é aceitável trabalhar com consistência eventual e quando é necessária consistência imediata.
Mas para onde os eventos vão?
E a pergunta que surge é: quem faz essa ponte? Quem é responsável por garantir que o evento saia do produtor e chegue nos serviços certos?
A resposta está nos brokers ou event buses. Eles são literalmente a ponte que conecta quem dispara o evento a quem precisa ouvi-lo.
Na event-driven, o broker desempenha esse papel de central de distribuição. O provedor do evento (como o serviço de pedidos em um e-commerce) não precisa se preocupar em quem vai receber o evento, quando vai recebê-lo ou como o evento será processado. Tudo o que ele faz é deixar o evento e nas mãos do broker, mas vamos entender melhor isso com uma analogia.
Como o broker faz esse desacoplamento?
Para entender melhor como o broker contribui para o desacoplamento em sistemas distribuídos, podemos pensar na seguinte situação cotidiana: imagine que você trabalha em um escritório onde o gerente coloca um memorando no quadro de avisos. Aqui, o gerente é o provedor do evento, o quadro de avisos representa o broker, e o memorando é o evento. Os funcionários, que são os consumidores, podem ler o memorando quando tiverem tempo.
O gerente não precisa saber quem vai ler ou quando. Ele apenas coloca a informação no quadro de avisos, e os funcionários acessam o memorando conforme sua disponibilidade. O memorando permanece acessível a todos os funcionários interessados, sem a necessidade de intervenção ou controle por parte do gerente.
Essa é a essência de como o broker funciona em uma arquitetura dirigida a eventos: o provedor apenas publica o evento, e os consumidores o processam no momento que for mais adequado para eles, garantindo o desacoplamento e a flexibilidade do sistema. Vários serviços podem se interessar pelo mesmo evento, e o broker facilita essa distribuição de forma eficiente.
Por que isso é tão importante? Porque ao desacoplar os serviços, garantimos que o sistema seja mais robusto e possa escalar com mais facilidade. Essa abordagem não apenas desacopla os serviços, mas também aumenta a resiliência e escalabilidade do sistema, já que cada serviço pode processar os eventos de forma independente, no seu próprio ritmo, sem depender de consultas diretas a outros.
Esse desacoplamento é fundamental em arquiteturas orientadas a eventos, pois o serviço provedor não precisa entender como a mensagem será entregue ou processada. O gerente não monitora cada funcionário para garantir a leitura, o broker não precisa controlar o que os consumidores fazem com os eventos, nem notificar diretamente cada um. Cada serviço consumidor tem liberdade para processar (consumir) as mensagens de acordo com sua própria lógica de negócio.
Quem pode ser o broker?
Agora que entendemos o papel crucial do broker em garantir o desacoplamento entre os serviços, a próxima pergunta é: quem ou o que pode ser esse broker? Em uma arquitetura dirigida por eventos, o broker pode ser uma ferramenta especializada para gerenciar a comunicação assíncrona entre os produtores e consumidores de eventos.
Existem várias soluções amplamente usadas que podem desempenhar o papel de broker. Aqui estão alguns exemplos:
RabbitMQ: Um dos brokers de mensagens mais populares que usa o protocolo AMQP. RabbitMQ é conhecido por sua robustez, capacidade de rotear mensagens de forma eficiente e garantir que os eventos sejam entregues corretamente, mesmo em caso de falhas.
Apache Kafka: Muito usado em sistemas de alta escala que precisam lidar com grandes volumes de eventos em tempo real. O Kafka permite o armazenamento dos eventos por um longo período, possibilitando que os consumidores "releiam" eventos antigos quando necessário.
Amazon EventBridge: Um serviço da AWS que facilita a construção de arquiteturas dirigidas por eventos na nuvem. O EventBridge permite a integração entre diversos serviços, tanto da AWS quanto de terceiros, com um sistema de roteamento avançado.
Google Cloud Pub/Sub: O broker de mensagens da Google Cloud. Ele permite uma comunicação assíncrona e escalável entre diferentes serviços, suportando grandes volumes de eventos com alta confiabilidade.
Esses brokers oferecem ferramentas poderosas para garantir que os eventos cheguem aos seus destinos, garantindo a escalabilidade, flexibilidade e o desacoplamento que são características centrais de uma arquitetura orientada a eventos
Para onde o Broker envia os eventos?
Depois que o broker recebe um evento, a próxima pergunta lógica é: para onde ele manda esses eventos?
Os serviços consumidores são aqueles que estão escutando ou assinando determinados eventos que o broker disponibiliza. Esses consumidores têm interesse em reagir a tipos específicos de eventos. Por exemplo, em um sistema de e-commerce, o serviço de pagamento pode estar interessado no evento "Pedido Realizado", enquanto o serviço de estoque se interessa pelo mesmo evento para atualizar as quantidades de produtos disponíveis.
No entanto, o broker não envia diretamente os eventos para os serviços consumidores. Ele disponibiliza os eventos em canais, filas, ou tópicos apropriados. Os serviços consumidores, por sua vez, buscam e processam os eventos de acordo com suas necessidades e capacidade de processamento. Esse comportamento desacoplado é essencial para garantir que cada serviço possa operar no seu próprio ritmo, evitando sobrecarga e permitindo escalabilidade.
Dependendo de como o sistema está configurado, o broker pode:
Postar o evento em uma fila: Cada consumidor acessa essa fila para buscar os eventos quando estiver pronto para processá-los.
Distribuir o evento por meio de um tópico: Vários serviços podem escutar o mesmo evento e processá-lo simultaneamente, cada um de acordo com sua responsabilidade e lógica de negócio.
No caso do Amazon EventBridge, o broker pode também configurar targets (destinos), que são os consumidores responsáveis por processar os eventos. Esses targets podem ser serviços como AWS Lambda, SQS, SNS, ou até APIs externas via API Destinations.
É importante notar que, mesmo quando o EventBridge "aciona" os targets, isso não caracteriza uma comunicação síncrona. O broker não espera uma resposta imediata dos serviços consumidores. Ele apenas dispara o evento para o target, e o target processa o evento de maneira independente, mantendo a comunicação assíncrona e desacoplada. Isso garante que os serviços possam operar sem dependências rígidas entre si, o que é crucial para a escalabilidade e resiliência da arquitetura.
Além disso, o EventBridge permite o uso de regras predefinidas para filtrar e rotear os eventos de forma eficiente. Assim, o sistema garante que cada evento seja disponibilizado apenas para os consumidores que precisam dele, e no formato adequado. Dessa maneira, os serviços podem buscar e processar os eventos relevantes no momento certo, conforme suas próprias lógicas de negócio.
Essa abordagem de filas, tópicos, e targets permite que os serviços consumidores sejam mais flexíveis, processando os eventos de maneira assíncrona e conforme sua própria capacidade, garantindo escalabilidade e resiliência ao sistema, sem introduzir acoplamento indesejado.
Por que filas?
A fila é uma peça fundamental em muitas arquiteturas orientadas a eventos porque ela resolve vários problemas comuns em sistemas distribuídos, como:
Desacoplamento temporal: Nem sempre os consumidores conseguem processar os eventos imediatamente. Com uma fila, o broker mantém os eventos até que o serviço consumidor esteja pronto para consumi-los, sem que isso afete o sistema como um todo.
Balanceamento de carga: Imagine que um serviço consumidor precise processar milhares de eventos em um curto período de tempo, como durante uma promoção de Black Friday. Ao usar uma fila, o broker garante que o serviço possa consumir os eventos conforme sua capacidade, evitando sobrecargas e falhas.
Garantia de entrega: Em sistemas críticos, como financeiros, é vital que os eventos não sejam perdidos. Usando filas, o broker pode garantir que cada evento seja entregue e processado uma vez, mesmo que o serviço consumidor esteja temporariamente indisponível.
Quando o broker posta os eventos em uma fila, cada consumidor pode se conectar a essa fila e buscar os eventos quando estiver pronto. Uma abordagem comum é o uso de Filas dedicadas, cada serviço consumidor tem sua própria fila. O evento é copiado ou distribuído para várias filas, e cada serviço consome de forma independente.
E se eu escolher RabbitMQ ou Kafka como broker?
Quando falamos sobre RabbitMQ e Apache Kafka, estamos lidando com duas ferramentas extremamente poderosas para arquiteturas orientadas a eventos. Ambos desempenham o papel de brokers, mas fazem isso de maneiras diferentes. A beleza dessas plataformas é que, em vez de se comunicar diretamente com um serviço consumidor, você pode optar por direcionar os eventos para uma fila ou tópico.
Então, como isso funciona? Vamos dar uma olhada.
RabbitMQ: Fila como principal mecanismo
Se você optar por RabbitMQ, a ideia principal gira em torno do uso de filas. Pense no rabbit como uma central de mensagens, onde os eventos são roteados para filas, e os serviços consumidores vão buscar esses eventos conforme sua disponibilidade. A lógica é simples: o produtor de eventos (como um serviço de pedidos, por exemplo) dispara um evento — "pedido realizado", por exemplo — e esse evento é direcionado para um exchange dentro do RabbitMQ.
Agora, o exchange é como um roteador que decide para onde esse evento vai. Dependendo das regras que você configura, o evento pode ser enviado para uma ou mais filas. E quem está esperando do outro lado? Os serviços consumidores, que estão conectados a essas filas, prontos para pegar os eventos quando estiverem prontos para processá-los.
Isso significa que o consumidor não recebe o evento imediatamente, mas sim quando ele vai até a fila e busca a mensagem. E aqui está a parte interessante: você tem total controle sobre como isso acontece. Pode configurar filas dedicadas para um único serviço ou compartilhar uma fila entre vários consumidores, balanceando a carga entre eles.
Então, em vez de ter aquela comunicação direta, você está postando os eventos em filas, e os serviços vão consumindo no seu próprio ritmo. É como se cada serviço fosse uma pessoa pegando uma encomenda quando tem tempo para abrir o pacote.
Kafka: Tópicos como principal mecanismo
Agora, se você escolher Kafka, a dinâmica muda um pouco, mas a lógica ainda é de desacoplamento. Em vez de filas, o Kafka trabalha com tópicos. Imagine um tópico como um grande "livro de registros" onde os eventos vão sendo adicionados, e os consumidores podem "ler" esses registros conforme eles chegam ou até revisitar eventos antigos.
No Kafka, quando o produtor de eventos publica um evento, como "pedido realizado", ele é colocado em um tópico específico, como "pedido_realizado"
. O interessante é que os consumidores se inscrevem nesses tópicos e podem escutar os eventos conforme eles chegam. E, assim como no RabbitMQ, os consumidores não recebem os eventos diretamente, mas sim quando estão prontos para processá-los.
A vantagem do Kafka é que ele armazena os eventos por um período de tempo, ou até indefinidamente, dependendo de como você configurar. Isso significa que, se um consumidor quiser "reler" um evento ou processar eventos em lotes, ele pode fazer isso sem problemas. Isso é super útil para quem precisa fazer auditoria ou análise dos eventos mais tarde.
E se você precisa de mais escalabilidade? Sem problema! O Kafka também oferece particionamento, o que significa que você pode dividir um tópico em várias partes e diferentes consumidores podem processar essas partes em paralelo, o que melhora o desempenho do sistema.
Agora, vamos explorar uma situação onde uma ação direta e específica é necessária, levando à criação de um comando.
Comandos
Dois conceitos fundamentais precisam ficar bem claros aqui: comandos e eventos. Embora ambos sejam formas de comunicação entre serviços, eles têm propósitos e comportamentos bem distintos.
Você já sabe o que é um evento. Agora, imagine outra situação: depois de ler (consumir) o memorando no quadro de anuncios, você decide ir até a sala do João e pedir pessoalmente: "João, confirme se a reserva do restaurante foi feita." Aqui, você está dando uma ordem direta ao João, esperando que ele tome uma ação em resposta (confirmar a reserva). Isso é um comando — uma instrução clara e específica com uma expectativa de execução.
Da mesma forma, em sistemas orientados a eventos, um comando, diferente de um evento que informa que algo aconteceu e permite que os serviços reajam conforme sua conveniência, o comando solicita uma ação imediata e é direcionado para um serviço específico, esperando que ele aja de acordo com a instrução.
Por exemplo, em um sistema de e-commerce:
O serviço de pedidos pode gerar um evento "Pedido Realizado", e esse fato quando disponibilizado pelo broker, outros serviços interessados podem consumir.
No entanto, o serviço de pagamento, ao processar esse pedido, pode precisar enviar um comando para o serviço de cobrança, dizendo: "Realize a cobrança deste pedido." Nesse caso, o comando é uma ordem direta que deixa claro quem deve agir e qual ação deve ser tomada, sem espaço para ambiguidade.
Quando Usar um Comando na EDA?
A utilização de comandos é essencial quando você precisa garantir que uma ação seja realizada. Na arquitetura orientada a eventos, onde a comunicação tende a ser assíncrona e flexível, os comandos podem complementar os eventos para assegurar que determinadas tarefas sejam executadas no momento certo e pela parte correta do sistema.
Ao invés de esperar que um serviço reaja de forma passiva a um evento, o comando impõe uma responsabilidade direta sobre o serviço receptor. Isso é especialmente útil em casos onde a ação que precisa ser realizada é crítica para o fluxo de trabalho, como processar um pagamento ou finalizar uma transação.
A verdadeira força de entender e usar comandos corretamente está no controle. Usar apenas eventos cria um sistema desacoplado, mas também menos direcionado. Eventos são "anúncios" abertos, enquanto comandos são diretivas claras.
Por exemplo, se você precisa garantir que um pagamento seja processado antes que um pedido seja enviado, um comando seria a escolha adequada. Ele ordena a execução do pagamento, garantindo que esse serviço não tenha a opção de ignorar ou processar o pedido sem essa etapa crítica.
Assim, o comando serve para preencher as lacunas onde a autonomia dos serviços não pode comprometer a consistência ou a finalização de uma tarefa específica. Ele traz uma camada de certeza dentro de uma arquitetura orientada a eventos, em que a flexibilidade às vezes pode implicar que nem todas as partes do sistema reajam como esperado.
Então resumindo:
Direção: O comando tem um destinatário claro. Ele é enviado diretamente para um serviço específico que tem a responsabilidade de processá-lo e executar a ação solicitada.
Intenção: O comando ordena que algo seja feito. O serviço que o recebe precisa realizar essa ação de forma explícita.
Exemplo: Um serviço de compras envia um comando ao serviço de pagamento dizendo: "Processar o pagamento deste pedido."
Mas agora vamos entrar em outra questão, como os eventos são transportados entre os serviços? A resposta está nos protocolos que facilitam essa troca de informações. Dependendo do contexto do sistema, do volume de eventos e das necessidades de escalabilidade e garantia de entrega, diferentes protocolos de comunicação podem ser adotados.
Protocolos em uma Arquitetura Dirigida a Eventos
Quando falamos sobre arquiteturas orientadas a eventos, uma das perguntas que sempre surge é: qual protocolo é mais usado na prática? Afinal, com tantas opções disponíveis — HTTP, AMQP, WebSockets, Kafka (que usa um protocolo binário via TCP), MQTT, gRPC —, pode parecer complicado escolher o caminho certo.
Mas, na verdade, a resposta está diretamente ligada ao tipo de sistema que você está construindo, o volume de eventos que você precisa processar, e o nível de confiabilidade que você quer garantir. Ainda assim, alguns protocolos são mais comuns em arquiteturas orientadas a eventos por causa da sua flexibilidade e robustez.
HTTP: Uma Solução Simples e Amplamente Usada
Se formos pensar na solução mais simples e amplamente utilizada, sem dúvidas o HTTP é um dos protocolos mais comuns. E isso tem muito a ver com a sua facilidade de implementação e com o fato de que quase todo sistema suporta HTTP.
O Hypertext Transfer Protocol é particularmente prático quando você está começando a implementar uma arquitetura orientada a eventos, ou quando os serviços são relativamente simples e o volume de eventos é mais baixo. Por exemplo, imagine um sistema de pagamento online que dispara um evento de "pagamento aprovado" via Webhook (que nada mais é do que uma requisição HTTP). O serviço de notificações pode ouvir esse evento e enviar um e-mail de confirmação para o cliente.
Esse uso do HTTP, especialmente com Webhooks, é muito comum porque ele funciona bem em muitos casos e é fácil de integrar com serviços externos, como SaaS. Empresas que oferecem serviços de pagamento, CRM, ou automação de marketing, por exemplo, muitas vezes disponibilizam eventos via Webhooks. Isso torna o HTTP uma solução prática para notificar outros serviços quando algo importante acontece.
Mas, é importante lembrar que o Hypertext Transfer Protocol tem suas limitações. Se o seu sistema precisa processar grandes volumes de eventos ou você precisa de uma garantia de entrega, o HTTP pode não ser a melhor escolha, porque ele não foi projetado para persistência ou alta escalabilidade.
Quando escolher HTTP e quando optar por algo mais robusto como AMQP ou Kafka?
Essa decisão depende muito do que você espera do sistema:
Se o foco é simplicidade, integração rápida e você não precisa processar milhões de eventos por segundo, HTTP (com Webhooks) é uma ótima escolha. Ele é direto, e é fácil encontrar suporte para ele em praticamente qualquer linguagem de programação ou serviço na web.
Se você precisa de robustez e confiabilidade, onde eventos críticos não podem ser perdidos, AMQP é a solução mais segura. Empresas que lidam com transações críticas ou que precisam garantir o processamento único de cada evento tendem a preferir esse protocolo.
Agora, se você está em um cenário de alta escala, com volumes gigantescos de eventos sendo processados, o Apache Kafka é, na prática, a escolha mais frequente. Ele permite que grandes quantidades de dados fluam constantemente pelo sistema, sem sobrecarregar os serviços ou gerar gargalos.
E quanto a WebSockets e MQTT?
Embora o WebSocket seja mais comum em aplicações em tempo real, como chats ou jogos online, ele não é tão amplamente usado em arquiteturas de eventos corporativas. Isso porque ele exige uma conexão contínua, o que pode ser difícil de escalar quando falamos de milhões de eventos ou sistemas complexos. Já o MQTT é muito popular em IoT (Internet das Coisas), onde você tem dispositivos pequenos que precisam enviar eventos para um servidor central de maneira leve e eficiente.
Agora que conversamos sobre os protocolos, vamos falar mais sobre a estrutura da mensagem.
Anatomia de uma Mensagem
Você deve estar se perguntando: como essa mensagem precisa ser estruturada? Afinal, se o evento é o que aconteceu (como uma venda realizada ou um pagamento processado), a mensagem é o meio pelo qual esse evento é comunicado para outras partes do sistema.
No contexto de sistemas distribuídos, é crucial que a mensagem seja clara e completa, porque é ela que vai viajar entre os serviços, passando pelo broker e chegando aos consumidores que precisam reagir ao evento. Cada detalhe da mensagem é importante, e sua estrutura precisa ser bem pensada para garantir que o sistema funcione de maneira eficaz e desacoplada.
A diferença entre o evento e a mensagem
Antes de mais nada, como eu mencionei no artigo "Comunicação Assíncrona em Microserviços: O que poucos te contam", é importante entender que o evento e a mensagem não são a mesma coisa, embora estejam intimamente ligados. O evento é o que aconteceu, é um fato de uma ocorrência significativa. Já a mensagem é o meio pelo qual você comunica esse evento para outras partes. Resumindo, a mensagem contém os detalhes desse evento (como ID do pedido, lista de itens, informações do cliente) e é encaminhada pelo broker para os serviços interessados que estão inscritos para processar esse tipo de evento.
Agora, a questão é: como essa mensagem deve ser estruturada para que todos os serviços consigam entendê-la, processá-la e agir sobre ela?
Vamos imaginar a mensagem como um pacote que viaja do provedor de eventos para os consumidores, através do broker. Esse pacote precisa conter todas as informações necessárias para que os serviços consumidores possam tomar uma decisão. Ele precisa ser claro, flexível o suficiente para acomodar diferentes tipos de eventos, e ao mesmo tempo padronizado para que todos os consumidores entendam sua estrutura.
Event Envelope
Uma prática amplamente adotada na arquitetura dirigida a eventos é encapsular o evento dentro de uma estrutura chamada Event Envelope (ou envelope de evento). Esse envelope serve como uma camada externa que envolve todos os elementos que discutimos acima (tipo, ID, timestamp, fonte, dados). Ele garante uma organização padronizada e facilita a interpretação do evento por qualquer serviço consumidor.
A ideia do Event Envelope é garantir que a mensagem seja autoexplicativa e contenha todos os elementos essenciais, para que qualquer serviço que consuma o evento saiba como processá-lo. Vamos entender os principais campos que estão nesse envelope.
1. eventType (Tipo do Evento)
Esse campo é essencial. O eventType
informa ao consumidor o que aconteceu. Imagine que a mensagem é como uma carta, e o eventType
é o título dessa carta. Ele indica de forma clara qual evento foi disparado.
Por exemplo, se o evento for sobre uma venda realizada, o eventType
pode ser algo como "OrderPlaced"
. Se for sobre um pagamento, pode ser "PaymentProcessed"
. Esse campo garante que os consumidores consigam identificar rapidamente o tipo de evento e, com base nisso, decidir se devem ou não processá-lo.
{
"eventType": "OrderPlaced",
...
}
2. eventId (Identificador Único do Evento)
O eventId
é um identificador único, como um RG do evento. Ele serve para rastrear o evento dentro do sistema. Isso é especialmente importante em cenários onde você precisa garantir que o evento seja processado uma única vez. Além disso, em casos de erro ou falhas, o eventId
ajuda a identificar se um evento já foi processado ou se ele está sendo processado novamente.
{
"eventType": "OrderPlaced",
"eventId": "123456-abc-789",
...
}
Com esse campo, cada evento tem sua própria "digital", o que facilita o rastreamento ao longo do ciclo de vida da mensagem.
3. timestamp (Momento do Evento)
Em sistemas distribuídos, o tempo é um fator crítico. O timestamp
indica quando o evento ocorreu. Esse campo ajuda os consumidores a organizarem o processamento dos eventos e a tomar decisões baseadas no tempo.
Por exemplo, se um serviço consumidor recebe eventos em lotes, ele pode processar os eventos na ordem em que ocorreram, com base nesse timestamp
. Além disso, o campo é útil para auditoria e para resolver eventuais problemas de conflito de dados.
{
"eventType": "OrderPlaced",
"eventId": "123456-abc-789",
"timestamp": "2024-09-03T12:30:00Z",
...
}
4. source (Fonte do Evento)
O source
descreve de onde o evento veio. Isso é particularmente útil quando você tem um sistema com múltiplos produtores de eventos. Imagine um cenário onde diferentes serviços podem emitir eventos do tipo "OrderPlaced"
. Com o campo source
, você consegue identificar qual serviço disparou o evento, facilitando o rastreio e a organização dos dados.
{
"eventType": "OrderPlaced",
"eventId": "123456-abc-789",
"timestamp": "2024-09-03T12:30:00Z",
"source": "OrderService",
...
}
5. data (Dados do Evento)
Aqui está o coração da mensagem. O campo data
contém os detalhes específicos do evento. Se o evento for de um pedido realizado, por exemplo, esse campo pode conter o número do pedido, itens comprados, quantidade, valor total, entre outras informações relevantes. O data
é o que realmente interessa aos consumidores, pois são os dados contidos aqui que determinarão as ações que cada serviço vai tomar.
Um exemplo para um evento de pedido realizado pode ser:
{
"eventType": "OrderPlaced",
"eventId": "123456-abc-789",
"timestamp": "2024-09-03T12:30:00Z",
"source": "OrderService",
"data": {
"orderId": "12345",
"userId": "67890",
"items": [
{ "productId": "A1", "quantity": 2, "price": 30.00 },
{ "productId": "B2", "quantity": 1, "price": 90.00 }
],
"totalAmount": 150.00
}
}
Esse campo carrega todas as informações detalhadas que cada serviço consumidor precisa para agir de acordo. O serviço de pagamento, por exemplo, vai olhar para o totalAmount
, enquanto o serviço de estoque vai focar nos items
para ajustar as quantidades disponíveis.
Existem algumas variações e abordagens diferentes para estruturar uma mensagem, dependendo do contexto do sistema, das ferramentas e até das preferências dos arquitetos de software.
Estrutura com Metadados Enriquecidos
Além dos campos básicos como eventType
, eventId
, timestamp
, e data
, alguns sistemas preferem enriquecer suas mensagens com metadados adicionais. Esses metadados podem incluir informações contextuais que ajudam os serviços consumidores a interpretar o evento de forma mais eficiente, sem a necessidade de fazer consultas adicionais.
Por exemplo, em um evento de "pedido realizado", além dos dados do pedido, a mensagem pode conter informações como IP do cliente, localização geográfica, ou até mesmo tags de priorização. Isso permite que os serviços consumidores tenham uma visão mais ampla do que aconteceu, sem precisar buscar esses dados em outro lugar.
Um exemplo com metadados poderia ser assim:
{
"eventType": "OrderPlaced",
"eventId": "123456-abc-789",
"timestamp": "2024-09-03T12:30:00Z",
"source": "OrderService",
"metadata": {
"ipAddress": "192.168.1.1",
"location": "São Paulo, BR",
"priority": "high"
},
"data": {
"orderId": "12345",
"userId": "67890",
"items": [
{ "productId": "A1", "quantity": 2, "price": 30.00 },
{ "productId": "B2", "quantity": 1, "price": 90.00 }
],
"totalAmount": 150.00
}
}
Estrutura com Separação de Evento e Comando
Outro padrão que você pode encontrar em algumas arquiteturas orientadas a eventos é a separação entre eventos e comandos. Enquanto o evento é uma notificação de que algo aconteceu, o comando é uma instrução para que algo seja feito. Esse padrão é comum em sistemas onde o comportamento dos consumidores é mais proativo, e não apenas reativo.
Na prática, a diferença entre um evento e um comando pode ser sutil, mas importante. O evento informa que algo aconteceu, enquanto o comando indica que algo precisa ser feito. Alguns arquitetos gostam de encapsular essas duas ideias dentro de mensagens diferentes, com estruturas um pouco distintas.
Por exemplo, uma mensagem de evento poderia ser algo como:
{
"eventType": "OrderShipped",
"eventId": "abcdef-123456",
"timestamp": "2024-09-04T09:15:00Z",
"source": "LogisticsService",
"data": {
"orderId": "12345",
"shippingProvider": "DHL",
"trackingNumber": "ABC123456"
}
}
E uma mensagem de comando poderia ter uma estrutura similar, mas com uma semântica diferente, solicitando que algo seja feito:
{
"commandType": "ProcessRefund",
"commandId": "xyz-789",
"timestamp": "2024-09-04T09:30:00Z",
"source": "CustomerService",
"data": {
"orderId": "12345",
"reason": "Product damaged",
"refundAmount": 150.00
}
}
Aqui, o comando indica uma ação específica que precisa ser realizada — no caso, um reembolso — enquanto o evento apenas comunica que algo já aconteceu, como um pedido sendo despachado.
Estrutura Focada em "CloudEvent"
Outra anatomia que vem ganhando bastante popularidade nos últimos tempos, especialmente em sistemas cloud-native, é o padrão CloudEvent. Esse é um padrão open-source que surgiu para unificar e padronizar a estrutura de eventos em ambientes de computação em nuvem. O objetivo do CloudEvent é oferecer uma maneira consistente de descrever eventos para diferentes plataformas, como AWS, Azure, ou Google Cloud.
A estrutura do CloudEvent é bastante semelhante ao que discutimos anteriormente, mas segue uma especificação mais rígida. Ele define campos obrigatórios e opcionais que podem ser usados para transportar eventos entre diferentes serviços e sistemas de mensageria.
Aqui está um exemplo de uma CloudEvent para um pedido realizado:
{
"specversion": "1.0",
"type": "com.ecommerce.order.placed",
"source": "/order/service",
"id": "A234-1234-1234",
"time": "2024-09-03T12:30:00Z",
"datacontenttype": "application/json",
"data": {
"orderId": "12345",
"userId": "67890",
"items": [
{ "productId": "A1", "quantity": 2, "price": 30.00 },
{ "productId": "B2", "quantity": 1, "price": 90.00 }
],
"totalAmount": 150.00
}
}
Os campos de CloudEvent são mais padronizados e incluem:
specversion
: Versão da especificação do CloudEvent.type
: O tipo de evento (parecido com oeventType
).source
: A origem do evento.id
: Um identificador único.time
: O horário do evento.datacontenttype
: O tipo de dado no campodata
.data
: O conteúdo do evento.
O uso do padrão CloudEvent é muito comum em ambientes onde diferentes sistemas em nuvem precisam conversar entre si, facilitando a interoperabilidade e a consistência na comunicação.
Agora que conversamos sobre as estruturas das mensagens, vamos falar brevemente sobre padrões.
Padrões Mais Usados em Arquiteturas Orientadas a Eventos
Em uma arquitetura orientada a eventos, o objetivo é criar um sistema flexível. Para isso, diversos padrões são utilizados para organizar o fluxo de comunicação entre os serviços. Vamos conhecer os mais importantes:
1. Notificação de Evento (Event Notification)
Imagine uma situação em que você recebe uma mensagem que diz: "Algo importante aconteceu". Só isso. A notificação não dá mais detalhes, nem exige uma ação imediata. Isso é o que chamamos de notificação de evento. O evento é apenas uma mensagem indicando que algo relevante ocorreu. Os serviços podem decidir se e como vão reagir a essa informação.
Exemplo: Em uma loja online, quando um cliente faz um pedido, o evento "Pedido Realizado" é emitido. A partir daí, serviços como pagamento e estoque podem agir se acharem necessário.
2. Transferência de Estado via Evento (Event-Carried State Transfer)
Agora imagine que, além de dizer que algo aconteceu, essa mensagem inclui todos os detalhes necessários para agir. É o que acontece aqui: o evento não apenas notifica que algo ocorreu, mas também carrega dados importantes sobre essa mudança de estado.
Exemplo: O evento "Pedido Realizado" pode incluir informações como o nome do cliente, os produtos comprados e o valor total. Assim, os serviços que consumirem esse evento têm tudo o que precisam para fazer seu trabalho.
3. Event Sourcing (Fonte de Eventos)
Já pensou em registrar cada mudança de estado de um sistema como um evento separado? Isso é o Event Sourcing. Em vez de armazenar apenas o estado final de um pedido, por exemplo, você armazena todos os eventos que o levaram até ali. Se precisar voltar a um estado anterior ou entender como algo mudou ao longo do tempo, é só revisar a sequência de eventos.
Exemplo: Cada vez que um pedido muda de status (recebido, pago, enviado), esses eventos são registrados para que o estado final possa ser reconstruído a qualquer momento.
4. Publish/Subscribe (Publicar/Assinar)
Esse padrão é bastante comum e simples de entender. Imagine que você publica uma mensagem em um canal, e todos os interessados se inscrevem para recebê-la. Isso é o Pub/Sub. O produtor de eventos publica algo, e todos os serviços que se inscreveram ficam sabendo e podem reagir.
Exemplo: No e-commerce, o serviço de pedidos publica o evento "Pedido Realizado", e os serviços de pagamento e estoque, que estão inscritos nesse canal, consomem a mensagem e agem conforme necessário.
5. Event-Driven Workflow (Fluxo de Trabalho Orientado a Eventos)
Em vez de um fluxo de trabalho linear e rígido, esse padrão permite que os eventos conduzam a sequência de ações em um sistema. Um evento inicial pode desencadear outros eventos, criando um fluxo de trabalho descentralizado.
Exemplo: O evento "Pedido Realizado" aciona "Pagamento Processado", que por sua vez aciona "Pedido Empacotado", e assim por diante, até o pedido ser entregue.
6. Retry e Dead Letter Queues
Em um mundo ideal, tudo sempre funciona perfeitamente na primeira tentativa. Mas, no mundo real, falhas acontecem. É aí que entram as filas de retry e dead letter. Quando um evento não é processado corretamente, ele pode ser reenviado (retry). Se o problema persistir, o evento vai para uma fila especial chamada Dead Letter Queue, onde poderá ser analisado e processado manualmente.
Exemplo: Se o serviço de pagamento não conseguir processar um evento "Pedido Realizado" devido a um erro temporário, o evento será reenviado. Se continuar falhando, ele vai para uma Dead Letter Queue para ser tratado.
7. Choreography (Coreografia)
Aqui, cada serviço age de forma independente, reagindo a eventos à medida que eles chegam. Não há um serviço central coordenando o que deve acontecer. Tudo ocorre como uma dança, onde cada serviço "dança" conforme os eventos que recebe.
Exemplo: O serviço de pagamento processa o pagamento após o evento "Pedido Realizado", e o serviço de estoque reage ao evento "Pagamento Processado". Tudo acontece sem um maestro coordenando as ações.
Conclusão
Event-Driven Architecture oferece uma forma poderosa e flexível de construir sistemas distribuídos, onde os serviços são desacoplados e reagem a eventos conforme necessário. Ao entender os principais conceitos — como eventos, comandos, coreografia e orquestração, além do papel fundamental dos brokers — você pode projetar sistemas que sejam escaláveis, resilientes e prontos para lidar com as demandas do mundo moderno. Dominar esses padrões e práticas é essencial para criar soluções que não só respondam de forma eficiente a mudanças, mas também permitam uma evolução contínua da arquitetura.