DEV Community

Leonardo Policarpo
Leonardo Policarpo

Posted on

Modernizando Ecossistemas Maduros: Clean Architecture e Performance em Microsserviços de Leitura

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])
}
Enter fullscreen mode Exit fullscreen mode

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)