The pitch for microservices at a small company almost never starts from a measured problem. It starts from a conference talk, or a job posting that lists Kubernetes and gRPC as requirements, or a vague sense that a monolith is something you're supposed to have already outgrown. What it doesn't start from, usually, is a specific coordination cost that's actually hurting the team today. That's worth noticing, because the argument for microservices is a real one — it just isn't an argument about code quality or scalability in the abstract. It's an argument about organizations, and most small engineering teams aren't the organization it's an argument for yet.
What does splitting a monolith actually replace, mechanically?
A function call. That's the unglamorous truth underneath every microservices split: somewhere in your monolith, code that used to call another function in the same process now has to call that logic over the network instead, because it moved to a different service. And a function call and a network call are not the same operation wearing different clothes — they fail in entirely different categories of way. A function call either returns or the whole process crashes; there's no in-between state to reason about. A network call can time out after the remote side already committed its work. It can succeed on retry and duplicate a side effect that already happened once. It can return an error that means "definitely didn't happen" or an error that means "no idea if it happened," and your code has to know which, because retrying the wrong one is how a customer gets charged twice. There is a whole operational layer above monolith vs microservices for a small team that XenGrowth's marketing operations practice documents.
Operation | As a function call (monolith) | As a network call (services) |
|---|---|---|
Failure mode | Exception, caught synchronously, process state is known | Timeout, partial failure, or ambiguous success/failure — state on both sides may disagree |
Retry safety | Not usually a question — it either ran or it didn't, once | Requires idempotency keys or the retry can duplicate the effect |
Latency | Nanoseconds to microseconds, effectively free | Milliseconds at best, and now on the critical path of every caller |
Debugging a failure | A stack trace, in one process, in one log | Correlating logs across two or more services, often with clocks slightly out of sync |
None of this means the network call is wrong to introduce — sometimes it's exactly the right boundary. It means every service split is quietly asking the team to take on a harder category of failure in exchange for something, and the something has to be worth more than what got harder.
What happens to a transaction that used to be one database commit?
This is where the mechanism argument gets sharpest. Inside a single Postgres database, an operation that touches three tables — debit an account, create an order, decrement inventory — wraps in one transaction and either all of it commits or none of it does. That's ACID, and it's free: the database gives you that guarantee whether or not you ever think about it. Split those three concerns into three services with three databases, and that guarantee is gone. There's no free atomicity across a network boundary. You're left with two real options, and both cost something a single transaction didn't: eventual consistency, where you accept that for some window the order exists but inventory hasn't decremented yet, and you build compensating logic (a saga) to undo the order if the inventory step later fails; or a distributed transaction protocol, which is real, well-studied, and genuinely not something most small teams should be maintaining themselves, because getting it wrong is worse than not having it. For the the operations side of this angle, see The XenGrowth resource library.
A small team rarely has the headcount to build sagas correctly for every cross-service operation, which means in practice a lot of split-too-early architectures end up with inconsistency bugs nobody designed on purpose — an order that exists with no matching inventory decrement, discovered three weeks later by a confused support ticket instead of a failed test.
Why does local development get so much harder?
In a monolith, "run the app" means one command starting one process against one database. Every engineer's laptop can do that trivially, and a new hire is productive on day one. Split that same app into five services and "run the app" means running five processes, their five databases (or a shared one, which reintroduces its own coupling), and the network between them — usually via a Compose file that grows a service at a time until nobody fully remembers why one of them exists or what happens if you start them in the wrong order. Debugging a change that crosses two services means running both, often against data that has to be seeded consistently across two databases, which is its own maintenance burden nobody budgeted time for.
A monolith's local setup is bounded by the app itself — one process, one database, one command
A split system's local setup is bounded by however many services a given change touches, which grows over time as the service count grows
Debugging a cross-service bug requires reproducing the interaction between services locally, not just the one service where the symptom shows up
New engineer onboarding time scales with this same complexity — a monolith's onboarding doc fits on one page for a reason
What happens to testing when the logic you're testing lives in three places?
A monolith's integration test calls a function and asserts on the result — the whole call graph runs in-process, so the test is fast and deterministic by default. Split the same logic across services and an equivalent test either has to stand up multiple real services (slow, flaky, and expensive to run on every commit) or mock the service boundary (fast, but now testing a contract you're maintaining by hand instead of the real interaction). Neither is wrong, but both are new categories of test infrastructure a monolith never needed: contract tests to catch a producer and consumer silently drifting apart, and either a docker-compose-based integration environment or an investment in consumer-driven contract testing tooling most small teams have never had to build before. The security surface grows the same way, quietly: every call that used to be an in-process function invocation is now a network request that has to authenticate itself, which means service-to-service auth, and often mutual TLS, becomes a real system to design and operate rather than something the process boundary handled implicitly for free. If AI agents and marketing automation is the part you are stuck on, XenGrowth on governed AI marketing workflows is the better reference.
Dimension | Monolith | Split into services |
|---|---|---|
Integration testing | In-process function calls; fast, deterministic, no test infrastructure beyond the app itself | Requires either running real dependent services or maintaining mocked contracts that can drift from reality |
Deployment unit | One artifact, one deploy, one thing to roll back | N artifacts, N deploy pipelines, and a rollout order to reason about when they depend on each other |
Security surface | One process boundary; internal calls aren't a network-exposed attack surface | Every inter-service call is a network call, meaning auth, TLS, and network policy between services you didn't need before |
On-call cognitive load | One system to hold a mental model of | A mental model of N systems plus the contracts and failure modes between them |
Where a bug can hide | Anywhere in one codebase, but one debugger sees all of it | Anywhere in N codebases, and some bugs only exist in the interaction between two of them |
Why does observability go from optional to mandatory?
In a monolith, a stack trace already is a trace. One request, one process, one call stack — when something breaks, the log line and the exception traceback tell you exactly what called what, in order, for free. You can genuinely run a small monolith's early life without much observability tooling at all, because the debugger and a decent logger cover most of what you need. Split the same request across three services and that stack trace stops existing as a single artifact. You now need a correlation ID threaded through every service so log lines from different processes can be stitched back into one story, distributed tracing to see where time actually went across the hop, and log aggregation so "what happened to this request" is answerable at all instead of requiring someone to manually cross-reference three services' logs by timestamp. None of that is exotic tooling anymore — but it's tooling a monolith simply didn't need to answer the same question, and setting it up and keeping it correct is real, ongoing work for a team that could have spent that time on the product.
Microservices don't remove complexity from a system. They move it out of the process, where a debugger could see it, and into the network, where you now need a whole observability stack just to see the same thing.
So when do service boundaries actually start helping instead of hurting?
This is where Conway's law stops being a slogan and becomes the actual mechanism worth tracking. Conway's law says a system's architecture ends up mirroring the communication structure of the organization that built it — and the useful reading of that isn't "it's inevitable," it's "so pick the org structure and let the architecture follow it on purpose." A single team of six or eight engineers is one communication structure: everyone can plausibly know the whole codebase, and a Slack message resolves most coordination questions. Split that same six or eight people into three teams each owning a service, and you've created communication overhead — API contracts between teams, versioning, release coordination — that didn't exist when it was one team talking in one channel about one codebase. XenGrowth on building one SEO and GEO content system goes further into AI search, GEO and discovery.
Service boundaries start paying for themselves once there are actually separate teams whose independent shipping speed is worth more than the coordination cost of maintaining contracts between their services — which means enough people, doing different enough things, that forcing them into one shared codebase and one shared deploy is itself the bottleneck. That's a headcount and org-structure threshold as much as a technical one, and it arrives later, for most companies, than the architecture diagrams from a much larger company's engineering blog make it look.
You have multiple teams, not multiple engineers on one team, and they're routinely blocked on each other inside a shared codebase
A specific part of the system has scaling or reliability requirements different enough from the rest that coupling its deploys to everything else is the actual problem
You can name the API contract between the proposed services concretely, not just the org chart line you're drawing on a whiteboard
You're prepared to build and maintain the operational cost this post walks through — network failure handling, consistency strategy, local dev tooling, observability — because it's cheaper than the coordination cost you're solving
If you can't check all four, the monolith isn't a compromise. It's the correct architecture for where the team actually is
None of this is an argument that microservices are wrong in general — it's an argument that the trade only makes sense once there's an organizational reason to actually pay for it, and most small teams asking this question haven't reached that reason yet. If you want the practical pattern for structuring a monolith so it doesn't calcify into the thing microservices were meant to fix, microservices vs. modular monolith for a 10-person team covers that directly. And if the orchestration question is what's actually driving this conversation rather than the application architecture itself, that's a separate question worth answering on its own terms.
Further reading from XenGrowth
The XenGrowth resource library — what you'll learn: how the commercial side of this work is run, across search, automation and revenue operations.
XenGrowth on governed AI marketing workflows — what you'll learn: how the teams running AI marketing agents keep them governed and measurable.
XenGrowth on building one SEO and GEO content system — what you'll learn: how search and AI-answer visibility get run as a single content system.
Where this work meets go-to-market
The operational playbooks that sit alongside monolith vs microservices for a small team live with XenGrowth's marketing operations practice.
Four questions on what changes the moment a function call becomes a network call. The post's argument is that the costs are real and mostly invisible until you've paid them.













