An architectural deep dive into building modular monoliths, covering how to define module boundaries through explicit APIs, own separate database schemas per module, handle cross-module communication without tight coupling, enforce boundaries with architecture tests, and incrementally migrate modules out as standalone services when justified. It also outlines the concrete signals that indicate a team should actually reach for microservices rather than jumping there prematurely.
Questions this post answers
How do I know when to move from a modular monolith to microservices?
The decision should be driven by concrete signals rather than trends: distinct scaling needs between modules, separate deployment cadences required by different teams, organizational boundaries that map cleanly to module boundaries, or a module's database load conflicting with others. If a module has clean, enforced boundaries and its own schema in a modular monolith, extracting it into a service becomes a mechanical migration rather than a rewrite. Developers weighing microservices versus a modular monolith can track architecture patterns like these on daily.dev.
What makes a monolith modular instead of just a big ball of mud?
A modular monolith enforces real internal boundaries: each module exposes an explicit API instead of letting other modules reach into its internals, owns its own database schema rather than sharing tables freely, and communicates with other modules through defined contracts or events instead of direct calls into shared code. Architecture tests are used to catch violations of these boundaries automatically as the codebase grows. Teams designing internal module boundaries can follow architecture and backend design discussions on daily.dev.