ModularMS demonstrates an architectural approach where modules are the primary design unit, and deployment topology is a runtime decision controlled via profiles.
The same codebase can be deployed as:
- A modular monolith
- One microservice per module
- Grouped microservices, where multiple modules are deployed together
This allows teams to adapt architecture to team size, domain maturity, and operational capacity without rewriting business logic.
The goal is not to avoid microservices — it is to delay irreversible architectural decisions while preserving clean boundaries.
- Modules first, services second
- Deployment is configuration, not refactoring
- Boundaries are enforced in code, not infrastructure
- DX and productivity are first‑class concerns
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Module A │ │ Module B │ │ Module C │
│ (Domain) │ │ (Domain) │ │ (Domain) │
└─────┬───────┘ └─────┬───────┘ └─────┬───────┘
│ │ │
└────── Module APIs / Interfaces ───┘
Modules:
- Own their domain logic
- Expose explicit APIs
- Do not depend on deployment topology
Description All modules enabled in a single application.
┌──────────────────────────────────────┐
│ Application │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │Module A│ │Module B│ │Module C│ │
│ └────────┘ └────────┘ └────────┘ │
└──────────────────────────────────────┘
When to use
- Small teams (1–3 devs)
- Early product stage
- High iteration speed required
Benefits
- Fastest local development
- Simplest debugging
- Zero network overhead
- Minimal DevOps
Description Each module deployed independently using profiles.
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Module A │ │ Module B │ │ Module C │
│ Service │ │ Service │ │ Service │
└────────────┘ └────────────┘ └────────────┘
When to use
- Growing teams
- Independent scaling needed
- Clear service ownership
Benefits
- Fault isolation
- Independent deployment
- Module‑level scaling
Description Multiple related modules deployed together as a single service.
┌──────────────────┐ ┌──────────────────┐
│ Service X │ │ Service Y │
│ ┌────────────┐ │ │ ┌────────────┐ │
│ │ Module A │ │ │ │ Module C │ │
│ │ Module B │ │ │ │ Module D │ │
│ └────────────┘ │ │ └────────────┘ │
└──────────────────┘ └──────────────────┘
When to use
- Medium teams
- Closely related domains
- Microservices feel too granular
Benefits
- Fewer services to operate
- Reduced network chatter
- Better performance
- Still maintains modularity
- One mental model across all environments
- No architectural rewrite when scaling
- Fast onboarding
- Easier debugging
- Fewer repositories and pipelines
Developers always work with the same modules, regardless of deployment mode.
- Architecture scales with team size
- Cross‑module changes remain simple
- Operational complexity is incremental
- Teams avoid premature DevOps burden
To keep this architecture healthy:
- Each module has a single responsibility
- No shared domain models across modules
- All cross‑module access goes through interfaces
- No direct access to internals
- Profiles decide what starts, not how logic behaves
- HTTP, messaging, persistence adapters live at module boundaries
- No cyclic dependencies
- Shared utilities must be minimal and stable
Profiles control:
- Which modules start
- Which adapters are active (local vs remote)
Example strategies:
monolithmodule-amodule-a,module-b
Profiles must be:
- Explicit
- Documented
- Validated at startup
- Module tests: always run
- Monolith mode tests: fast feedback
- Service mode tests: critical paths only
Avoid testing all permutations blindly — focus on real deployment modes.
Use these signals:
✔ Independent scaling pressure ✔ Different uptime requirements ✔ Team ownership boundaries ✔ Heavy write or compute load ✔ External integration complexity
Avoid splitting just because "microservices are better".
- Profile complexity
- Risk of hidden coupling
- Larger testing matrix
- Requires architectural discipline
This architecture optimizes for small and growing teams, not hyperscale organizations.
ModularMS shows how teams can:
- Start simple
- Scale responsibly
- Preserve DX
- Avoid premature distributed complexity
Architecture should grow with the team — not ahead of it.