Event-Driven Architecture (EDA)
Visão Geral
O backend é desenhado sob os princípios de Event-Driven Architecture (EDA). Historicamente, integrações entre microserviços sofriam com engarrafamentos ao chamarem uns aos outros de modo síncrono via REST. No paradigma Event-Driven, a pipeline abandona acoplamentos HTTP: os serviços operam reagindo ativamente sempre que um "evento" (como o upload de um currículo) é emitido.
Isso se torna fundamental quando consideramos que o processamento de um currículo pode levar segundos, devido à necessidade de chamar LLMs externos como o Ollama. Sem EDA, teríamos que esperar o processamento completo antes de responder ao usuário, o que geraria um bloqueio em cadeia em todos os serviços que antecederam a chamada ao LLM.
Além disso, a EDA permite que os serviços sejam escalados de forma independente, garantindo que o sistema seja resiliente a falhas e capaz de lidar com picos de tráfego.
Deep Dive 1: RabbitMQ
Para concretizar isso foi adotado o RabbitMQ (Broker AMQP). Todo bloco de texto ingerido atravessa este sistema em filas duráveis garantindo forte resiliência mesmo que o container do RabbitMQ caia.
Se um pico de dados ocorre - por exemplo via loader-resumes-kaggle disparando até 700 currículos de uma vez - o evento se enfileira no Broker (backpressure), assegurando que a infraestrutura local (serviços rodando conectando LLMs como Ollama e conectores de banco de dados) opere em carga constante, sem causar pânico no cluster inteiro e sem penalidade de perda de dados.
Deep Dive 2: Protocolos de Comunicação
A mensageria sustenta o orquestramento via três mecânicas de integração. Cada protocolo atende a um requisito específico da malha assíncrona:
A. Publish/Subscribe (ors.topic)
Os serviços emitem eventos (OpportunityScraped, ResumeUploaded) em uma Topic Exchange. Eles não conhecem quais ou quantos serviços irão escutá-los, configurando comunicação assíncrona "One-to-Many".
Esse mecanismo é a base para o funcionamento das pipelines de processamento de currículos e oportunidades, onde o resultado de cada etapa da pipeline é encapsulado em um evento e enviado para a Topic Exchange para que outros serviços possam consumi-lo e dar continuidade ao processamento.
B. RPC Over Message Queue
Determinadas rotinas requerem feedback direto, como as consultas no banco de dados via connectors. Contudo, utilizar REST para esse cenário se tornaria um grande gargalo no design por trás dos connectors já que eles são responsáveis por centralizar todas as operações de leitura e escrita aos banco de dados de toda a aplicação.
Para evitar gargalos, emprega-se padrão RPC Over Message Queue aproveitando o recurso nativo do RabbitMQ de Direct Reply-To que permite criar direct queues para trafegar as requisições e reply-to queues e correlation_id temporários para direcionar os retornos, unificando todo I/O externo no protocolo RabbitMQ.
O padrão RPC Over Message Queue é uma forma de comunicação síncrona em um sistema assíncrono.
C. Server Sent Events (SSE)
Para fornecer uma experiência fluida ao usuário, por exemplo em telas de loading, toda a comunicação Pub/Sub assíncrona gerada em backend isolation flui para o serviço notifier. Ele mantém o elo HTTP persistente One Way mandando bytes reativos para o navegador do usuário (exibindo telas de loading), traduzindo interações do RabbitMQ diretamente ao frontend via SSE.
Deep Dive 3: Em Baixo Nível (FastStream, FastAPI e o Event Loop)
Se detalharmos o funcionamento interno de um serviço em particular (um processo do sistema operacional), notamos que todas as tarefas do serviço são gerenciadas por uma única instância de Event Loop do Python (fornecido ao por meio da interface da lib nativa asyncio).
Porém mesmo que o Python já ofereça uma interface amigável para gerenciar as tarefas de forma assíncrona, a biblioteca FastStream se destaca por fornecer uma interface ainda mais amigável e poderosa com funcionalidades e integrações prontas para uso (com módulos do RabbitMQ, FastAPI, etc) que por esse motivo foi escolhida para ser a base dos serviços.
Para dar suporte ao FastStream, a biblioteca proprietária ors-shared unifica o modelo do payload dos eventos, configuração de serializadores unificados, logging em formato JSON, entre outros.
Cada serviço é uma aplicação FastStream (que pode ser integrado com FastAPI caso haja a necessidade de endpoints HTTP) que é configurado no arquivo main.py da seguinte forma:
- A inicialização de um microserviço (
apienotifier) levanta uma aplicaçãoFastAPIque serve endpoints HTTP (vital pra SSE ou Healthchecks) pareada na lifespan à instânciaFastStream. - Essa fusão faz com que ambas as malhas de rede residam unicamente num único Event Loop Assíncrono (
asyncio). - O roteador FastStream mantém uma conexão escutando permanentemente o socket AMQP. À medida que os bytes de uma submissão de texto de currículo transitam pela fila da CPU do SO:
- FastStream intercepta e faz unmarshal estrito testando o body aos modelos da
ors-shared. - FastStream aloca a mensagem como task e invoca a corrotina associada à flag da rota (
async def handler...). - Como a thread de Worker é
async, o Worker suspende a corrotina (await) no meio da requisição enquanto pede que um modelo de LLM rode, ou espera RPC do banco Neo4j. Outros eventos fluem naturalmente atendidos pela mesmíssima API thread.
- FastStream intercepta e faz unmarshal estrito testando o body aos modelos da
O sistema elimina lockings, evita a imprecisão de threading manual e executa a conversão dos de texto de currículos e perfis com extrema eficiência de recursos computacionais locais.