Back to technical writing

Technical writing

Modular Monolith or Microservices? A Decision Guide

Choose a deployment architecture from domain boundaries, consistency, team ownership, operational maturity, and measured constraints.

Monoliths and microservices are deployment choices with different costs. A useful decision starts with the system's domain boundaries, consistency needs, delivery bottlenecks, team ownership, and operational capability—not with the popularity of either label.

Start with the forces, not the architecture name

A modular monolith packages the application as one deployable unit while keeping business capabilities separated inside the codebase. Microservices move selected boundaries into independently deployable processes that communicate over a network. Both approaches can be well structured or tightly coupled. Distribution does not repair unclear ownership or weak module boundaries; it makes those problems more expensive to change.

For a new product, a modular monolith is often the lower-risk starting point because a team can learn the domain and move boundaries without coordinating network contracts or data migrations. Starting with services can be justified when the boundaries are already stable and independent deployment, isolation, or scaling solves a measured constraint.

Questions that shape the decision

Are the domain boundaries stable?

Service boundaries should follow cohesive business capabilities or bounded contexts. If the team still changes those boundaries frequently, keeping them as modules in one process makes refactoring cheaper. A service split becomes safer when responsibilities, language, data ownership, and change patterns are understood.

What consistency does the workflow require?

A single database can support local transactions across modules. Services usually own their data, so a workflow that crosses service boundaries needs explicit coordination, idempotency, retries, and reconciliation. Eventual consistency can be appropriate, but it changes product states and failure handling. The team should make that trade-off visible rather than reproducing a shared database behind service APIs.

What must change, scale, or fail independently?

Independent deployment is valuable when one capability changes on a different cadence, needs a distinct runtime, or has a scaling profile that dominates the rest of the system. Fault isolation also requires timeouts, bounded retries, circuit breaking, capacity controls, and degraded behavior. A process boundary alone does not guarantee resilience.

Can the team operate a distributed system?

Microservices add contracts, deployment pipelines, service identity, secrets, observability, incident response, versioning, and network failure modes. Those costs recur for every service. The architecture should match the number and shape of teams that can own services through development and production, not an expected future headcount.

A practical comparison

A modular monolith tends to fit when

  • One team owns most of the system and coordinated releases are not a measured bottleneck.
  • The domain is still evolving and moving functionality between modules is common.
  • Workflows rely on strong transactional consistency across several capabilities.
  • Operational simplicity and fast feedback matter more than independent scaling.
  • Clear module APIs and dependency rules can be enforced inside the codebase.

Microservices tend to fit when

  • Business boundaries and data ownership are stable enough to become service contracts.
  • Multiple teams need to deploy capabilities independently without release coordination.
  • A measured scaling, availability, security, or regulatory boundary benefits from isolation.
  • The product can represent intermediate states and recover from partial failure.
  • The organization already operates reliable delivery, observability, and incident-response platforms.

Apply the questions to an ecommerce system

Consider users, catalog, inventory, orders, payments, and notifications. A small team can begin with modules and one deployment while giving each capability a clear interface and restricting direct cross-module data access. An order can still be created in one local transaction, while notification delivery runs after commit.

If payment processing later needs stricter isolation, a separate compliance boundary, or an independent release cadence, it may be a strong extraction candidate. That extraction requires more than moving code: the team must define ownership of payment state, authenticate calls and callbacks, handle duplicate events, reconcile uncertain outcomes, and observe the full order-to-payment workflow.

Evolve from evidence

Measure where coupling causes harm: lead time, deployment contention, failure blast radius, resource saturation, or ownership confusion. Strengthen the module boundary first. Then extract one capability whose boundary and benefit are clear, preserve compatibility during the transition, and compare the result with the original constraint. A strangler or branch-by-abstraction approach can reduce migration risk when old and new paths must coexist.

Keep an architecture decision record with the context, alternatives, expected benefit, new failure modes, migration plan, and reversal conditions. Revisit it when team topology, workload, or business constraints change.

Decision checklist

  • Name the current delivery or runtime constraint and show how it is measured.
  • Define capability boundaries, owners, contracts, and data ownership.
  • Describe consistency requirements and behavior during partial failure.
  • Estimate platform, observability, security, testing, and on-call costs.
  • Choose a migration slice and a safe rollback or coexistence strategy.
  • Set a review date and success signals before implementation.

References

Microsoft Azure Architecture Center: Microservices architecture style

AWS Prescriptive Guidance: Decomposing monoliths into microservices

Martin Fowler: Monolith First