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:

  1. Todos os eventos são distribuídos via distribuição de mensagens aos tópicos.
  2. 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.
  3. 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.

  1. Ao gerar o evento no legado, a API A atualiza também a sua própria base e publica esse evento no broker.
  2. As APIs que assinam os eventos gerados pela API A (B, C e D) recebem o evento do broker pelos workers.
  3. 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!