Desafogando legados com streams de eventos
Hoje quero tratar de um assunto muito recorrente na vida da esmagadora maioria de desenvolvedores: convivência com legados.
Esse seria atualmente, na minha opinião, um dos maiores desafios das áreas de desenvolvimento de qualquer empresa média e grande hoje em dia. Por quê?
Diversos fatores impossibilitam uma completa reescrita desses módulos (dinheiro, tempo, entrega de novas funcionalidades perante concorrentes etc etc etc). E mesmo assim precisamos conviver com esse peso e ao mesmo tempo entregar novas funcionalidades e melhorias aos sistemas atuais.
Um meio-termo muito adotado é a escrita de diversas APIs em torno do legado. Esses pequenos módulos satélite são fáceis de testar, buildar, escalar e monitorar. Implementar novas funcionalidades no legado acaba se tornando muito trabalhoso e ineficaz.
E assim tudo progride: os times vão entregando novas funcionalidades, novas APIs aparecem e a quantidade de acessos aumenta. Até que um belo dia o legado começa a engasgar, e a performance das suas APIs despencam junto com ele.
E aí mora o perigo. Nesses sistemas, escalabilidade só acontece de maneira vertical, um processo custoso e não muito eficaz. Dependendo do legado, existem cenários onde mais hardware sequer cause um aumento de throughput por conta de alguns artifícios como locks no banco.
Agora fica a pergunta: Como alcançar um pouco de escalabilidade sem realizar alterações na infraestrutura do meu legado?
Isso mesmo amiguinhos: introduzindo eventos ao nosso ecossistema de APIs. Mas antes de explicar um modo de fazer isso, vamos a uma rápida introdução sobre processamento de eventos (fica aqui o link de um artigo excelente que aborda um pouco sobre o assunto).
Falando de uma maneira muito simplista, eventos são ações ou ocorrências interpretadas pelos sistemas. São geradas por um produtor (publisher) e recebidas pelos assinantes (subscriber) através de tópicos. Uma grande vantagem no paradigma de eventos é o desacoplamento: Um determinado tópico pode conter N assinantes que são notificados à cada geração de evento, sem qualquer conhecimento entre produtor e assinantes.
Passada essa explicação simplista sobre os conceitos, vou explicar como aplicar o uso de eventos para aliviar o tráfego dos legados.
Traçando uma analogia, o processo é muito parecido com o que está ocorrendo aqui agora enquanto você lê o artigo. A diferença básica é o evento não chegou automaticamente até vocês, e sim vocês que o procuraram.
A questão é que quando vocês processarem este evento, vão armazenar em suas bases locais (cérebro) as informações do artigo que acharam úteis.
O ponto principal é que o evento original (artigo) e a informação gerada por vocês (conhecimento adquirido) não terá o mesmo formato. É até possível que nada do que eu escrevi aqui seja útil e o evento seja descartado por completo.
E aplicando esta técnica conseguimos atingir alguns resultados muito legais:
- Todos os eventos são distribuídos via distribuição de mensagens aos tópicos.
- Todas as APIs interessadas assinam o tópico de um determinado evento e os processa como bem entender, persistindo os dados na sua própria base de dados apartada do legado.
- Como agora cada API possui sua própria base, os times passaram a ter mais liberdade para adotar escolhas mais variadas de bancos de dados de acordo com o propósito da aplicação. Alguns times continuaram com bancos SQL e outros partiram para o NoSQL.
Nesse projeto em específico, utilizamos um broker AMQP com exchanges para o envio dos eventos, mas existem tecnologias mais adequadas à esse uso como o Apache Kafka.
- Ao gerar o evento no legado, a API A atualiza também a sua própria base e publica esse evento no broker.
- As APIs que assinam os eventos gerados pela API A (B, C e D) recebem o evento do broker pelos workers.
- Os workers atualizam as bases locais das APIs B, C e D.
Pontos positivos dessa abordagem:
- Reduz a quantidade de interações com o legado, liberando capacidade de processamento.
- Aumenta a resiliência das APIs, uma vez que mesmo que o legado sofra alguma indisponibilidade, cada API possui sua cópia atualizada da informação e pode seguir servindo consultas mesmo com o legado fora do ar.
- Permite aos desenvolvedores escolher uma base de dados mais voltada à sua necessidade.
Pontos negativos:
- Workers mal-dimensionados podem causar atrasos no consumo dos eventos, fornecendo uma informação defasada ao consumidor da API.
- O broker se torna um ponto único de falha em toda a arquitetura, exigindo atenção redobrada com disponibilidade e retenção de dados.
- Como cada API possui uma base local, teremos muitos cenários de duplicação de dados (o que não é bem um contra, mas é bom citar essa particularidade).
Enfim gente, era isso que eu queria passar pra vocês. Espero que tenha valido o tempo de leitura!