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.jarand 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.jarwould 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?
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 |