Desenvolvimento

15 fev, 2017

O que você quer dizer com “orientado a evento”?

Publicidade

No final do ano passado, participei de um workshop com meus colegas da ThoughtWorks para discutir a natureza das aplicações “orientadas a evento”. Nos últimos anos, temos construído muitos sistemas que utilizam muito os eventos, e eles têm sido muitas vezes elogiados e muitas vezes condenados. Nosso escritório norte-americano organizou uma evento, e desenvolvedores sênior da ThoughtWorks de todo o mundo apareceram para compartilhar ideias.

O maior resultado foi reconhecer que quando as pessoas falam de “eventos”, elas realmente se referem a coisas muito diferentes. Por isso, passamos muito tempo tentando esclarecer o que poderiam ser alguns padrões úteis. Este artigo é um breve resumo dos principais identificados.

Notificação de evento

Isso acontece quando um sistema envia mensagens de eventos para notificar outros sistemas sobre uma alteração em seu domínio. Um elemento-chave da notificação de eventos é que o sistema de origem não se importa muito com a resposta. Muitas vezes, não espera nenhuma resposta ou, se há uma resposta com a qual a fonte se preocupa, isso é indireto. Haveria uma separação marcada entre o fluxo lógico que envia o evento e qualquer fluxo lógico que responda a alguma reação a esse evento.

Notificação de evento é bom porque implica um baixo nível de acoplamento e é bastante simples de configurar. Ela pode se tornar problemática, no entanto, se realmente há um fluxo lógico que é executado sobre várias notificações de eventos. O problema é que pode ser difícil ver um fluxo, pois ele não é explícito em qualquer texto do programa. Muitas vezes, a única maneira de descobrir esse fluxo é monitorando um sistema ao vivo. Isso pode dificultar a depuração e modificar esse fluxo. O perigo é que é muito fácil fazer sistemas bem dissociados com a notificação de eventos, sem perceber que você está perdendo de vista esse fluxo em grande escala e, assim, se preparar para problemas nos próximos anos. O padrão ainda é muito útil, mas você tem que ter cuidado com a armadilha.

Um exemplo simples dessa armadilha é quando um evento é usado como um comando passivo-agressivo. Isso acontece quando o sistema de origem espera que o destinatário execute uma ação e deve usar uma mensagem de comando para mostrar essa intenção, mas, em vez disso, desenha a mensagem como um evento.

Um evento não precisa carregar muitos dados, muitas vezes apenas algumas informações de id e um link de volta para o remetente que pode ser consultado para obter mais informações. O receptor sabe que algo mudou, pode obter algumas informações mínimas sobre a natureza da mudança, mas em seguida envia um pedido de volta para o remetente para decidir o que fazer a seguir.

Transferência do estado do Event-Carried

Esse padrão aparece quando você deseja atualizar os clientes de um sistema de tal forma que eles não precisam entrar em contato com o sistema de origem para fazer mais trabalho. Um sistema de gerenciamento de clientes pode disparar eventos sempre que um cliente alterar seus detalhes (como um endereço) com eventos que contenham detalhes dos dados alterados. Um destinatário pode, então, atualizar sua própria cópia dos dados do cliente com as mudanças, de modo que nunca precise conversar com o sistema principal do cliente para fazer seu trabalho no futuro.

Uma desvantagem óbvia desse padrão é que há muitos dados schlepped ao redor e muitas cópias. Mas isso é menos um problema em uma era de armazenamento abundante. O que ganhamos é maior resiliência, uma vez que os sistemas de destinatário podem funcionar se o sistema do cliente se tornar indisponível. Reduzimos a latência, uma vez que não há chamada remota necessária para acessar as informações do cliente. Não temos que nos preocupar com a carga no sistema do cliente para satisfazer as consultas de todos os sistemas de consumo. Mas ele envolve mais complexidade no receptor, uma vez que tem que classificar a manutenção de todo o estado, quando é geralmente mais fácil apenas chamar o remetente para obter mais informações quando necessário.

Sourcing de eventos

A ideia central do sourcing de eventos é que sempre que fazemos uma mudança no estado de um sistema, registramos essa mudança de estado como um evento e podemos com confiança reconstruir o estado do sistema, reprocessando os eventos a qualquer momento no futuro. O armazenamento de eventos se torna a principal fonte de verdade, e o estado do sistema é puramente derivado dele. Para os programadores, o melhor exemplo disso é um sistema de controle de versão. O log de todos os commits é o armazenamento de eventos, e a cópia de trabalho da árvore de origem é o estado do sistema.

Sourcing de eventos introduz uma série de questões, que eu não vou abordar aqui, mas eu quero muito destacar alguns equívocos comuns. Não é necessário que o processamento de eventos seja assíncrono; considere o caso de atualizar um repositório local do git – isso é inteiramente uma operação síncrona, assim como a atualização de um sistema centralizado de controle de versão como subversão. Certamente ter todos esses commits permite que você faça todos os tipos de comportamentos interessantes, git é o maior exemplo, mas o núcleo do commit é fundamentalmente uma ação simples.

