A few years ago I sat in a planning meeting where a senior engineer proposed splitting our payments platform into fourteen services. We had eleven engineers. I did the arithmetic out loud: that's more services than people, and each one needs its own deployment pipeline, its own on-call story, its own database migration path. The room went quiet. We built a monolith instead, and it processed nine figures of transaction volume before we split off the first service three years later.
I am not against microservices. I have run them, and for some problems they are the only sane answer. But the default has quietly flipped. Somewhere along the way "just start with services" became received wisdom, and a lot of small teams are paying a distributed-systems tax they never needed to pay. This is my case for the monolith as a deliberate, defensible architecture choice, not a thing you tolerate until you can afford something better.
The tax nobody prices in
Every time you draw a network boundary between two pieces of code, you convert a function call into a distributed systems problem. That sounds dramatic until you have lived it. A method call that used to be a nanosecond and could never partially fail becomes an HTTP round trip that can time out, retry, arrive twice, or arrive out of order. You now need idempotency keys, correlation IDs, circuit breakers, and a tracing stack just to answer the question "why did this one request take four seconds." That is a lot of machinery to buy before you have a single paying customer.
In a monolith, a refund that touches the ledger, the payout scheduler, and the notification queue is one transaction. It commits or it doesn't. Split those into three services and you have inherited the two-generals problem, and now you are writing sagas and compensating transactions to un-refund money that already left. I have debugged that exact failure at two in the morning, and I promise you the elegance of the service diagram is cold comfort when the ledger and the payout service disagree about reality.
The first rule of distributed systems is don't distribute your system until you have a measured reason to. The second rule is that the reason is almost never "it feels cleaner."
One transaction, one truth
The single biggest reason I reach for a monolith in fintech is transactional integrity. When money moves, the invariants are strict and unforgiving: debits equal credits, balances never go negative without an explicit overdraft facility, and no state exists where a customer has been charged but the goods were never allocated. A relational database inside a single process gives you all of that for free with one keyword.
using var tx = connection.BeginTransaction(IsolationLevel.Serializable);
// Debit the payer, credit the merchant, record the fee.
// All three succeed together or the whole thing rolls back.
await ledger.Debit(payerAccountId, amount, tx);
await ledger.Credit(merchantAccountId, amount - fee, tx);
await ledger.Credit(platformAccountId, fee, tx);
if (await balances.WouldGoNegative(payerAccountId, tx))
throw new InsufficientFundsException(payerAccountId);
tx.Commit();
Try reproducing that guarantee across three services and you will end up building a distributed transaction coordinator, or more likely reinventing one badly. The eventual-consistency version is not wrong, exactly, but it is enormously more code, more failure modes, and more edge cases that auditors and regulators will ask you about. When a compliance reviewer asks "can a customer ever be debited without the corresponding credit landing," I would much rather answer "no, it is one serializable transaction" than walk them through a saga state machine and a dead-letter queue.
The team you actually have
Conway's Law is usually quoted as a warning, but it is also a sizing tool. Your architecture will mirror your communication structure whether you plan for it or not, so the honest question is: how many teams do you actually have that can each own a service end to end? Not people. Teams. A service without a clear owner is a liability that shows up as stale dependencies and a pager nobody answers.
With eleven engineers we had, generously, two teams. Fourteen services would have meant every engineer context-switching across a handful of repos, each with its own build, its own secrets, its own quirks. The monolith let a new hire clone one repository, run one command, and have the whole platform running locally in under two minutes. That onboarding speed is not a vanity metric. It compounds every single time someone joins, debugs, or reproduces a bug.
- One repository to clone, one build to understand, one test suite to trust.
- One deployment to reason about, which means one rollback when it goes wrong.
- Refactoring across module boundaries is a compiler-checked rename, not a coordinated multi-repo release with a deprecation window.
- A single place to add cross-cutting concerns like audit logging and PII redaction, instead of fourteen slightly different implementations.
Modular is not the same as distributed
The strongest objection to monoliths is that they turn into a mud ball where everything imports everything and no change is safe. That is real, and I have inherited a couple of those. But the failure there is a lack of internal boundaries, not the absence of network calls between them. You can have crisp module boundaries inside a single deployable, and you should.
We organised our monolith into modules with explicit public interfaces and enforced the boundaries in the build. The ledger module exposed a handful of methods; nothing outside it could touch its tables. If a developer tried to reach across a boundary, the build failed. In .NET you can do this with separate projects and internal visibility; in other stacks there are architecture-test libraries that fail the build when a forbidden dependency appears. The point is that a well-modularised monolith gives you most of the isolation benefits people credit to microservices, without the network in the middle.
When we eventually did extract a service, those clean module seams were exactly where we cut. The interface already existed; we just changed the transport behind it. That is the payoff of doing the boundaries properly up front, and it is why I think of a modular monolith as the option that keeps the most doors open, not the fewest.
What latency actually costs
People underestimate how much of a request's time budget gets eaten by hops. A single checkout might touch pricing, inventory, fraud scoring, the ledger, and notifications. In a monolith those are in-process calls measured in microseconds. Split them into services and each hop adds a network round trip, serialization, and deserialization. On a good day inside one datacenter that is maybe a millisecond or two per hop. Chain five of them with a couple of retries and you have quietly built a checkout that spends more time on the wire than doing work.
I watched a team add roughly 200 milliseconds of p99 latency to a login flow purely by decomposing what had been one service into four. Nothing about the business logic changed. The users just waited longer, and the on-call rotation got noisier because now there were four things that could be slow instead of one, and four sets of dashboards to check at 3am to find out which. Latency is a feature, and distribution spends it.
Operational blast radius
There is a comforting story that microservices contain failures, that if the recommendations service falls over your checkout keeps working. Sometimes true. But you have traded one kind of failure for a subtler one: partial failure, where half your system believes one thing and half believes another. Those are the incidents that take days to untangle, because there is no single log file that tells the whole story.
A monolith fails honestly. When it is down, it is down, and everyone knows. That sounds worse but it is often easier to operate, especially for a small team without a dedicated platform group. You have one thing to monitor, one thing to scale, one set of dashboards. I would rather run one well-understood process across three redundant instances than fourteen services where the failure combinations outnumber the engineers who understand them.
When I actually reach for services
None of this is dogma. There are concrete signals that tell me a piece genuinely wants to be its own service, and when they show up I move without hesitation. The trigger is a measured constraint, not an aesthetic preference or a conference talk.
I split when a component has a wildly different scaling profile from the rest, such as a fraud-scoring model that needs GPUs while everything else is CPU-bound and cheap. I split when a component has a different compliance boundary, like a module handling raw card data that I want inside a smaller PCI scope so the audit surface shrinks. I split when a team has genuinely grown big enough to own something end to end and the coordination cost inside the monolith starts to exceed the coordination cost of a clean API. And I split when one module's release cadence is fundamentally at odds with the rest, changing hourly while the core changes weekly.
Notice that none of these are "we read a blog post" or "the diagram looks tidier." They are load, risk, ownership, and cadence. When you can point at one of those with a number attached, extraction pays for itself. When you cannot, you are buying complexity on credit.
The cost of being wrong in each direction
Architecture decisions are bets, and the smart move is to compare the cost of being wrong. Start with a monolith and outgrow it, and the fix is real work but mechanical: extract along seams you already have, one service at a time, with the old code as a reference implementation. I have done that migration and it is a good problem to have because the business grew enough to need it.
Start with microservices and turn out not to need them, and the fix is brutal. You are collapsing a distributed system back into a process, unwinding network contracts, and explaining to everyone why you are deleting infrastructure they spent a year building. Nobody wants to be the person who proposes recombining services, so it rarely happens. The premature distribution just sits there, taxing every feature forever. Given that asymmetry, the monolith is the reversible bet, and I take the reversible bet almost every time.

Conclusion
Here is the thing I wish someone had told me earlier: the monolith versus microservices debate is mostly a debate about when, not whether. Almost every large system ends up distributed in some form. The question is whether you pay that cost on day one, when you have the least information and the smallest team, or whether you defer it until the system itself tells you where the seams are. Buy the complexity when you can afford it and the code has shown you where it belongs. Until then, keep it in one process, keep the boundaries clean inside, and spend your scarce engineering hours on the product instead of on the plumbing between fourteen services that could have been fourteen well-named folders. The best architecture is the one that lets a team of eleven ship like a team of eleven, not one that makes them feel like Google on a whiteboard.