People often focus only on style, asking which architecture is better, which one can scale, or which one serious teams choose. I think this way of looking at things leads to a lot of bad decisions.
You are not choosing between two styles. You are deciding where to put boundaries, and the only ones that work are the ones your team can operate. That means your team should be able to build, test, deploy, and fix things on their own, without needing help from other teams. When you look at it this way, team size, ownership, and how mature your operations are matter much more than whatever label you put on a diagram.
What actually differs
A monolith is a single deployable unit. It usually has one pipeline and one database, with calls that happen in-process, and a transaction scope that covers the whole request. Microservices, on the other hand, are separate services that can be deployed on their own. Each has its own data and they communicate over the network.
flowchart TB
subgraph M["MONOLITH — 1 deployable unit, 1 pipeline, 1 transaction scope"]
direction LR
MA[Orders] -->|in-process call| MB[Payments]
MB -->|in-process call| MC[Notifications]
MA --> MDB[(Shared database)]
MB --> MDB
MC --> MDB
end
subgraph S["MICROSERVICES — 3 deployable units, 3 pipelines, no shared transaction"]
direction LR
SA[Orders] -->|network call| SB[Payments]
SB -->|network call| SC[Notifications]
SA --> SDA[(Own DB)]
SB --> SDB[(Own DB)]
SC --> SDC[(Own DB)]
end
M ~~~ S
style MDB fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
style SDA fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
style SDB fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
style SDC fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
Three things change: where the network boundary sits, who owns the data, and how many pipelines you run.
The real differences are smaller than most people think. Where is the network boundary? Who owns the data? What does “deployment” mean in your company? These are the main questions. Both styles can be organized or messy. A monolith without clear boundaries is a ball of mud, and so is a group of eighty services all reading the same tables, only now the mess includes network hops.
The evidence that changes the question
DORA’s research changed how I think about architecture styles. Over eight years and more than 33,000 responses, the key factor was loosely coupled architecture and teams. This means teams can make big changes without asking others, can test on demand even without a shared test environment, and can deploy during business hours without causing real downtime. DORA is blunt that this is not a technology property: it’s achievable “with mainframe technologies,” and it’s possible to fail “even when using the latest, most trendy technologies.” The 2022 report still recommends that teams focus on loosely-coupled architectures, better CI/CD, and good version control.
O’Reilly’s 2020 survey of 1,502 people found something similar. 74% said their teams handled the whole build, test, deploy, and maintain cycle, and those teams reported success at a rate 18% higher than everyone else. The two main selling points for microservices were actually near the bottom: only 20% saw better availability, and lower development costs was the least-cited benefit of all at 15%. The audience is technical and self-selected, so treat it as directional. Still, the pattern is clear: ownership led to success, and the big promises rarely showed up.

Two roads to the same mess
Simon Brown made the point in 2014, before most of this became fashionable: “if teams find it hard to create a well structured monolith, I don’t rate their chances of creating a well structured microservices architecture.” His worry was a world of distributed big balls of mud.
Kelsey Hightower put it even more simply: “Most people say: ‘Look, we lost all of our discipline in the monolith… now the codebase is so bad. You know what we should do? We’re gonna break it up and somehow find the engineering discipline we never had in the first place.’” Splitting up a system does not give you the discipline you did not have before. It just turns that missing discipline into more operational work.
What the premium buys, and who paid it
Martin Fowler calls the extra cost the microservice premium: things like automated deployment, monitoring, handling partial failures, and eventual consistency. He also lists the prerequisites: rapid provisioning, basic monitoring, rapid deployment, and a good relationship between development and operations. He says clearly that if you do not have these basics, you should not try this approach.
Four teams made four different choices, and none of them were simply personal preference.

