Para saber quando utilizar a arquitetura baseada em eventos (EDA), entenda os conceitos importantes que a envolvem, como comunicação sincrona e assíncrona, DDD, mensageria e muito mais 

Como tudo em tecnologia, decidir se a arquitetura baseada em eventos (EDA) faz sentido no seu sistema depende de uma série de fatores. A EDA traz diversas vantagens, desde representar melhor a realidade até oferecer maior escalabilidade e possibilidade de evolução. Mas tem também desafios, como a complexidade de ser implementada e de lidar com sua natureza assíncrona. 

 

Para entender melhor como fazer tomar essa decisão, é preciso entender conceitos importantes no desenho de uma arquitetura escalável, que envolve comunicação, DDD, mensageria e outras coisas mais, que explico a seguir. 

 

Entendendo o que é comunicação

 

Segundo o Google, comunicação é a “ação de transmitir uma mensagem e, eventualmente, receber outra mensagem como resposta”. A mensagem transmitida trafega do emissor ao receptor, e ao redor dela temos alguns elementos:

 

– Contexto: também chamado de referente, representa a situação ou o assunto do qual a mensagem se trata;

– Canal: é o meio por onde a mensagem trafega. Por exemplo, texto, no caso deste artigo, ou a voz de uma palestrante através do microfone em um evento presencial;

– Código: é o conjunto de elementos e regras comuns entre o emissor e o receptor, que definem como a mensagem é formada — neste caso, por exemplo, a língua portuguesa.

 

Representação dos elementos da comunicação. O "Emissor" tem uma seta em direção ao "Receptor", representando a mensagem sendo trafegada de um para o outro. Ao redor da mensagem, estão os outros três elementos da comunicação: contexto, canal e código.

Imagem: Representação dos elementos da comunicação. O “Emissor” tem uma seta em direção ao “Receptor”, representando a mensagem sendo trafegada de um para o outro. Ao redor da mensagem, estão os outros três elementos da comunicação: contexto, canal e código.

 

Entendendo o Domain Driven Design (DDD)

 

O DDD — design (ou desenvolvimento) orientado a domínio — é um termo definido por Eric Evans em seu livro Domain Driven Design, em 2003, que tem como objetivo desenvolver sistemas onde o código (ou design) esteja baseado e centrado em conceitos e regras que façam sentido dentro do domínio. Em outras palavras, o DDD prega que o código de um sistema deve ser baseado na realidade, sem existir uma camada de tradução entre negócio e tecnologia.

 

Não é objetivo deste artigo se alongar na definição de DDD, mas existem dois conceitos que merecem destaque: linguagem ubíqua e contextos delimitados.

 

Linguagem Ubíqua (ubiquitous language): é uma linguagem comum, compartilhada entre pessoas desenvolvedoras, de negócio e usuárias do sistema. Por exemplo, se as pessoas de negócio dizem “cliente”, o código também deveria dizer “cliente” (e não “usuária”), sem necessidade de tradução de termos em conversas entre tecnologia e negócios (obviamente, isso não se refere à tradução para inglês).

 

Contextos delimitados (bounded contexts): são as bordas ou limites entre conceitos de domínio que fazem sentido em conjunto. Por exemplo, para um e-commerce, faz sentido existir um contexto de Inventário, que é responsável pelo controle de estoque de produtos; outro de Pagamentos, responsável por cobrar os clientes; outro de Logística, responsável pelo envio de produtos etc.

 

Imagem: Representação dos contextos delimitados. São três contextos representados por formas ovais com bordas tracejadas, cada uma contendo domínios (ou entidades) que façam sentido juntos. Esses domínios ou entidades têm ligações entre si, tanto dentro quanto fora de cada contexto.

Micro-serviços

 

Conforme a arquitetura dos sistemas evolui e mais complexidade é adicionada aos sistemas, muitas vezes faz sentido separar responsabilidades entre vários serviços — chamados de micro-serviços. 

 

Nesta separação, faz sentido que os novos serviços sejam organizados por domínios, para que cada um tenha apenas uma responsabilidade e consiga ser o mais independente possível. Por esse motivo, os contextos delimitados costumam ser ótimos candidatos a se tornarem esses novos serviços.

 

Representação de micro-serviços. É o mesmo desenho dos contextos delimitados, mas com bordas preenchidas ao invés de tracejadas, representando uma barreira maior entre cada contexto (micro-serviço). A comunicação entre serviços é mais forte e mais complexa do que a ligação entre domínios de um mesmo serviço ou contexto.

