Blog
Clean Architecture + Modular Monolith: Build systems that stay clear as they grow
A visual guide to protecting business rules with Clean Architecture — and scaling teams with a Modular Monolith before you jump to microservices.

Most products don’t fail because the first framework was the wrong choice. They fail because structure dissolves as the system grows: modules blur, business rules leak into controllers, and every change feels risky.
Clean Architecture and the Modular Monolith aren’t opposing camps. Used together they give a practical default: protect the business core, split the system into clear business areas, and defer distributed-system complexity until you truly need it.
What Clean Architecture protects
Clean Architecture is less about folders and more about a dependency rule: source-code dependencies always point inward. Outer rings may know about inner rings. Inner rings must not know about frameworks, databases, or how things communicate.
Domain
Business truth lives here
Frameworks & Drivers
UI · DB · APIs · Messaging
Interface Adapters
Controllers · Presenters · Gateways
Application
Use cases · Ports · Orchestration
Domain
Entities · Rules · Policies
At the center are the entities and policies that create product value. Around them are use cases, then adapters that connect to the outside world, and on the outside are frameworks, UI, and databases. If you swap Postgres for another store tomorrow, or REST for gRPC, the business logic barely needs to know.
Three quick tests:
- Can you explain a business rule without mentioning NestJS, Laravel, Spring, or React?
- Can you unit-test a use case with fakes and no real database?
- If a table is renamed, does the domain language break, or only an adapter?
If the answer is no, the architecture is saying too much about infrastructure.
What a Modular Monolith looks like
A modular monolith is still one deployable application, but inside it is organized by business capability, not only by technical layer. Each module owns its API, application flow, domain model, and persistence adapter. Modules talk through explicit contracts and never reach into each other’s tables.
Modular Monolith
Single process · shared infrastructure · modular boundaries
Identity
- API / Controllers
- Application services
- Domain model
- Persistence adapter
Catalog
- API / Controllers
- Application services
- Domain model
- Persistence adapter
Ordering
- API / Controllers
- Application services
- Domain model
- Persistence adapter
Billing
- API / Controllers
- Application services
- Domain model
- Persistence adapter
Fulfillment
- API / Controllers
- Application services
- Domain model
- Persistence adapter
Reporting
- API / Controllers
- Application services
- Domain model
- Persistence adapter
Shared platform
Auth middleware · observability · messaging bus · database access helpers, never shared domain logic across modules.
For many products this is the sweet spot: enough structure for several people to work together, without paying the cost of dozens of services, network hops, and deployment pipelines from day one. Each module can be organized as a small Clean system of its own:
ordering/
domain/ entities, value objects, domain services
application/ use cases / ports
adapters/ http, persistence, messaging
ordering.module.ts
Calls across modules go through the application API or in-process messages, not one shared database model.
Keep the monolith or split into services
You don’t need microservices to look “modern”. You need boundaries you can trust.
Keep the modular monolith when:
- It is one product, a few teams, and releases cut across many areas.
- You need strong consistency between flows (checkout, billing, inventory).
- You want fast local development and simple operations.
Consider splitting a service when:
- One module needs to scale or run in a very different environment.
- Its release cadence has to be fully independent of the rest.
- Fault isolation is worth more than the network and observability cost.
A checklist for your next design review
- Name modules after the business, not the framework (say
billing, notcontrollers-v2). - Keep domain language pure: no ORM annotations, HTTP DTOs, or UI state in the core.
- Define each module’s public API and treat internal packages as private.
- Forbid cross-module database joins unless you accept the coupling knowingly.
- Add observability at module boundaries so you can see which module owns what in production.
In short, Clean Architecture keeps the rules honest and the Modular Monolith keeps the organization honest. Together they give you a system that is still readable in month three and still evolving in year three.
Designing a new platform, or untangling a codebase that keeps growing? MinTech can help choose boundaries that fit your product. Talk to MinTech.