Netflix paid it for a risk a monolith couldn’t retire. In August 2008 a database corruption meant they could not ship DVDs for three days. They concluded the problem was vertically scaled single points of failure, and spent seven years moving to hundreds of microservices with a denormalized NoSQL data model. The part people skip: they changed the operating model at the same time. Budget approvals, centralized release coordination, and multi-week hardware provisioning “made way to continuous delivery, engineering teams making independent decisions using self service tools.” You can’t buy the second thing with the first.
Segment paid it without the prerequisites and walked it back. Their destinations pipeline grew past 140 services, each having its own repo and queue, running shared libraries at drifting versions. Three full-time engineers spent most of their time keeping it alive. After consolidating back to one service, shared-library improvements went from 32 in the microservices year to 46 the year after, and the test suite went from up to an hour to milliseconds. They were honest about what they gave up: fault isolation, cache effectiveness, and a wider blast radius on dependency updates.

Stack Overflow never paid it. In 2016 they were serving over 209 million HTTP requests a day and could still run the entire Q&A network off a single application pool on a single server. Scale, by itself, doesn’t force distribution.
Shopify hit the team-scale wall and refused the trade. In 2016 an innocuous change could set off a cascade of unrelated test failures, and CI was slow. They chose modularity instead of distribution by adding clear internal boundaries but not creating new deployment units, on the reasoning that it got them the advantages of both without so many of the downsides.
The counterargument I take seriously
Stefan Tilkov thinks monolith-first is usually exactly the wrong thing to do, and his reasoning is hard to wave off. If you already know your domain and you know you want services, the monolith phase quietly builds in coupling using shared libraries, shared tables, and shared transaction assumptions — and afterwards it’s “extremely hard” to split. Separate services make the wrong thing physically harder, which is a more reliable enforcement mechanism than a code review convention.
Fowler admits the same risk exists from the other side too. His advice to start with a monolith comes with the observation that most systems end up with too many dependencies between modules to split them up later. The monolith-first approach only works if you keep real modules in it. This is the precaution teams often miss.
The audit I’d actually run
Take one candidate boundary — a payments module, a notifications pipeline, whatever you’re arguing about — and ask four questions in order.
Can one team own it end to end, from build through test and deploy to the 3 a.m. page, without chronic coordination? Is the boundary actually clear in the business domain, or is it just an assumption you might regret in six months? Do you have the operational floor — automated deploy, observability, contract tests, someone who answers the page? And if you make a mistake, how do you fix it?
flowchart TB
S([Pick ONE candidate boundary<br/>payments module, notifications pipeline]) --> Q1
Q1{"1 — Can one team own it end to end?<br/>build, test, deploy, the 3 a.m. page,<br/>without chronic coordination"}
Q2{"2 — Is the boundary real<br/>in the business domain?"}
Q3{"3 — Is the operational floor there?<br/>automated deploy, observability,<br/>contract tests, someone who answers the page"}
Q4{"4 — If you get it wrong,<br/>how do you get out?"}
Q1 -->|No| K
Q1 -->|Yes| Q2
Q2 -->|No| K
Q2 -->|Yes| Q3
Q3 -->|No| K
Q3 -->|Yes| Q4
Q4 -->|No| K
Q4 -->|Yes| SP[Split it]
K[Keep it in-process<br/>behind an ENFORCED internal module.<br/>Buy time to learn where the seam really is]
SP --> N([Stop. Run the same four questions<br/>on the next boundary])
K --> N
N -.-> S
style K fill:#fef3c7,stroke:#d97706,color:#78350f
style SP fill:#dcfce7,stroke:#16a34a,color:#14532d
Any no and it stays in-process. Four yeses and you split one boundary, then start over.
If you answer no to any question, keep the boundary inside the process. Put it behind an enforced internal module and buy yourself time to learn where the seam really is. If you answer yes to all four, then go ahead and split it. After that, pause and ask the same four questions about the next boundary. You do not have to decide the whole architecture in one meeting.
The real question was never “which architecture is better.” It’s “which boundary can my team operate tomorrow morning.” If anyone knows of a team that got real value from splitting up components before they could answer those four questions, I would genuinely like to hear about it.