Imagem: Representação de micro-serviços. É o mesmo desenho dos contextos delimitados, mas com bordas preenchidas ao invés de tracejadas, representando uma barreira maior entre cada contexto (micro-serviço). A comunicação entre serviços é mais forte e mais complexa do que a ligação entre domínios de um mesmo serviço ou contexto.

 

Ao adotar essa estratégia de micro-serviços, o primeiro desafio que aparece é: como lidar com a comunicação entre serviços? O que antes era uma simples chamada de métodos de outras classes agora passa a ser uma barreira muito maior.

 

Comunicação síncrona

 

O jeito mais simples e comum de resolver esse desafio é usando uma abordagem síncrona, com comunicação via HTTP, por exemplo. Aqui, o serviço A faz uma requisição HTTP para o serviço B, que responde imediatamente à requisição, indicando sucesso ou falha. Esse cenário é muito parecido com uma simples chamada de métodos (por obter uma resposta imediata), mas adiciona a complexidade de a requisição e a resposta serem trafegadas pela rede (internet).

 

Requisição síncrona entre serviço A e serviço B, onde o A faz uma requisição para o B (seta roxa) e o B envia uma resposta da requisição de volta para o A (seta vermelha)

Imagem: Requisição síncrona entre serviço A e serviço B, onde o A faz uma requisição para o B (seta roxa) e o B envia uma resposta da requisição de volta para o A (seta vermelha)

 

Apesar da simplicidade, comunicações síncronas têm como desvantagem:

 

– Acoplar serviços: aquele que faz a requisição precisa, no mínimo, conhecer bem o serviço que está chamando (qual é o endereço, quais são os parâmetros e seus formatos etc);

– Lidar com erros: o serviço que faz a requisição também precisa conseguir lidar com qualquer tipo de erro que possa acontecer naquele que está chamando (respostas erradas ou inesperadas, serviço lento, serviço fora do ar etc);

– Lidar com problemas de performance: quando a requisição do serviço A para o serviço B depende de o B fazer outras requisições para outros serviços, que fazem outras requisições também, a resposta do serviço A só virá depois que todas as outras requisições se completarem, podendo demorar consideravelmente. Além disso, qualquer erro na “teia” de requisições pode desencadear um erro no sistema como um todo.

 

Comunicação assíncrona

 

Uma forma de lidar com as desvantagens da comunicação síncrona entre sistemas é se comunicando assincronamente, através de mensagens (mensageria), onde as respostas não são imediatas. Nesse tipo de comunicação, o serviço que faz a requisição não espera até que o serviço que recebe a requisição responda. Qualquer resposta também deve ser enviada de forma assíncrona, através de mensagens. 

 

Cada requisição (ou mensagem) é enviada através de um sistema intermediário (chamado de message broker ou message bus), que entrega a mensagem ao(s) sistema(s) interessado(s).

 

Por exemplo, imagine um sistema de e-commerce que tenha dois serviços:

  1. Pedidos: responsável por organizar o carrinho de compras e processar os pedidos, além de dar feedbacks sobre o status da compra para as pessoas usuárias do sistema.
  2. Pagamentos: responsável por processar pagamentos com base nos dados do pedido, aprovando ou não cada pagamento.

 

Exemplo de comunicação assíncrona, onde o serviço "Pedidos" e o serviço "Pagamentos" se comunicam através de mensagens indicando o que aconteceu.

Imagem: Exemplo de comunicação assíncrona, onde o serviço “Pedidos” e o serviço “Pagamentos” se comunicam através de mensagens indicando o que aconteceu. 

 

Nesse exemplo, o serviço Pedidos envia uma mensagem ao serviço Pagamentos através do message broker avisando que um pedido foi realizado. Assim que o sistema envia a mensagem, já é possível retornar para a usuária avisando que o pagamento está em processamento, não necessitando, então, aguardar a resposta de outro serviço para dar um feedback imediato à cliente. 

 

Após receber a mensagem de que um pedido foi realizado, o serviço Pagamentos sabe que deve começar a processar o pagamento daquele pedido. A resposta desse processamento é outra mensagem enviada para o serviço Pedidos, avisando que o pagamento foi aprovado ou reprovado. Com isso, o primeiro serviço pode retornar outro feedback à cliente, avisando sobre o status do pagamento.

 

Por se comunicarem apenas através do message broker e precisarem apenas saber o formato das mensagens recebidas e enviadas, os serviços que se comunicam não precisam se conhecer (e nem saber que os outros existem, inclusive), trazendo um desacoplamento real que permite total independência entre eles.

 

