Como escalei uma arquitetura de leitura de dados reduzindo custos de infraestrutura e desacoplando um monólito legado.
Por Leonardo Policarpo
Engenheiro de Software | Arquitetura de Backend & Entusiasta de IoT
🇺🇸 Read the English version here
Recentemente, trabalhei em um desafio comum em grandes cenários corporativos: a necessidade de criar novas funcionalidades performáticas e escaláveis que precisam consumir dados de uma base de dados consolidada, sem comprometer a estabilidade do sistema central.
O objetivo era arquitetar um microsserviço Read-Only (apenas leitura) para alimentar interfaces de visualização de dados e rotinas de exportação. O requisito de negócio era agilidade na entrega; o requisito técnico que me impus foi excelência de engenharia e manutenibilidade a longo prazo.
Neste artigo, compartilho as decisões arquiteturais tomadas para garantir uma solução robusta e desacoplada utilizando Node.js, Clean Architecture, Prisma e Docker.
1. O desafio da engenharia
O cenário envolvia complexidades típicas de sistemas que cresceram organicamente ao longo de anos:
- Uma base de dados relacional (SQL) com grande volume de dados e estruturas complexas.
- Necessidade de alta disponibilidade para servir múltiplos clientes simultâneos sem bloquear o Event Loop do Node.js.
- Requisitos estritos de segurança e isolamento total entre ambientes de homologação e produção.
- Infraestrutura na AWS que precisava ser otimizada para eficiência de custos (Compute/Storage).
2. Clean Architecture:
Para garantir a longevidade do projeto, foi adotada a Clean Architecture. O objetivo principal foi isolar as regras de negócio de detalhes de implementação como frameworks web ou drivers de banco de dados.
A estrutura foi organizada em camadas com responsabilidades definidas:
- Domain: Interfaces e modelos puros da aplicação.
- Data: Casos de uso e regras de negócio.
- Infra: Implementações externas (Repositórios, Criptografia, Integrações).
- Main: Camada de composição que injeta dependências e inicializa o serviço.
Essa separação permite, por exemplo, substituir o framework HTTP (ex: Express por Fastify) ou o ORM no futuro com impacto zero na lógica de domínio.
3. Integração com Bases Existentes (Prisma ORM)
Um dos desafios técnicos era mapear a estrutura de dados pré-existente de forma segura e tipada. Queries manuais tendem a ser frágeis em manutenções futuras.
A solução foi utilizar o Prisma ORM com sua funcionalidade de Introspection. Ao invés de gerenciar migrations (que não eram o escopo deste serviço satélite), configurei o Prisma para ler o schema da base existente e gerar a tipagem TypeScript automaticamente.
Para lidar com tabelas de relacionamento antigas que utilizam chaves compostas em vez de identificadores únicos, utilizei o mapeamento nativo do schema:
model ExampleRelation {
userId Int
itemId Int
// Define a identidade única pela combinação das colunas
@@id([userId, itemId])
}
Isso trouxe Type Safety para o consumo de dados, prevenindo erros de runtime e acelerando o desenvolvimento.
4. Performance e Eficiência:: Docker Multi-stage e PM2 Cluster
A otimização de infraestrutura foi focada em reduzir o footprint de memória e maximizar o uso de CPU.
Docker Multi-stage Build
Para evitar imagens pesadas contendo arquivos desnecessários de desenvolvimento, implementei um build em múltiplos estágios usando Alpine Linux:
- Builder Stage: Instala todas as dependências, compila o TypeScript e gera os artefatos.
-
Runner Stage: Copia apenas o código transpilado (
dist) e as dependências de produção.
Resultado: Imagens finais compactas (cerca de 150MB), resultando em deploys mais rápidos e redução de custos de armazenamento no Registry (ECR).
PM2 em Cluster Mode
Por ser single-threaded, uma aplicação Node.js padrão utiliza apenas um núcleo do processador, o que pode subutilizar recursos em instâncias de nuvem multi-core.
A implementação do PM2 em modo Cluster permitiu que a aplicação escalasse verticalmente dentro do container. O gerenciador sobe múltiplos processos (workers) baseados na disponibilidade de CPU, otimizando o throughput. Além da performance, essa estratégia é crucial para resiliência: caso um worker precise reiniciar, o balanceador interno mantém a disponibilidade do serviço redirecionando o tráfego.
Visão conceitual da arquitetura proposta:
5. Imutabilidade e Segurança
Para garantir consistência entre ambientes, implementei uma pipeline de CI/CD baseada em artefatos imutáveis:
- Um único
Dockerfileé usado para todos os ambientes (Dev, Homol, Prod). - As variáveis de ambiente sensíveis são injetadas apenas no momento da execução do container.
- Segregação de ambientes via Tags no Registry de containers.
Na camada de aplicação, a segurança foi reforçada com middlewares de CORS estrito (permitindo apenas domínios autorizados) e validação de autenticação em todas as rotas.
Conclusão
Este estudo de caso demonstra que é possível aplicar padrões modernos de engenharia de software para modernizar ecossistemas maduros.
A combinação de Clean Architecture para organização, Prisma para segurança de dados e Docker/PM2 para eficiência operacional resultou em um backend sólido, fácil de manter e preparado para escala.
Gostou do artigo? Conecte-se comigo no LinkedIn ou confira meus projetos de código aberto no GitHub.

Top comments (0)