Sistemas de software apoiam ou automatizam a execução de atividades. A ocorrência de uma atividade pode ser descrita como “evento”. Sempre que um evento acontece, é possível gerar um “registro” que o descreva. Este “registro descritivo”, por sua vez, pode conter informações como o tipo da atividade, quando ela aconteceu, além de outros dados relevantes associados.
A venda de um produto, em um site de e-commerce, por exemplo, é um evento. O registro associado seguramente conteria o momento da venda, o produto que foi vendido, as condições de pagamento e entrega, o cliente que realizou a compra, entre outras informações.
A ocorrência de um evento frequentemente estará associada a uma mudança relevante do “estado interno” de um componente. |
A captura de dados de um sensor metereológico, em sistemas com IoT para suporte a indústria agro, também é um evento. O registro associado poderia conter a identificação do sensor, o momento da coleta, temperatura e outras informações relacionadas a condição climática.
Se um “registro descritivo” for gerado para cada atividade relevante suportada por um componente, então, pode-se assumir que a operação desse componente irá produzir um “fluxo de eventos” (event stream). Se este fluxo for apropriadamente armazenado, constituirá uma “fonte da verdade atemporal”. Afinal, conterá um registro não apenas das mudanças de estado que aconteceram, mas também as “motivações” (comportamentos) que geraram tais mudanças, habilitando event sourcing.
Definição: Event Sourcing
Event sourcing consiste no armazenamento de todos os registros descritivos de eventos significativos para o estado de um componente, ao longo do tempo, de forma a permitir, por meio da “replay“, a restauração “posições de estado” consolidadas em qualquer momento do tempo.
Se um componente “produzir” os registros descritivos de um evento, publicando-o em algum mecanismo que permita “observação”, então será possível que outros componentes possam “consumir” tais registros, apropriadamente, disparando atividades complementares ou atualizações internas de estado. Eventualmente, esses “componentes consumidores”, ao regir um evento, também atuem como “produtores”.
EDA e Domain-driven design
A elaboração de arquiteturas event-driven tem sido facilitada pelo uso de Domain-driven Design.
A identificação de contextos delimitados, com suas respectivas responsabilidades, faz emergir naturalmente os “eventos de domínio”, utilizados sobretudo para a comunicação entre contextos diversos, de maneira alinhada as demandas do negócio.
Aliás, uma das técnicas mais populares para “descoberta” do domínio é centrada em eventos: event storming.
Voltando ao cenário de um sistema de e-commerce, a publicação, por parte do componente “carrinho”, da consolidação de uma venda, pode servir como “gatilho” para que o componente “cobrança” efetue
Event-driven architectures implementam as características descritas nos parágrafos anteriores. Elas se destacam, acima de tudo pela redução dramática do acoplamento e melhora da responsibividade.
Diferença entre responsividade e desempenho
Frequentemente, há confusão entre responsividade e desempenho.
A responsividade é um atributo de qualidade que tem relação com o tempo que um usuário ou sistema cliente tem de esperar para, após iniciar alguma operação, poder iniciar outra.
O desempenho, também um atributo de qualidade, tem relação com o tempo total demandado por um sistema computacional para processar uma operação completamente.
Assim, um sistema pode ser responsivo sem ser performático.
Desmistificando Event-driven Architectures
Arquiteturas event-driven atendem objetivos de negócio, respeitando restrições, atingindo determinados padrões em atributos de qualidade, adotando padrões e práticas de design que colocam “eventos” no centro.
Definição: Evento
É o reconhecimento formal de um fato que, provavelmente, provocou uma modificação relevante no estado de uma aplicação.
No dia-a-dia, o termo “evento” também é frequentemente associado com “registros descritivos” contendo informações sobre um evento propriamente dito.
Entretanto, é importante que se destaque que dois sistemas, alegadamente event-driven, podem adotar padrões e práticas de design completamente distintas.
Dois sistemas podem ter arquiteturas event-driven, entretanto, com características significativamente disitintas. |
The Many Meanings of Event-Driven Architecture Martin Fowler, em uma palestra para o GOTO, em 2017, fala sobre os “muitos entendimentos” a respeito |
Ativações por notificações
Há três “modelos mentais” para ativação entre serviços:
- ativação ativa síncrona, via chamada de APIs, quando um serviço precisa recuperar informações sob controle de outro ou, também, quando um serviço deseja “disparar” uma ação em outro serviço.
- ativação ativa assíncrona, via envio de mensagens (comandos ou queries), através de um message-broker
- ativação passiva assíncrona, via eventos, através de event-broker
Arquiteturas event-driven baseiam-se, frequentemente, no terceiro modelo.
Definição: Event-broker
Event-broker é um componente responsável por “receber eventos”, armazená-los em uma fila ou em streams, e disponibilizá-los para outros componentes.
Eventualmente, event-brokers, realizam tarefas administrativas como compactação.
Ativação passiva assíncrona habilita responsibividade e resiliência.
Event sourcing
Fazer com que um sistema lance eventos de notificação para toda modificação no estado de uma entidade abre espaço para algumas soluções interessantes. Podemos, por exemplo, usar os eventos de notificação para, além de permitir implementação desacoplada de consistência eventual entre aplicações, contar a “história” de entidades, através de uma técnica conhecida como Event Sourcing.
A ideia central da técnica é reconhecer que o estado de uma entidade é “explicado” pela ocorrência de uma sequência clara de eventos, em uma ordem determinada.
Com posse do histórico de eventos de notificação de alteração de estado, podemos restituir qualquer um dos “estados históricos” de uma entidade, bastando, para isso, que realizar um replay do seu histórico de eventos até o ponto apropriado.
Em termos simples, mantendo o histórico de eventos armazenado, podemos “recuperar” o estado de uma entidade, em qualquer momento. Podemos também saber quando mudanças ocorreram, quem fez essas mudanças e qual foi a intenção de negócio. Poderoso, porém complexo!
Sistemas que usam event sourcing costumam manter, além da base de eventos, uma outra base com materializações do estado da entidade em algum momento (geralmente o atual).
Essa materialização é útil por ser extermamente fácil de pesquisar. Podemos pensar essas materializações como “fotografias” do estado da entidade em algum momento. Talvez por isso, convencionou-se utililizar a designação snapshot.
A base de dados utilizada para armazenar eventos geralmente é nosql. A base para os snapshots pode ser quaquer uma, inclusive relacionais. Aliás, Greg Young, que formalizou o conceito de Event Sourcing, criou um banco de dados para armazenar eventos chamado EventStore.
Datomic: desafios e benefícios de um banco de dados imutável O Nubank utiliza Datomic, uma base de dados imutável, incremental, registrando eventos. |
Event-carrier state transfer
Event brokers se convertem em fonte da verdade, substituindo demandas naturais por recuperação de estado através de requisições de APIs, por consolidação de dados em bases locais, mediante processos de seleção, transformação e redução.
Duas topologias para colaboração em EDA
Há duas topologias fundamentais para implementação de colaboração de arquiteturas event-driven. A primeira delas é baseada na ideia de mediadores ativos e orquestração. A segunda, é baseada na utilização de intermediadores (brokers) e coreografia.
Implementações event-driven que utilizam mediadores ativos e orquestração são mais apropriadas para cenários onde o tratamento de eventos precise ser gerenciado, com forte controle sobre falhas e sobre o andamento. Por outro lado, implementações com brokers e coreografia são mais apropriadas para estruturas dinâmicas, fire-and-forget.Ambas as abordagens assumem que um “evento inicial” será capturado pelo sistema iniciando processamento.
Usando mediadores ativos e orquestração
A principal característica de uma arquitetura event-driven com mediadores ativos e orquestração é a presença de um componente orquestrador (o mediador ativo) que gerencia e controla o fluxo de atividades, acionando componentes processadores, de maneira organizada, em tempos apropriados.
Nessa topologia, explícita, um “evento inicial” é submetido a uma fila que é “escutada” pelo componente mediador. Este componente, por sua vez, “sabe” quais procedimentos que devem ser executados para “tratar” o evento e em que sequência. Por isso, aciona “executores”, notificando-os com eventos através de canais de eventos point-to-point. Toda vez que um componente executor conclui seu trabalho, geralmente notifica o componente mediador.
Os componentes executores comunicam-se apenas com o componente mediador, nunca com outros componentes executores. Enquanto isso, os componentes mediadores são a “fonte da verdade” com relação ao estágio de execução de um fluxo para tratamento de um evento.
De maneira concreta, quando o mediador exerce função ativa, direcionado o workflow, muitas vezes comunica-se enviando comandos para os diversos processadores.
Geralmente, aplicações com arquiteturas event-driven desenvolvidas nessa abordagem têm diversos componentes mediadores, algumas vezes, agrupados processos correlatos, outras vezes, ponderando estratégias para atender atributos de qualidade como disponibilidade, elasticidade e performance.
Mediadores podem ser implementados em código puro ou utilizando tecnologias como Apache Camel e Mule ESB ou Apache ODE. Há também quem defenda a utilização de ferramentas BPM (e BPA).
Usando brokers e coreografia
Diferente da abordagem utilizando mediadores e orquestração, a utilização de brokers e coreografia se destaca pela inexistência de um componente central, responsável pelo sequenciamento dos trabalhos.
Nessa topologia, implícita, componentes executores são acionados de maneira encadeada, onde um componente executor encaminha um evento para um message broker (como RabbitMQ ou outras implementações AMQP), sempre que conclui seu trabalho, acionando outro(s) componente(s) executor(es), e assim sucessivamente, até que todo o processamento necessário aconteça.
A estratégia de cada componente executor ao encaminhar eventos é do tipo fire-and-forget e é muito importante que todo processamento executado seja notificado, com eventos, ao sistema, ampliando chances de extensibilidade ao custo de desperdício de recursos computacionais (com eventos que não são “escutados” por ninguém).Fire-and-forget
Fire-and-forget é um padrão de mensageria onde o produtor envia uma mensagem para um consumidor sem esperar resposta. Trata-se da forma mais simples de troca de mensagens.
Fire-and-Forget destaca-se pelo baixíssimo acoplamento, afinal o produtor não precisa saber nada sobre seus consumidores, incluindo quantos existem ou o que eles fazem com a mensagem. Esse tipo de interação sem estado resulta em sistemas de mensagens altamente escaláveis.
A simplicidade e o baixo acoplamento, entretanto, tem seu preço: o tratamento de erros não é possível porque não há feedback sobre a “entrega” das mensagens. Assim, implementações Fire-and-Forget devem utilizar mecanismos mais robustos de “entrega” ou aceitarem que algumas mensagens possam ser perdidas. Além disso, é importante ter consciência de que um consumidor terá pouco a fazer caso a mensagem recebida esteja mal-formada ou com dados inválidos.
Três padrões de processamento
Componentes executores assumem, basicamente, três padrões de comportamento:
- processamento simples;
- processamento de streams;
- processamento de eventos complexos (complex event processing – CEP).
Enquanto o padrão de processamento simples ocorre tanto arquiteturas orquestradas e coreografadas, processamento de streams e de eventos complexos costumam ser implementadas apenas em topologias coreografadas.
O detalhamento desses padrões de execução estão além do escopo desse livro. Aliás, há obras inteiras dedicadas ao tema.
Processamento simples
Componentes executores que operam adotando processamento simples, “escutam” um determinado tipo de mensagem e executam, para cada ocorrência, algum trabalho. Trata-se do padrão mais fácil de implementar e, provavelmente, mais comum.
Processamento de streams
Componentes executores que operam adotando processamento de streams, podem “escutar” diversas ocorrências de eventos mas só executam algum processamento quando algum critério é atendido. Estes executores operam agregando, analisando e processando streams de eventos.
Considere, por exemplo, um componente executor recebendo todas as transações de um determinado cartão de crédito. Caso duas transações presenciais (dois eventos) sejam executadas em localidades afastadas a uma distância maior do que aquela que poderia ser percorrida no intervalo de tempo entre as ocorrências, este executor poderia gerar um evento (agregação) indicando possibilidade de fraude.
Processamento de eventos complexos (CEP)
Componentes executores que operam adotando processamento de eventos complexos, identificam eventos significativos (como oportunidades ou ameaças) a partir de eventos menos expressivos, oriundos de dois ou mais streams.
Contratos para eventos
O acoplamento entre serviços, tradicional em soluções que não têm EDA, é substituído por acoplamento com contratos dos registros descritivos dos eventos.
Uma bom contrato indica muito mais do que “algo aconteceu”. Bons contratos indicam “tudo que aconteceu”, servindo, de fato, como “fonte da verdade” quando uma verificação ocorrer (sem necessidade de pesquisas complementares).
Idealmente, os “esquemas” associados a contratos devem ficar registrado em um registry.
Reatividade natural
Arquiteturas event-driven, quando bem implementadas, são naturalmente reativas. Ou seja, são responsivas, resilientes, elásticas e orientadas a mensagens.
A responsividade é natural ao processamento assíncrono, comum em EDAs.
A resiliência, por sua vez, é garantida pela estabilidade dos mecanismos de mensageria, que “seguram” os eventos caso exista alguma instabilidade nos componentes processadores.
A elasticidade é possível graças ao desacoplamento dos componentes executores dos demais. Em princípio, quando a demanda sobe pela ocorrência de mais eventos, componentes executores mais pesados podem ter novas instâncias criadas e descartadas conforme a escala oscila.
Por fim, o “fluxo dos eventos” é, quando é usado um mecanismo adequado de mensageria, naturalmente orientado a mensagens.
Conway, outra vez!
Arquiteturas event-driven geralmente são particionadas pelo domínio, o que facilita a formação de times especialistas para características do negócio.
Geralmente, a estrutura dos times irá replicar a topologia dos componentes executores e mediadores. Pelo alto acoplamento aferente, a infraestrutra deverá ter ownership de um time dedicado, mesmo com práticas maduras de DevOps, afim de evitar difusão de responsabilidades. O mesmo é válido para componentes mediadores em função do alto acoplamento eferente.Indicações e contraindicações
Arquiteturas event-driven são poderosas e estão alinhadas com boas práticas para implementação de sistemas modulares, até mesmo com microsserviços. Entretanto, são naturalmente complexas e quando esta complexidade não for bem justificada, a adoção deste estilo poderá representar incremento de custo difícil de justificar.A capacidade de lidar melhor com cenários dinâmicos e complexos vem com o custo indireto de lidar com consistência eventual. As facilidades para escalar vem com o custo indireto de maior dificuldade para monitorar. As facilidades para adaptar e responder a demandas do negócio vem acompanhadas de maior dificuldade para testes de integração.
// TODO
Antes de avançar para o próximo capítulo, faça as seguintes reflexões:
- O sistema em que está trabalhando agora lida, naturalmente, com eventos?
- Baseado em sua experiência, com que frequência o aumento da complexidade justificaria “transformar” sistemas request-driven em event-driven?
- Sistemas escaláveis são, geralmente, assíncronos. Concorda com essa afirmação?
- Em que cenários adotaria Event Sourcing?