Modular monolith over microservices
Context
The most-shipped pattern for serious B2B SaaS in the 5-50 engineer range is a modular monolith — one deployable, well-bounded internal modules, an event log for the patterns that benefit from async. Microservices solve organizational scaling problems most CleenUI customers don't have; they pay the latency, ops, and debugging tax for capabilities they don't need.
Decision
CleenUI ships as 14 vertical-slice modules (M01-M14) inside a single ASP.NET Core 8 deployable. Cross-module communication is in-process. Background work runs as Azure WebJobs + Azure Functions, organized as a 12-project Visual Studio solution.
Consequences
Positive
- Sub-millisecond cross-module calls. No service-mesh overhead.
- One database, one schema, ACID across the application.
- Single deployment pipeline. One staging environment, one production environment, no inter-version compatibility matrix.
- Easy to migrate to microservices later (per-module boundaries are real) — but you don't pay the cost until you need to.
Trade-offs
- Vertical scaling is the primary scale-out story. Horizontal scaling works (the API is stateless) but the database is the throughput ceiling.
- A bad release can take everything down. The mitigation: rigorous CI + canary deploys, not microservices.
Alternatives considered
- Microservices (one service per module). Rejected: operational complexity not justified by team size.
- Single 'big-ball-of-mud' monolith (no module boundaries). Rejected: that's exactly what the codebase is trying to NOT be.