Uma arquitetura simples e eficiente para sistemas Event-Driven em Python— Parte I
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.