A modular monolith keeps one application deployment while dividing the code into modules with clear responsibilities. Each module exposes a limited interface and keeps implementation details behind that interface. The structure makes dependencies visible without adding network calls or separate operational environments.
How module boundaries work
A module groups code and data rules for one coherent capability. Other modules use its published interface instead of importing internal classes or updating its records directly. The rules can be enforced through language visibility, build configuration, dependency tests, or repository structure.
| Boundary concern | Weak separation | Explicit module rule |
|---|---|---|
| Code access | Any package imports any class | Only public interfaces are callable |
| Data access | Modules update shared tables directly | One module owns each write path |
| Dependencies | Cycles form between features | Dependency direction is checked |
| Change ownership | Responsibility follows folders | A team or role owns the capability |
The application still starts, runs, and deploys as one unit. Modularity changes its internal design rather than its deployment topology.
Why it matters in a legacy codebase
A legacy monolith often contains useful boundaries that have become blurred over time. Refactoring can recover them incrementally. The team can move one capability behind an interface, redirect callers, and verify current behaviour before changing the next area.
An audit looks for evidence that suggests a viable module:
- Cohesive behaviour. Rules and workflows that change for the same business reason.
- Recognisable ownership. Data and operations that belong to one capability.
- Limited dependencies. A manageable set of calls into and out of the proposed boundary.
- Independent verification. Tests that can exercise the module through its interface.
These signals are more reliable than dividing code by technical layers alone. A user-interface folder, a service folder, and a data folder may all contain parts of the same capability.
Modular monolith versus separate services
A modular monolith uses in-process calls and one deployment. Separate services communicate across a network and can have independent releases. That adds operational and failure concerns, including timeouts, partial availability, and contract compatibility.
Service extraction may be justified when a capability needs independent scaling, deployment, technology, or ownership. Until then, an internal module can provide the same logical separation with fewer moving parts. A bounded context can guide either form because it defines a model boundary rather than a deployment choice.
Using it as a migration stage
The first stage is to map current dependencies and choose one boundary. The team then defines an interface, moves access behind it, and uses characterization tests to protect observable behaviour. Direct imports and data writes are removed after callers use the interface.
That sequence can leave the module inside the application or prepare it for later extraction. The target is not a particular number of services. It is a codebase where ownership, dependency direction, and change impact can be stated before the next refactoring step begins.