Choosing: Monolith or Microservices?

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

Architectural Styles defined what a monolith, a modular monolith, and microservices actually are, and Benefits and Drawbacks of Microservices named what each style buys and what it costs. This page turns those trade-offs into a decision, using Bookshop — the same Spring Boot 4.1 monolith fronting Catalogue, Orders, Payments, Shipping, and Customers — to ground both sides of the call.

Choose a monolith when

A monolith — built as a modular monolith from day one, per Architectural Styles's enforced module boundaries, not as an undifferentiated ball of packages — is the right default in most of these situations:

  • The project is small. A handful of screens and a handful of business rules do not need five services and five deployment pipelines to be maintainable.

  • The team is small. If everyone building Bookshop still fits around one table, there is no team-autonomy problem for microservices to solve; the coordination cost microservices exist to remove is not there yet to remove.

  • The delivery is one-shot. A prototype, a proof of concept, or a fixed-scope project that will be handed off and not iterated on for years does not benefit from independent deployability it will never exercise.

  • The application will not need to evolve much. Stable requirements and a stable domain do not need the independent release cadence microservices are built around.

  • The business model itself is still being discovered. Richardson is explicit about this: "a startup should almost certainly begin with a monolithic application" (Microservices Patterns, section 1.5.2) — when the product itself might pivot next quarter, paying up front to decompose a domain model that might not survive contact with real customers is money spent on the wrong problem. Martin Fowler’s MonolithFirst makes the same argument: start monolithic, extract services once the boundaries have proven themselves against real usage, not before.

  • The domain is strictly transactional. If most of Bookshop’s core workflows genuinely need one ACID transaction across Order, Payment, and Shipment rows — rather than being able to tolerate the eventual consistency Sagas requires — a single shared database inside one monolith keeps that guarantee for free.

  • The operational prerequisites for microservices are missing. Fowler’s MicroservicePrerequisites names three: rapid provisioning of new infrastructure, basic monitoring, and a fully automated deployment pipeline. Running five services by hand, deployed by someone remoting into a server, is strictly worse than running one monolith the same way — microservices only pay off once those prerequisites already exist.

Choose microservices when

Microservices earn the cost Benefits and Drawbacks of Microservices lists when at least one of these is true:

  • The system is large, complex, and long-lived, built by several autonomous teams. Once Bookshop has grown past one team’s ability to hold the whole domain in their heads, splitting the codebase along the same lines the organization is already split along removes the coordination bottleneck a shared bookshop.jar and a shared release train impose.

  • Performance and scale reasons, when components have very different resource and scaling profiles. Bookshop’s Catalogue (memory-hungry full-text search, mostly read traffic) and Payments (CPU-light but latency-sensitive and must never be starved) want to scale on different axes of the scale cube, to different instance types, on different schedules — a single shared process cannot give either one what it needs without over-provisioning the other.

  • Fault isolation or independent release cadence is a business requirement, not just a nice-to-have — for example, Orders must keep taking new orders even if Shipping’s carrier-integration code is misbehaving, or Orders needs to deploy ten times a day while Shipping deploys weekly without either one blocking the other.

  • Parts of the system genuinely need a different technology stack. Bookshop’s Recommendations service benefits from a different language and a different data store than the relational, Java-based core; forcing it to live inside bookshop.jar would mean forcing the whole monolith onto whatever compromise stack serves both jobs adequately and neither one well.

  • The product is consumer-facing or SaaS at scale. High, spiky, unpredictable traffic against a customer-facing storefront is exactly the profile where independent, per-capability scalability stops being optional — see Migration Path: From Single-Tenant to Multi-Tenant SaaS for how this plays out once Bookshop itself becomes a SaaS product.

Is it really the architecture?

Before blaming the architecture, check whether the actual complaint is a process problem wearing an architecture costume. Richardson makes this point directly (Microservices Patterns, section 13.1.1): slow delivery and buggy releases are very often caused by manual testing, the absence of continuous integration, and no automated deployment pipeline — none of which microservices fix by themselves. Splitting a monolith with no CI/CD into five services with no CI/CD does not produce five services that deploy faster and more safely; it produces five services that are each slow and risky to deploy, with network calls between them added on top. Fix the pipeline first. If delivery is still slow and releases are still risky once tests run on every commit and deployment is one click (or fully automatic), then the architecture itself is a credible suspect.

Signals it is time to split — or to merge back

