Cloud Architecture
Advanced 16 min read Cloud Architecture

Patterns: Monolith vs Microservices

Size the architecture to the team — not the trending post

Modular monolith vs microservices is a sizing decision, not a dogma war. The factors: team size/cadence, independence boundaries, ops maturity. Modern middle: modular monoliths with clean seams that can split later, strangler-fig migration, DDD bounded-context discipline.

monolithmodular monolithmicroservicesbounded contextstrangler figsizingteam topologies
Before we start

What you will learn

  • Compare monolith/modular/microservices with team-fit lens
  • Identify bounded contexts worth separating
  • Design for a split later (seams) or a safe strangler migration
In 60 seconds

Microservices buy you independent deploy + scale + team autonomy, at a tax: observability, networking, data consistency, and ops duplication. A modular monolith buys you target simplicity with DDD seams you can crack later. Choose by team size + cadence + domain boundaries — not by fashion.

Introduction

What is this?

Shapes: monolith (one deployable; simplest ops; scaling/failure blast = whole app), modular monolith (one deployable, but boundary-encoded modules/PAPI seams — the modern default), microservices (multiple deploys, per-service DB, team-per-context, independent scale). Drivers to micro: independent deploys (cadence spikes), team autonomy (2-pizza per context), independent scale (hot path vs bulk), ownership boundaries. Drivers to monolith: small team, stable domain, rapid iteration, low ops maturity. Hybrid: strangler fig (migrate outwards per capability — classic safe migration), CQRS/event hooks for seams. DDD tells you WHERE the seams are: bounded contexts with explicit service contracts; shared-nothing data. Anti-fail: microservices at 3-person team, or monolith with no boundaries (hard to split later). Observability + test + contracts tooling is the enabler for micro; without it you've split the worst part of your app.

Motivation

Why do you need it?

  • Right-sizing saves the tax: teams that needed micro and stayed mono pay deployment/s competition; teams that split too soon pay ops + observability blow-ups.
  • A modular monolith + seams is the 'have both later' structural bet — you keep simplicity today and a real split path.
  • Bounded contexts are the honest boundaries: splitting along them is safe; splitting around feelings isn't.
  • The strangler pattern makes migration incremental — the cloud way (brownfield) rather than big-bang.
Think about it

Mental model

Imagine

A workshop: one bench does everything vs. specialized benches per craft — how many benches fit your crew?

Think of

Monolith vs micro

A lone craftsman gains nothing from a dozen specialty benches (it's all transport cost); a big crew bottlenecks at one bench. The bench count (service count) is sized to the crew — and the seams between crafts stay clean either way.

Mechanics

How it works

  • Map bounded contexts (DDD): where do data/domain seams naturally live?
  • Decide the deployable shape by team size/cadence + context independence needs.
  • Keep context boundaries encoded even in a monolith (modules, contracts, no cross-table hacks).
  • If micro: per-service DB + contracts + observability from day one; team-per-context.
  • Migration: strangler fig — switch one capability at a time behind a facade.

Sizing decision.

Bounded contexts
Team + cadence
Shape
Seams + contracts
Migrate
Hands-on

Examples

Modular seam in code

Modular seam in code
# Order domain vs Billing domain as modules with contracts:
# module order { export interface OrderServiceFactory }
# module billing { export interface BillingGateway }
# Keep an anti-corruption layer: order never reads billing tables directly;
# it calls the BillingGateway (contract) with its own DTO.
# → Today: one deployable (mono), tomorrow: split each module into a service
# by moving the contract behind an HTTP call. Seams now, split later.

Boundaries are the real wisdom — enforced now, cheaply split later. No ceremony, no micro-tax today.

Real world

In production

  • Start modular monolith; split only the contexts the team/cadence truly forces (e.g., the nightly-etl hot path).
  • Strangler for the legacy migration: capability-by-capability under a facade.
  • If true multi-service: per-service DB + OpenAPI/AsyncAPI contracts + full trace/observability + independent deploys.
Guidelines

Best practices

  • Size to team + cadence: deploy rhythm and autonomy drive splitting, not the count of services someone admired.
  • Encode boundaries even in a monolith (contracts, no cross-context table writes).
  • Use a strangler facade for migrations; always incremental.
  • Keep data ownership per context — shared DB is how microservices become 'distributed monolith'.
  • If splitting: prove it on one context before the fleet-wide party.
Watch out

Common mistakes

  • Microservices because of a slide, at a small team → ops tax + observability debt.
  • Monolith with zero boundaries → every split later is a burn while three contexts share tables.
  • Splitting the write path across services sharing one DB → distributed transactional corruption.
  • Migrating big-bang (rewrite every context at once) instead of strangler.
When things break

Debugging tips

  • A 'monolith' that fails one sprint → the boundary/contract discipline slipped; search cross-context table access first.
  • Microservices await slow deploys yet → their contract/version skew between services; enforce code-gen/versioning.
  • Strangler stuck at one capability → the facade did the switch but the old path still serves reads; cutover the read path too.
  • Team autonomy promised but tweaks bounce → service ownership/contract-change process undefined; define who breaks what.
Interviews

Interview checkpoint

Test yourself

Quick quiz

0/2
1

The modern default when the team is small:

2

A sign you built a 'distributed monolith':

Go deeper

Official documentation