Event-driven Architecture (EDA) em uma Arquitetura de Microsserviços
Quando nos referimos à arquitetura de microsserviços e sua natureza de comunicação temos dois caminhos possíveis a considerar, comunicação síncrona e assíncrona. EDA (Arquitetura orientada a eventos) implementa basicamente a natureza de comunicação assíncrona, porém, antes de entendermos e entrarmos em maiores detalhes sobre seu conceito e funcionamento, precisamos primeiro relembrar como a natureza de comunicação síncrona é implementada dentro de uma arquitetura de microsserviços. A natureza de comunicação síncrona é implementada dentro da arquitetura de microsserviços expondo-se APIs baseadas em REST sob o protocolo HTTP, através de chamadas bloqueantes fazendo com que o cliente desta API tenha que aguardar o retorno do processamento da requisição, criando-se assim uma conversação entre o cliente (consumidor da API) e o servidor (Backend).
Comunicação Síncrona
Neste caso, o design da API depende totalmente do protocolo HTTP, amarrando desde os métodos (GET, POST, PUT, DELETE, PATCH) passando pelos Headers (Authorization, Accept, Content-Type, etc) até os códigos de status de retorno destas chamadas HTTP (200, 201, 400, 403, 500, 503, etc). No estilo de comunicação síncrono, o cliente, que poderia ser uma aplicação, enviaria uma mensagem/comando (ex: HTTP Request) para a API exposta como REST sempre que necessitasse de algo, neste caso o servidor (Backend) então responderia enviando um retorno do processamento (HTTP Response) juntamente com o código de status de retorno HTTP, indicando se a requisição foi processada com sucesso ou se ocorreu alguma falha no processamento. Uma API que segue este design funciona se a interface de usuário ou aplicação cliente estiver ciente em que deve espelhar as ações destes Endpoints amarrando detalhes de sua implementação à API e todos os objetos de entrada e saída utilizados na comunicação, devendo conhecer detalhes sobre as operações orquestradas na API as quais necessariamente permaneceriam sob sua responsabilidade.
Suponhamos que um requisito adicional tenha surgido para sua aplicação, o qual deveria realizar algumas atualizações em alguns objetos existentes em pontos distintos no ecossistema. Utilizando uma aplicação Web-API baseada em Request-Response (REST/HTTP) um caminho seria fazer Pulling nos Endpoints que deveriam sofrer atualização e encaminhar cada uma das informações a serem atualizadas a cada Endpoint (REST/API) responsável por determinado dado, devendo a aplicação interessada nesta atualização centralizar todo o fluxo de processamento. Adicionalmente, ao utilizar REST poderíamos acrescentar níveis de maturidade maiores (HATEOAS) melhorando a experiência do cliente com nossa API de forma mais organizada, tornado-a RESTful, porém ainda assim não eliminaríamos estas características específicas do estilo de comunicação síncrono (Request-Response). Ainda assim, para a maior parte das soluções oferecer uma API baseada em REST trata-se de um grande ponto de partida.
Comunicação Assíncrona
EDA trata-se de um Design Pattern arquitetural no qual a comunicação entre os componentes é modelada usando Streams de Eventos propondo-se à realizar notificações de mudança de estado (Data Changes) da aplicação e dos componentes (microsserviços) promovendo o baixo acoplamento. Esta interação se limita ao envio de eventos por parte dos microsserviços, para um Event Bus (Barramento de Eventos) devendo em seguida ser despachado (Broadcast) para todos os componentes inscritos em determinados acontecimentos. Eventos são publicados (Publishers) por certos tipos componentes e recebidos (Subscribers) por outros, por meio de inscrição de interesse em determinados eventos por parte do componente interessado. De forma que, tanto o Publisher quanto o Subscriber não conhecem a identidade um do outro ficando esta tarefa encarregada ao Broker. Event-driven e a assincronicidade que este estilo de arquitetura promove, refere-se a otimização de tempo, onde não temos bloqueio de recursos para o atendimento das requisições enviadas, como tradicionalmente acontece em microsserviços que utilizam natureza de comunicação síncrona eliminando a ociosidade que encontramos quando implementamos chamadas tradicionais bloqueantes.
Arquiteturas baseadas em evento confiam em um canal de comunicação semelhante a um Message Bus atuando na camada de infraestrutura (Backbone) encarregando-se de entregar os eventos aos componentes inscritos, damos a este componente essencial no EDA o nome de Broker. Esta infraestrutura precisa ser confiável, rápida, escalável, entre outras coisas. Existem diversas opções disponíveis para desempenhar o papel do Broker como Apache Kafka, ActiveMQ, RabbitMQ, IBM MQ, etc. Um acontecimento notável no tempo, ocorrido dentro do ecossistema de microsserviços, representado por meio de eventos, será entregue para diversos microsserviços inscritos de uma só vez em um mesmo Broadcast pelo Broker de acordo com a quantidade de componentes interessados no evento. EDA pode ser utilizado nos microsserviços que necessitem desta natureza de comunicação voltada a eventos (implementada de forma assíncrona) visando oferecer autonomia e independência aos componentes. A adoção e promoção deste estilo de arquitetura necessita de dois ingredientes essenciais para seu sucesso, estar inserido em uma arquitetura de microsserviços além de utilizar Containers (por exemplo Docker) com Serverless Deployment (deploy sem servidor de aplicação, por exemplo Tomcat Web Container ou Netty) por sua natureza desacoplada, independente e auto contida.
Adotando Arquitetura de Eventos
Ser, por natureza Event-driven (EDA), seria assumir uma arquitetura distribuída composta por componentes independentes, em formato de microsserviços assíncronos adquirindo maior agilidade e mais inteligência ao interagir com um ecossistema voltado a eventos. A reação aos eventos segue uma ordem natural de acontecimentos na linha do tempo, assim como a publicação dos mesmos onde o fluxo de processamento somente segue seu curso natural tendo os eventos modificando o estado da aplicação. A possibilidade de reação a eventos ocorridos pode ser aplicada a qualquer componente interessado, bastando para isso, que ele se torne Subscriber de um evento. Ao tornar-se Subscriber de um evento deixamos o Broker ciente através da produção, detecção, consumo e reação aos eventos aplicados aos componentes, que passarão a ser notificados quando da chegada de novos eventos ao Event Bus. Os principais componentes necessários para a implementação do EDA são: Eventos, Produtores, Consumidores, Barramento de Eventos e Brokers.
O principal valor agregado ao negócio ao adotar-se EDA (Event-driven Architecture) seria a facilidade de estender o ecossistema com novos componentes, de forma modular, prontos para reagir a eventos existentes ou produzir novos sem o risco de comprometer as implementações existentes e seu funcionamento. Eventos são uma forma de capturar fatos e comportamentos de forma semelhante ao que ocorre na vida real, podendo estes, chegar de diversas fontes e em tempo real. Onde um evento é descrito como um acontecimento notável que ocorre dentro ou fora de seu domínio de negócio (execução de um processo de negócio) sendo utilizados para anunciar a mudança de estado dos objetos, atuando como um mecanismo de distribuição de estado da aplicação. Eventos são a essência do EDA bem como seu princípio de design chave, atuando como mecanismo de atualizações de estado contextualizadas das aplicações, os quais nos mostram entre outras coisas o que está acontecendo dentro de nossa aplicação e muitas vezes nos fazendo repensar a maneira como nosso software é construído. EDA promove o baixo acoplamento dos componentes no sentido de que para atender novas funcionalidades emergentes de requisitos de negócio no futuro simplesmente acrescentamos o novo componente que passará a escutar e reagir aos eventos existentes. Uma sequência relacionada e ordenada de eventos reflete um comportamento (geralmente chamado de Stream) na linha do tempo.
Eventos com EDA
Eventos, dentro do EDA, podem ser de dois tipos: Event Notification ou Event-Carried State Transfer. Sendo que no padrão Event Notification o evento apenas encarrega-se de notificar, de forma simples, que algo relevante ocorreu e que uma reação relacionada a este acontecimento pode ser desencadeada. Por outro lado, o padrão Event-Carried State Transfer descreve que um evento pode carregar consigo um Payload (corpo com informações relevantes ao acontecimento) além de Headers, Metadados e Timestamps, etc facilitando ao componente receptor do evento, que ao reagir obtém no próprio contexto de execução do evento o seu payload com dados relevantes para seu processamento. Para que possamos ter maior controle no manuseio da definição de tipos de dados para os objetos presentes nos Payloads dos eventos, uma alternativa seria utilizar Frameworks como Apache Avro que utiliza JSON para definir tipos de dados serializando estes dados em um formato binário compacto.
Diferente das APIs expostas utilizando REST, onde o controle e continuidade do fluxo de execução é de responsabilidade do consumidor da API (Orquestração), o processamento com EDA possui o papel de notificar por meio de eventos a chegada de novas mensagens (Coreografia) dividindo a responsabilidade da execução do fluxo em diversos microsserviços, cada qual responsável pelos eventos que são de seu interesse promovendo a natureza distribuída. Lembrando que, ao implementar EDA na prática, dentro de uma arquitetura de microsserviços, também podemos implementar Orquestração, possibilitando desta forma, que algum componente principal centralize o fluxo de processamento. Uma aplicação guiada por eventos é organizada de maneira a reagir a estes eventos podendo produzir novos ou encerrando a cadeia, estes eventos orquestram ações e reações dentro do processo em execução transformando o estado dos objetos continuamente até o término de sua execução. Ainda, técnicas como o padrão Split-join podem ser aplicadas durante o processamento do fluxo, onde atividades poderiam ser executadas em paralelo e somente após a chegada da última notificação de evento o fluxo daria prosseguimento a sua execução. Lembrando que como estes eventos serão controlados e gerenciados dependerá especificamente da implementação e infraestrutura que você construir, a responsabilidade de garantir eventualmente a consistência transacional de seus dados fica a cargo do Framework escolhido para atuar como Broker.
O Papel do Broker
No EDA, Brokers (Message ou Streams) são utilizados para intermediar a comunicação entre os microsserviços, os quais não possuem contato direto, por meio de roteamento de mensagens. O Message Broker Design Pattern não abrange conceitualmente modelos transacionais, deixando a cargo de Frameworks que implementam determinadas funcionalidades presentes em um Broker a decisão de ser naturalmente transacional ou não lidar com transações de maneira padrão. Pois em sistemas tradicionais, utilizando por exemplo um servidor de aplicação e filas JMS, a implementação da garantia de consistência transacional permanece sob responsabilidade da infraestrutura e não da aplicação, onde a mesma apenas executa dentro do escopo de uma transação já iniciada e ao final da execução do processo, marca a transação para que seja efetuado um commit ou rollback. EDA, por outro lado, promove a consistência eventual (Eventual Consistency) onde não são permitidas transações distribuídas atomicamente entre os microsserviços envolvidos, ficando a cargo de cada componente tratar da consistência de sua transação interna e notificando através de eventos componentes externos para que eventualmente garantam a integridade de seus dados. Cabe lembrar, ainda dentro da implementação do EDA na prática, quando erros ocorrem, os quais não permitem que a integridade dos dados seja garantida, eventos de compensação dos dados devem ser gerados possibilitando e informando ao ecossistema que devem desfazer o que foi feito, devendo a aplicação retroceder ao estado inicial porém ainda assim sem nunca transcender os limites transacionais de cada microsserviço.
A diferença entre Message Broker e Event Streaming é que Event Streaming permite-nos revisitar mensagens passadas na sequência em que foram executadas (Stream History) além de nos permitir agregar valores e carregar instâncias de entidades baseando-se em eventos ocorridos até o presente momento desde o último Snapshot (padrão Event Sourcing aplicado na prática) podendo ainda serem executados Replay de uma sequência de eventos reconstruindo comportamentos anteriores na linha do tempo (Change Log) facilitando o Troubleshooting (investigação de problemas ocorridos na aplicação). Streams de eventos podem ser acessados em tempo real e sequencialmente dado um ponto no tempo por diversos consumidores paralelamente e ajudam a estender o software além das naturezas de comunicação Request-Response (síncronas) comuns em abordagens que utilizam REST pois o baixo acoplamento entre os componentes permitem ao Publisher desconhecer as ações que serão tomadas dentro do ecossistema quando determinado acontecimento registrado por ele for gerado. O modelo característico de processamento Event Streaming é apresentado pelo padrão Publisher-Subscriber (possuindo 0 ou N inscritos) promovendo o anonimato e assincronia do ponto de vista do evento em uma cadeia bidirecional.
Mesclando EDA com HTTP APIs Síncronas
Dentro de um ecossistema EDA podemos implementar microsserviços utilizando uma combinação de naturezas de comunicação. Onde teríamos uma API REST sob HTTP em frente ao ecossistema EDA, gerenciando as requisições de origem e desencadeando e eventos a partir desta origem em um fluxo de Event Stream orquestrado e centralizado por ela. microsserviços assíncronos inscritos em eventos a partir da sequência do fluxo são anônimos do ponto de vista da API síncrona (REST/HTTP) inicialmente executada. Eventos são altamente acoplados ao conceito de comandos, podendo ser atômicos ou relacionados a outros eventos compondo uma sequência, onde neste caso o acúmulo de fatos determinam um comportamento de alguma funcionalidade do software. EDA adota um padrão para nos auxiliar a pensar na construção de nossa aplicação em uma ordem inversa se comparados ao modelo de aplicação tradicional, onde, ao modelarmos componentes que irão integrar um ecossistema assíncrono guiado por eventos precisamos primeiro imaginar estes comportamentos e seus estímulos para depois pensarmos nas ações derivadas destes e por fim na implementação. Eventos sempre carregam acontecimentos passados como consequência de alguma ação executada, sendo que em um cenário ideal guiado por eventos, eles, os eventos devem ser a primeira coisa a ser imaginada e pensada em tempo de modelagem de sua aplicação e a partir deste ponto o restante do projeto deve ser construído entorno dos mesmos.
APIs baseadas no modelo Request-response (REST/HTTP) são essenciais quando entregamos nossos microsserviços. Contudo, problemas relacionados à quantidade de informação a ser processada nos sistemas atuais, além das necessidades de ser Real-time, demandam cada vez mais a prática de estilos de arquitetura voltadas a eventos, onde, para ser Real-time é necessário tornar-se Event-driven. Para cada situação devem ser avaliados os casos de uso para definir-se assim o estilo de arquitetura mais apropriado para cada cenário, considerando cada problema em questão pois todos os estilos de arquitetura possuem sempre vantagens e desvantagens onde cada situação deve ser avaliada de forma individual e possuir decisões particulares entorno destas ao mesmo tempo em que não existem caminhos certos ou errados, somente Tradeoffs. Desta forma, como podemos observar, determinar entre uma abordagem síncrona (REST sob HTTP) ou assíncrona (EDA implementado utilizando Apache Kafka), e sua mudança de paradigma, não se trata de algo complexo quando apenas precisamos compreender as características e aplicabilidade de cada uma das abordagens nos contextos a serem aplicadas. A transição para uma arquitetura orientada a eventos tende a partir de simples implementações de casos de uso em direção a um entendimento cada vez mais profundo do processo de negócio e suas operações visando justificar e amadurecer a adoção do novo estilo de arquitetura, variando desde a compreensão de como um evento deve ser tratado dentro do fluxo do sistema até a observabilidade, confiabilidade e dependências que ele deverá possuir possibilitando com que você evolua seus componentes de maneira a torná-los cada vez mais inteligentes abrindo um leque de oportunidades cada vez mais ricas e modulares.
Considerações Finais
Em contra partida, adotar EDA adiciona a complexidade da garantia de transação via consistência eventual, pois não conta com a facilidade de atomicidade presente nas transações distribuídas requerendo pontos de atenção além de esforço extra de implementação. Além disso, esta adesão nos trás outros desafios os quais devemos lidar em aplicações distribuídas como Tracing e Logging (rastreabilidade) ou Error Handling (controle e tratamento de erros). Para adotar este paradigma você precisará de uma plataforma específica para auxiliar com tarefas desempenhadas pelo Broker (por exemplo Apache Kafka, RabbitMQ), além de avaliar os prós e contras com o objetivo de saber por onde começar e conhecer a fundo seu domínio de negócio onde residirão seus microsserviços inseridos em um ecossistema EDA. Ao aventurar-se com Event-driven Architecture (EDA), conforme mencionado, será necessário identificar eventos e comportamentos, adquirindo maturidade e cada vez mais subsídios para tomadas de decisão sobre o domínio dos componentes dentro de sua aplicação. Por fim, trata-se de uma jornada, que poderá ser positiva e agradável somente dependendo de você e de sua capacidade e interesse em tornar as expectativas em realidade, abraçando a oportunidade de adoção do EDA.
Arquiteto de Soluções | Team Leader na Compass
5 aEsse cara sabe muito.