Isso também faz com que se tenha menores problemas de disponibilidade pois, mesmo que um dos serviços pare de funcionar ou esteja lento, a resposta para à cliente (que chama o primeiro serviço) será imediata já que não depende de mais nenhum outro. Inclusive, qualquer erro, lentidão ou não funcionamento no meio da “teia” de trocas de mensagens não é desencadeado para os serviços e, portanto, não abala a estrutura como um todo.

 

Como nem tudo são flores, lidar com comunicação assíncrona também traz suas desvantagens, como:

 

– Complexidade: implementar esse message broker pode ser complexo, mesmo usando ferramentas de mercado, como o Apache Kafka, Google Pub/Sub, RabbitMQ e outros. Além disso, é necessário repensar a estrutura da comunicação entre serviços, já que tudo passa a ser assíncrono e através de mensagens.

 

– Consistência eventual: existe um período de tempo (geralmente não muito grande) em que um mesmo dado que habite mais de um sistema vai estar atualizado em um e não atualizado ainda no outro. Por exemplo, se dois serviços A e B mantêm o mesmo dado e o serviço B é atualizado a partir de uma mensagem do serviço A, por um momento, até que a mensagem seja enviada, lida e processada, o dado nos dois serviços estará diferente e inconsistente.

 

Exemplificando a consistência eventual. Serviços A e B têm o mesmo dado. Serviço A envia uma mensagem ao serviço B para que o B atualize o dado, mas existe um tempo entre a mensagem ser enviada e ser de fato lida e processada. Durante esse tempo, o B fica com dados inconsistentes. Eventualmente, quando a mensagem for processada, o dado volta a estar consistente nos dois serviços.

Imagem: Exemplificando a consistência eventual. Serviços A e B têm o mesmo dado. Serviço A envia uma mensagem ao serviço B para que o B atualize o dado, mas existe um tempo entre a mensagem ser enviada e ser de fato lida e processada. Durante esse tempo, o B fica com dados inconsistentes. Eventualmente, quando a mensagem for processada, o dado volta a estar consistente nos dois serviços.

 

Entendendo o que é mensageria

 

O começo deste artigo abordou um pouco sobre a estrutura da comunicação da vida real, onde uma mensagem é passada de um emissor para um receptor, e ao redor dela temos código, canal e contexto. Foi abordado também um pouco sobre Domain Driven Design, que tem como objetivo modelar a realidade. 

 

Faz sentido, então, que a comunicação entre sistemas represente a comunicação da realidade, sendo composta pelos mesmos elementos:

 

– Emissor: serviço que envia a mensagem;

– Receptor: serviço que recebe e lê a mensagem;

– Código: regras que definem o formato da mensagem, incluindo que atributos podem ser enviados e qual o tipo deles, além de metadados e qualquer outra informação que compõe a mensagem. Também pode ser chamado de contrato;

– Canal: por onde a mensagem é trafegada, que podemos entender tanto como a ferramenta que é utilizada (ex. Kafka ou RabbitMQ) quanto a fila ou tópico que é utilizado para o envio da mensagem. Tópicos são estruturas de dados que armazenam e organizam as mensagens trafegadas, idealmente de forma que faça sentido para o contexto da mensagem;

– Contexto: assunto ou domínio do qual a mensagem se trata.

 

Representação da comunicação entre sistemas. O "Emissor" tem uma seta em direção ao "Receptor", representando a mensagem sendo trafegada de um para o outro. Ao redor da mensagem estão os outros três elementos da comunicação: contexto, canal e código (ou contrato).

Imagem: Representação da comunicação entre sistemas. O “Emissor” tem uma seta em direção ao “Receptor”, representando a mensagem sendo trafegada de um para o outro. Ao redor da mensagem estão os outros três elementos da comunicação: contexto, canal e código (ou contrato).

 

A comunicação assíncrona descrita acima também é chamada de mensageria. E para que essa troca de mensagens seja bem estruturada, é interessante pensar em quais tipos de mensagens costumam trafegar entre serviços:

 

– Comando: uma mensagem “mandando” que o outro serviço tome uma ação. Por exemplo: “Processe o pagamento” ou “Busque informações da cliente X”.

– Notificação: uma mensagem avisando que algo aconteceu. Caso o serviço que recebeu a mensagem precise de mais informações sobre o ocorrido, ele deve buscar de alguma forma (através de uma chamada síncrona ou de um comando). Por exemplo: “Cliente X foi atualizada”, onde o serviço que leu a mensagem precisa buscar as novas informações no serviço que a emitiu.

