Uma arquitetura simples e eficiente para sistemas Event-Driven em Python— Parte I

Cassio R. Eskelsen
4 min readOct 4, 2020

--

Algum lugar no sudeste da Turquia — 2015

Parte 1 — O caminho até o Event-Driven

Quando comecei a trabalhar com microsserviços uma das minhas dúvidas iniciais foi como fazer a comunicação entre eles. A literatura nos traz diversas formas de se fazer isso, entre as quais podemos destacar:

a) De forma síncrona: normalmente através de APIs REST, os microsserviços expõe endpoints para receber, processar e entregar informações

b) De forma assíncrona: usa-se alguma mensageria (Rabbit, Azure Service Bus, AWS SQS, etc) para passar informações de um microsserviço para o outro.

A forma deve ser escolhida sempre levando-se em conta o contexto. A realidade e as necessidades de cada empresa/aplicação devem dirigir essa escolha antes de qualquer extremismo técnico.

No entanto é necessário apontar desvantagens e vantagens de cada modelo. Os ítens que irei apresentar são frutos da minha visão pessoal, não devem ser levados como escrita na pedra:

A comunicação síncrona é a forma mais simples e que exige uma menor curva de aprendizado. Além disso, em um primeiro momento, parece proporcionar uma visão mais rápida do que está ocorrendo durante um debug. No entanto, o preço disso é o acoplamento forte entre os microsserviços. Se um serviço parar, para tudo. É necessária a implementação de patterns como o Circuit Breaker para garantir a estabilidade geral do sistema.

Costumo dizer que a comunicação via APIs tende a produzir um “monolito de baixas calorias”: você distribui o processamento, mas se TODOS serviços não estiverem no ar, nada funciona direito.

Em uma arquitetura com comunicação síncrona você precisa implementar blocos de código para garantir a entrega da mensagem. Se o pedido não conseguiu ser enviado para o financeiro para aprovação, o que fazer? Marcar um flag na base? Quando tentar enviar novamente? Ficar em um loop eterno até conseguir enviar?

Por outro lado, a comunicação assíncrona entre microsserviços normalmente é implementada através de algum broker de mensageria (Rabbit, Redis, etc): as mensagens são enviadas para filas pelos producers e processadas em regime de FIFO (First in, First Out) pelos consumers. Como as mensagens são disparadas em esquema de fire-and-forget, não há necessidade de uma rotina para tentativa de reenvio das mensagens. A única coisa que você precisa garantir é que o broker esteja no ar.

Essa forma de comunicação envolve uma dose de confiança: não temos como garantir que o consumer recebeu a mensagem e algum processo de negócio pode ser interrompido em um eventual problema no broker. Se precisarmos ter 100% de segurança temos que implementar mecanismos de monitoramento e recuperação de falhas.

Então a partir de agora é só jogar tudo em filas e pronto?

Não! Após algum tempo de uso de uma arquitetura assíncrona você começa a perceber alguns problemas, entre os quais podemos citar:

a) O fluxo continua sendo pensado linearmente: os consumers e os producers são idealmente acoplados: espera-se que para cada mensagem enviada por um producer cause um comportamento específico em um consumer.

b) Não se consegue alterar o fluxo sem um bom refactoring.

c) Se inicialmente o microsserviço Z recebia mensagem apenas do microsserviço X mas depois de um tempo ele passa a receber de Y também, você não tem como saber de qual producer veio a mensagem, caso precise fazer um comportamento ligeiramente diferente de acordo com a origem.

Além disso, podemos citar alguns outros problemas típicos quando se trabalha com algum broker:

a) Erros na declaração dos nomes das Exchanges/Queues em microsserviços diferentes;

b) Parâmetros diferentes na declaração de filas nos producers e nos consumers. Quem nunca declarou uma fila como durable=True em um microsserviço e em outro declarou a mesma com durable=False?

c) Não existe um mecanismo simples para parar o consumo da fila caso aconteça algum problema e retomar assim que a situação é normalizada.

Parte destes problemas são resolvidos utilizando uma Arquitetura Event-Drive.

Na próxima parte, veremos o que é o Event-Driven e uma biblioteca que facilita essa arquitetura bem como endereça os problemas adicionais acima mencionados.

--

--