Skip to main content

Modular Monolith

A modular monolith keeps a single deployable app but enforces strong internal module boundaries, capturing much of the value of microservices without distributed-system complexity. It is often the right architecture and a deliberate stepping stone to services.

Organization
ThoughtWorks
Published
Apr 29, 2019

Best Practice: Modular Monolith

A modular monolith is a single deployable application that is internally divided into well-defined, loosely coupled modules with explicit boundaries. Each module owns its domain, exposes a clear interface, and hides its internals from the others. The application still builds and deploys as one unit, so you avoid the network calls, distributed transactions, and operational overhead of microservices, while still getting clean separation, independent reasoning, and the option to extract a module into a service later. This matters because many teams jump to microservices prematurely and inherit enormous distributed-system complexity. A well-structured modular monolith is often the right architecture, and a deliberate stepping stone if services are ever needed.

Step-by-Step Implementation Guidance

  1. Identify modules along business capabilities or bounded contexts, not technical layers.
  2. Give each module a public interface and keep its internals private.
  3. Forbid modules from reaching into each other's internal data or tables.
  4. Communicate between modules through interfaces or in-process events.
  5. Enforce boundaries with tooling such as build modules, packages, or architecture tests.
  6. Keep separate schemas or table ownership per module to ease later extraction.
  7. Extract a module into a service only when a real need, such as independent scaling, appears.

Common Mistakes Teams Make When Ignoring This Practice

  • Jumping straight to microservices and drowning in distributed complexity.
  • Building a big ball of mud monolith with no internal boundaries.
  • Letting modules share database tables, coupling them tightly.
  • Organizing by technical layer rather than business capability.
  • Relying on convention alone instead of enforcing boundaries with tooling.

Tools and Techniques That Support This Practice

  • ArchUnit and similar tools to test architectural boundaries.
  • Language module systems such as Java modules or .NET assemblies.
  • Build tools like Gradle or Maven multi-module projects.
  • In-process event buses for inter-module communication.

How This Practice Applies to Different Migration Types

  • Cloud Migration: Deploy one well-structured unit to the cloud, deferring service decomposition.
  • Database Migration: Per-module schema ownership makes splitting data later far easier.
  • SaaS Migration: Replace a single module's responsibility with a SaaS behind its interface.
  • Codebase Migration: Refactor a tangled monolith into modules as the first, lower-risk modernization step.

Checklist

  • Define modules by business capability.
  • Give each module a public interface and private internals.
  • Prevent cross-module access to internal data.
  • Communicate via interfaces or in-process events.
  • Enforce boundaries with architecture tests or tooling.
  • Keep schema or table ownership per module.
  • Extract services only when a concrete need arises.