– Notificação com conteúdo: uma mensagem avisando que algo aconteceu, carregada com o conteúdo relevante para aquela mensagem, evitando que quem a lê precise buscar mais informações. Por exemplo: “Cliente X foi atualizada e os dados novos são X, Y, Z”.

 

Eventos

 

Os dois últimos tipos de mensagem explicados acima, que se referem a uma notificação, também podem ser chamados de eventos, pois representam alguma mudança de estado que já ocorreu. Evento é, portanto, uma mensagem que expressa fatos que aconteceram no passado.

 

Por ser uma representação do passado, os eventos devem sempre ser descritos no passado (“Recurso X foi atualizado”, “Pedido foi concluído”, “Pagamento foi aprovado”). E por ser um fato, o evento também é imutável, pois não podemos mudar aquilo que já ocorreu — o significa que qualquer correção ou atualização necessária precisa ser feita através de eventos também.

 

Entendendo a Arquitetura Baseada em Eventos

 

Quando a comunicação entre serviços é assíncrona e as mensagens trocadas entre eles são eventos, temos uma arquitetura baseada em eventos — ou Event Driven Architecture (EDA) em inglês.

 

O serviço que envia os eventos também é chamado de produtor, e os serviços que os lêemsão chamados de consumidores.

 

Arquitetura baseada em eventos, onde um sistema A (produtor) emite um evento através de um message broker. O evento pode ser lido pelos serviços consumidores B e C.

Imagem: Arquitetura baseada em eventos, onde um sistema A (produtor) emite um evento através de um message broker. O evento pode ser lido pelos serviços consumidores B e C.

 

Uma das grandes vantagens de uma arquitetura baseada em eventos é que ela permite que os serviços sejam totalmente desacoplados. Isso habilita a criação de novos serviços que consumam eventos já existentes sem nenhum custo para os demais serviços. E, no caso de as mensagens já trafegadas serem persistidas, esse novo serviço pode consumi-las do início, sem perder nenhuma informação.

 

Há, porém, uma desvantagem: ao termos sistemas distribuídos desacoplados, enxergar como o sistema se comunica como um todo pode não ser tão trivial, especialmente por não ter descrito no código fonte dos serviços com quais outros serviços eles se comunicam.

 

Como vocês podem ver, a EDA é extremamente potente e ideal para diversos cenários, mas não utilize um canhão para matar uma formiga. Entenda se uma arquitetura baseada em eventos realmente atende a sua necessidade ou se um sistema com comunicação síncrona já não é mais do que o suficiente — o que é verdade na maioria dos casos, mas isso é assunto para outro artigo.

 

Para saber mais:

 

– A palestra Event Driven Architecture (slides disponíveis aqui), feita por mim, foi a base deste artigo.

– O livro Enterprise Integration Patterns (em inglês), do Gregor Hohpe com o Bobby Woolf, descreve padrões de comunicação entre serviços.

– A palestra The Many Meanings of Event-Driven Architecture (em inglês), do Martin Fowler, explora quatro diferentes possibilidades do que se entende por Arquitetura Baseada em Eventos, focando bastante em event sourcing.

– Nesta série de três artigos (Parte 1, Parte 2 e Parte 3), Nicolaz Zein explica tudo sobre como funciona uma das ferramentas utilizadas como message broker, o Kafka.

– Os artigos Quando eu devo usar DDD? e Quero aplicar DDD, por onde começo?, da Bruna Pereira, explicam mais a fundo o que é DDD e quando se deve ou não utilizá-lo.

 

CRÉDITOS

 

Autora

Camila Campos é engenheira de produto/software, e atualmente está ajudando a criar caminhos extraordinários para crianças com autismo na Genial Care através de código (nem sempre) bonito. Além disso, organiza a Rails Girls São Paulo e a Women Dev Summit, duas iniciativas incríveis que visam a inclusão de mulheres na tecnologia.

Saiba mais no LinkedIn

 

Revisora

Stephanie Kim Abe é jornalista, formada pela Escola de Comunicações e Artes da Universidade de São Paulo (ECA-USP). Trabalha na área de Educação e no terceiro setor. Esteve nos primórdios da Programaria, mas testou as águas da programação e achou que não era a sua praia. Mas isso foi antes do curso Eu Programo

 

Saiba mais no LinkedIn

 

Este conteúdo faz parte da PrograMaria Sprint Tech Leads.

 

O que você achou deste conteúdo? Deixe sua avaliação abaixo: