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 (1)

Collapse
 
xaviermac profile image
Xavier Mac

Gostei bastante da forma como você conectou Clean Architecture com otimização de infraestrutura. A decisão de usar Prisma por introspecção e manter o serviço apenas de leitura ficou bem clara e pragmática. Também curti o enfoque em imagens leves com multi-stage build e uso de PM2 cluster — artigo bem direto e aplicável ao dia a dia.