Outro erro comum é assumir que todo mundo que usa um sistema de sourcing de eventos deve entender e acessar o log de eventos para determinar dados úteis. Mas o conhecimento do log de eventos pode ser limitado. Estou escrevendo isso em um editor que é ignorante de todos os commits na minha árvore de origem, ele apenas presume que há um arquivo no disco. Grande parte do processamento em um sistema de sourcing de eventos pode ser baseada em uma cópia de trabalho útil. Somente elementos que realmente precisam das informações no log de eventos devem ter que manipulá-lo. Podemos ter várias cópias de trabalho com diferentes esquemas, se isso ajudar; mas geralmente deve haver uma separação clara entre o processamento de domínio e o derivar uma cópia de trabalho do log de eventos.

Ao trabalhar com um log de eventos, geralmente é útil criar snapshots da cópia de trabalho para que você não precise processar todos os eventos a partir do zero toda vez que precisar de uma cópia de trabalho. Na verdade, há uma dualidade aqui. Podemos olhar para o log de eventos como uma lista de alterações ou como uma lista de estados. Podemos derivar uma da outra. Sistemas de controle de versão geralmente mesclam snapshots e deltas em seu log de eventos para obter o melhor desempenho. [1]

Sourcing de eventos tem muitos benefícios interessantes, que facilmente vêm à mente quando se pensa no valor dos sistemas de controle de versão. O log de eventos fornece uma forte capacidade de auditoria (as transações contábeis são uma fonte de eventos para saldos de contas). Podemos recriar estados históricos reproduzindo o log de eventos até um ponto. Podemos explorar histórias alternativas, injetando eventos hipotéticos ao reproduzir. O sourcing de eventos torna plausível ter cópias de trabalho não duráveis, como uma Memory Image.

Sourcing do eventos tem seus problemas. A reprodução de eventos torna-se problemática quando os resultados dependem de interações com sistemas externos. Temos que descobrir como lidar com as mudanças no esquema de eventos ao longo do tempo. Muitas pessoas acham que o processamento de eventos adiciona muita complexidade a um aplicativo (embora eu fique pensando se isso é mais devido à pobre separação entre componentes que derivam uma cópia de trabalho e componentes que fazem o processamento de domínio).

CQRS

Command Query Responsibility Segregation (CQRS) é a noção de ter estruturas de dados separadas para ler e escrever informações. Estritamente, CQRS não é exatamente sobre eventos, uma vez que você pode usar CQRS sem quaisquer eventos presentes em seu projeto. Mas comumente as pessoas combinam CQRS com os padrões anteriores aqui, daí sua presença no evento.

A justificativa para o CQRS é que, em domínios complexos, um único modelo para lidar com as leituras e escritas fica muito complicado e podemos simplificar separando os modelos. Isso é particularmente atraente quando você tem uma diferença nos padrões de acesso, como muitas leituras e poucas gravações. Mas o ganho para usar CQRS tem que ser equilibrado contra a complexidade adicional de ter modelos separados. Acho que muitos dos meus colegas são profundamente cautelosos de usar CQRS, encontrando-o muitas vezes mal utilizado.

Fazendo sentido com esses padrões

Como uma espécie de botânico de software, ansioso para coletar amostras, acho que esse é um terreno capcioso. O problema central é confundir os diferentes padrões. Em um projeto, o gerente de projeto capaz e experiente me disse que o sourcing de eventos tinha sido um desastre – qualquer mudança levou o dobro do trabalho para atualizar tanto os modelos de leitura quanto os de gravação. Só nessa frase, eu posso detectar uma confusão potencial entre sourcing de eventos e CQRS – então, como eu posso descobrir qual foi o culpado? A liderança tecnológica no projeto afirmou que o principal problema eram as muitas comunicações assíncronas, certamente um conhecido reforçador de complexidade, mas não uma que é uma parte necessária de qualquer sourcing de eventos ou CQRS. Além disso, temos que ter cuidado com o fato de que todos esses padrões são bons no lugar certo e ruins quando colocados no terreno errado. Mas é difícil descobrir qual é o terreno certo quando se confundem os padrões.

Eu adoraria escrever um tratado definitivo que classifica toda essa confusão e dar orientações sólidas sobre como fazer bem cada padrão e quando ele deve ser usado. Infelizmente, eu não tenho tempo para fazê-lo. Eu escrevo este artigo na esperança de que será útil, mas estou completamente ciente de que serve bem sobre o que é realmente necessário.

[1]: Às vezes, ouço as pessoas dizerem que o git não é um exemplo de sourcing de eventos porque armazena estados de arquivos e árvores em .git/objects. Mas se um sistema usa alterações ou snapshots para seu armazenamento interno, não afeta se é evento sourced. Git felizmente irá reunir uma lista de mudanças para mim, sob demanda. E quando ele comprime dados em pacotes, ele usa uma combinação de snapshots e mudanças, com a mistura escolhida por razões de desempenho.

***

Martin Fowler faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://martinfowler.com/articles/201701-event-driven.html