Signals it is time to split

If Bookshop’s monolith is showing the monolithic-hell symptoms Benefits and Drawbacks of Microservices describes, that is the signal to start extracting services rather than a theoretical concern:

  • The edit-build-run-test loop has grown from seconds to minutes, and developers have stopped running the full test suite locally before committing.

  • What used to be same-day deploys has become a monthly release train, because no one can be confident a change to one capability did not break another.

  • Catalogue and Payments (or any two capabilities with genuinely different resource profiles) cannot be scaled independently, so one is chronically over-provisioned to keep the other healthy.

  • A failure in one capability — a memory leak in Catalogue’s indexing, say — takes the whole process down, including capabilities that were never broken.

  • Upgrading a library, framework, or JVM version means testing and migrating the entire application at once, so upgrades keep getting deferred and the stack keeps falling further behind.

  • New engineers need weeks to become productive because there is no capability small enough to understand end to end in a day.

Signals it is time to merge back

Splitting is not a one-way ratchet. Amazon’s Prime Video team ran into the opposite problem with their audio/video quality-monitoring service: a microservices-and-serverless pipeline that passed video frames between several independently deployed components turned out to be dominated by the cost of calling between components — chatty inter-service calls, expensive serverless orchestration, and S3 round-trips for every frame — rather than by the monitoring logic itself. Merging the pipeline’s components back into a single monolithic process, and keeping data in memory instead of round-tripping it between services, cut their infrastructure cost by approximately 90% at the same scale (Prime Video Tech). The signal to watch for is the mirror image of the split checklist above: services that talk to each other constantly and synchronously, where most of the per-request cost and latency is IPC and per-call infrastructure overhead rather than actual work, and where the components were split along a line that does not match any real team or scaling boundary. When that is the profile, folding the chattiest services back into one deployable — not "microservices were a mistake" as a blanket rule, but "these particular components were split too fine for this workload" — is a legitimate, and sometimes the correct, architectural move (The New Stack — Return of the Monolith).

Which one fits Bookshop today?

flowchart TD A["New Bookshop project or major feature"] --> B{"Team fits around\none table?"} B -->|"Yes"| C{"Business model still\nbeing discovered?"} B -->|"No -- several\nautonomous teams"| D{"Components need very\ndifferent scaling, fault\nisolation, or stacks?"} C -->|"Yes"| M["Monolith\n(modular from day one)"] C -->|"No -- requirements\nare stable"| E{"Deployment pipeline,\nmonitoring, rapid\nprovisioning in place?"} E -->|"No"| M E -->|"Yes"| D D -->|"No"| M D -->|"Yes"| S["Microservices"]

Bookshop’s own path through this flowchart: a single small team, an unproven business model, and no automated pipeline yet all point to a modular monolith — exactly the starting point Architectural Styles and Migration Path: From a Monolith to a Modular Monolith describe, and exactly what later lets Migration Path: From a Monolith to Microservices extract Orders, Customers, Catalogue, and Payments once the team, traffic, and operational maturity justify it.

The table below is a seed, not a final answer — it names the same profiles the flowchart above walks through, one row each, as a quick-reference summary. It is deliberately compact: Starting a New Project: Which Path and Which Technologies later expands each of these rows into a full scenario card with a worked recommendation, a suggested stack, and the reasoning behind it.

Project profile Recommended style Why

Prototype / one-shot delivery

Monolith

No independent deployability to exercise; speed of build matters more than scale

Small team, small product, stable domain

Monolith (modular from day one)

No team-autonomy problem to solve yet; module boundaries keep the door open

Startup validating its business model

Monolith (modular from day one)

The domain boundaries are not proven yet; MonolithFirst

Strictly transactional core domain

Monolith

Keeps single-database ACID transactions instead of sagas

No automated deployment pipeline / monitoring yet

Monolith

MicroservicePrerequisites are missing; fix that before splitting

Large, complex, long-lived, several autonomous teams

Microservices

Removes the shared-codebase coordination bottleneck

Components with very different scaling/resource profiles

Microservices

Independent scaling per the scale cube’s Y and Z axes

Fault isolation or independent release cadence required

Microservices

A failing/slow-moving capability must not block the rest

Parts of the system need a different tech stack

Microservices

Each service can pick its own language, runtime, and datastore

Consumer-facing / SaaS product at scale

Microservices

Independent, per-capability scalability under spiky traffic