DEV Community

Leonardo Policarpo
Leonardo Policarpo

Posted on

Modernizing Mature Ecosystems: Clean Architecture & Performance in Read-Only Microservices

Engineering strategies to decouple data retrieval, reduce infra costs, and scale Node.js apps.


By Leonardo Policarpo
Software Engineer | Backend Architecture & IoT Enthusiast


🇧🇷 Leia a versão em Português aqui

Recently, I tackled a common challenge in large corporate scenarios: the need to create new, high-performance, scalable features that consume data from a consolidated database, without compromising the stability of the central system.

The goal was to architect a Read-Only Microservice to feed data visualization interfaces and export routines. The business requirement was speed of delivery; the technical requirement I imposed on myself was engineering excellence and long-term maintainability.

In this article, I share the architectural decisions taken to ensure a robust and decoupled solution using Node.js, Clean Architecture, Prisma, and Docker.

1. The Engineering Challenge

The scenario involved complexities typical of robust systems that have grown organically over the years:

  • A relational database (SQL) with massive data volume and complex structures.
  • The need for High Availability to serve multiple concurrent clients without blocking the Node.js Event Loop.
  • Strict security requirements and total isolation between Staging and Production environments.
  • AWS Infrastructure that needed optimization for cost efficiency (Compute/Storage).

2. Clean Architecture: The Decoupling

To ensure the project's longevity, I adopted Clean Architecture. The main goal was to isolate business rules from implementation details like web frameworks or database drivers.

The structure was organized into layers with defined responsibilities:

  • Domain: Interfaces and pure models of the application.
  • Data: Use cases and business rules.
  • Infra: External implementations (Repositories, Cryptography, Integrations).
  • Main: The composition layer that injects dependencies and initializes the service.

This separation allows, for instance, replacing the HTTP framework (e.g., Express for Fastify) or the ORM in the future with zero impact on the domain logic.

3. Integrating with Existing Bases (Prisma ORM)

One of the technical challenges was mapping the pre-existing data structure safely and with type safety. Manual SQL queries tend to be fragile for future maintenance.

The solution was to use Prisma ORM with its Introspection feature. Instead of managing migrations (which were out of scope for this satellite service), I configured Prisma to read the existing schema and automatically generate TypeScript typing.

To handle older relationship tables that use composite keys instead of unique identifiers, I utilized native schema mapping:

model ExampleRelation {
  userId  Int
  itemId  Int

  // Defines unique identity by combining columns
  @@id([userId, itemId])
}
Enter fullscreen mode Exit fullscreen mode

This brought Type Safety to data consumption, preventing runtime errors and accelerating development.

4. Performance & Efficiency: Docker and PM2

Infrastructure optimization focused on reducing the memory footprint and maximizing CPU usage.

Docker Multi-stage Build

To avoid heavy images containing unnecessary development files, I implemented a multi-stage build using Alpine Linux:

  • Builder Stage: Installs dependencies, compiles TypeScript, and generates artifacts.
  • Runner Stage: Copies only the transpiled code (dist) and production dependencies.

Result: Compact final images (around 150MB), resulting in faster deploys and reduced storage costs in the Container Registry (ECR).

PM2 in Cluster Mode

Being single-threaded, a standard Node.js application uses only one processor core, which can underutilize resources in multi-core cloud instances.

Implementing PM2 in Cluster Mode allowed the application to scale vertically within the container. The manager spins up multiple processes (workers) based on CPU availability, optimizing throughput. Beyond performance, this strategy is crucial for resilience: if a worker needs to restart, the internal load balancer maintains service availability by redirecting traffic.

Conceptual view of the proposed architecture:

5. Immutability and Security

To ensure consistency across environments, I implemented a CI/CD pipeline based on immutable artifacts:

  • A single Dockerfile is used for all stages (Dev, Staging, Prod).
  • Sensitive environment variables are injected only at container runtime.
  • Environment segregation via Tags in the Container Registry.

At the application layer, security was reinforced with strict CORS middlewares (allowing only authorized domains) and authentication validation on all routes.

Conclusion

This case study demonstrates that it is possible to apply modern software engineering patterns to modernize mature ecosystems.

The combination of Clean Architecture for organization, Prisma for data safety, and Docker/PM2 for operational efficiency resulted in a solid backend, easy to maintain, and ready to scale.

Did you like the article? Connect with me on LinkedIn or check out my projects on GitHub.

Top comments (0)