Microservices help only when team and system boundaries are already real; otherwise you buy distributed complexity first.
01 THE PROBLEM
Microservices are the failure mode where a company decomposes code before it decomposes ownership, operational discipline, and domain boundaries.
The visible symptom is not “too many services.” It is a distributed monolith: separate deployables that still require synchronized changes, shared incident response, and constant cross-team coordination. You split the repository, but not the coupling.
The consequence shows up fast. Within 6 to 18 months, engineering throughput drops, incident count rises, and senior engineers become integration coordinators instead of product builders. The architecture meant to improve autonomy starts consuming it.
This is the distributed tax: every network boundary adds latency, retries, timeouts, schema versioning, observability overhead, security policy, deployment choreography, and on-call surface area. A function call becomes a production dependency.
The tax is not theoretical. Martin Fowler’s “Microservice Prerequisites” argument has held up because the prerequisites were always the hard part: strong operational maturity, automated deployment, observability, and teams capable of owning services end to end. Most startups adopt the topology before they build the operating model.
DORA’s research in Accelerate and the annual State of DevOps reports makes the core point more uncomfortable: architecture alone does not create software delivery performance. Elite delivery performance comes from capabilities like test automation, loosely coupled teams, observability, and deployment safety. If your architecture change weakens those capabilities in the near term, delivery gets worse even if the diagram looks more modern.
This is where leaders get trapped. They are not choosing between “simple monolith” and “mature microservices.” They are usually choosing between a well-understood modular monolith and a premature distributed system they do not yet have the people, tooling, or organizational shape to run.
For a CTO at a 50-person company, this matters immediately. Every additional service creates a permanent line item: CI/CD pipelines, dashboards, alerts, access controls, dependency management, rollback paths, and ownership ambiguity. None of that ships customer value directly. All of it compounds.
Netflix is the canonical counterexample people cite. It should be the warning, not the aspiration. Netflix moved to microservices under pressure from massive scale, rapid product expansion, and the need to survive failures in a cloud environment. It also built years of internal tooling around deployment safety, resilience testing, and service governance. Copying the service count without copying the platform and operational investment is cargo cult engineering.
The sharper truth is this: most engineering organizations under 200 engineers do not have a microservices problem. They have a modularity, ownership, release, or database contention problem. Those are different problems, and microservices are often the most expensive possible treatment.
02 WHY IT HAPPENS
The root cause is incentive mismatch.
Leaders want speed. Senior engineers want cleaner boundaries. Recruiters know “microservices” sounds more advanced than “modular monolith.” Vendors sell infrastructure that presumes a fleet of services. Everyone gets social reinforcement for decomposition long before anyone measures whether the current bottleneck is deployment coupling, runtime scaling, or team interference.
That mismatch gets amplified by a persistent false equivalence: modular architecture is treated as synonymous with distributed architecture.
It is not.
A modular monolith gives you many of the benefits teams actually need early on: domain separation, isolated tests, explicit interfaces, code ownership, and the ability to refactor quickly. What it does not impose is network failure between every boundary. That distinction matters more than most architecture discussions admit.
DHH has made this point for years from the Basecamp and 37signals experience: complexity has a carrying cost, and teams should spend that budget only when pressure is real. You do not get reliability by adding moving parts. You get more places for reliability to fail.
Stripe’s public engineering writing is useful here because Stripe did not become respected for indiscriminate decomposition. Stripe became respected for disciplined infrastructure, strong API design, careful abstractions, and deep operational control. Its architecture choices are tied to product and reliability constraints, not fashion. That is the pattern mature companies follow: they earn distribution through necessity.
The second cause is team topology.
Will Larson’s work in An Elegant Puzzle and the broader Team Topologies conversation both point to the same issue: software architecture mirrors communication structure. If one team still owns five tightly coupled services, you have not increased autonomy. You have multiplied operational overhead inside the same boundary.
Gabe’s “one team, many services” anti-pattern is exactly right. If a single team deploys most of its services together, reviews all cross-service contracts, and responds to incidents spanning the whole flow, then the architecture is giving you the costs of decomposition without the organizational benefit of independence.
The third cause is scale confusion.
There are three different forms of scale:
- Traffic scale — requests per second, data volume, throughput.
- Team scale — number of engineers making concurrent changes.
- Domain scale — product complexity and business capability spread.
Most companies cite the first when they are really suffering from the second or third.
If one endpoint is slow because a single query is bad, that is not a microservices justification. If deploys are dangerous because there is weak test coverage and no canary strategy, that is not a microservices justification. If five teams keep editing the same billing code because the ownership model is fuzzy, the answer may be a modular domain boundary, not a fleet of networked services.
Google’s SRE book is blunt about distributed systems: they create modes of failure that do not exist in local systems. Partial failure, gray failure, retries amplifying load, cascading timeouts, and eventual consistency are not edge cases. They are normal operating conditions. A team moving from a monolith into services inherits all of them on day one.
The fourth cause is platform immaturity.
Microservices require a paved road. That means:
- standardized service templates
- consistent telemetry
- centralized auth patterns
- schema compatibility rules
- deployment automation
- runtime discovery and configuration
- incident management with clear ownership
Without that, each team rebuilds the same basics in slightly different ways. Engineering time shifts from product logic to service scaffolding.
This is why platform teams often emerge after microservices, but too late. The architecture creates heterogeneous service sprawl first; the platform function tries to normalize it afterward. By then, every exception has already been committed into production.
Airbnb’s engineering history is instructive because the company has written openly about investing in a developer platform to reduce complexity as it scaled. The lesson is not “build a platform team early no matter what.” The lesson is that service-based architectures only stay productive when the platform burden is treated as a first-class product. Most Series A–C companies are not staffed for that.
The fifth cause is database mythology.
Teams often say they want microservices, but what they really want is to stop tripping over a shared schema. That is a real problem. It is also one of the hardest parts of decomposition.
Database-per-service sounds clean in architecture diagrams. In practice, the move creates cross-service consistency issues, duplicate data models, event propagation complexity, and painful backfills. If your current team struggles to evolve one schema safely, multiplying schemas does not remove the weakness. It expands it.
Figma’s engineering success is not commonly framed as an anti-microservices case, but it should be studied that way. Figma’s core challenge was not “break everything into tiny services.” It was building a product with demanding real-time collaboration, performance constraints, and nuanced data consistency requirements. Architecture followed product reality, not fashion.
The pattern that emerges at scale is simple: companies that benefit from microservices usually arrive there after hitting clear limits in a simpler architecture. Companies that suffer under microservices usually start there before those limits appear.
03 WHAT MOST GET WRONG
The most common misdiagnosis is thinking the monolith is slow because it is one deployable.
Usually, the monolith is slow because ownership is weak, tests are brittle, architecture boundaries inside the code are porous, and release engineering is underpowered. Splitting the codebase distributes those problems. It does not solve them.
A bad release process inside one app becomes a bad release process across 40 services.
A second mistake is treating deploy independence as a binary win.
Independent deployability matters only if services can actually change safely in isolation. If one user-facing workflow spans six services, and a feature requires contract changes across four of them, then the “independent deployability” benefit is mostly fiction. You now need compatibility windows, staging validation, traffic sequencing, and rollback coordination.
This is where distributed monoliths are born.
Fowler and others have warned about this pattern for years, but the practical test is simpler than the theory: if the same feature regularly requires same-week changes to multiple services owned by multiple teams, your service boundaries are wrong or premature.
A third mistake is assuming microservices improve reliability by containing blast radius.
That can be true. It can also be false.
Service boundaries limit blast radius only when dependencies are well understood, fallbacks exist, and upstream/downstream behavior under failure has been engineered deliberately. Otherwise, the blast radius just propagates through RPC chains instead of through in-process code.
Netflix famously built resilience tooling such as Chaos Monkey because service-based systems fail in non-obvious ways. The lesson is not that chaos engineering is cool. The lesson is that reliability in a distributed system requires active, continuous investment. Without it, every dependency becomes another path to user-visible failure.
Cloudflare’s engineering writing often surfaces this tradeoff cleanly: performance and reliability depend on simplifying hot paths, reducing unnecessary dependencies, and being deliberate about edge behavior. That mindset often points away from service proliferation on latency-sensitive paths.
A fourth mistake is underestimating observability cost.
Charity Majors has been one of the clearest voices on this: observability in distributed systems is not “nice to have logging.” It is the only practical way to debug unknown-unknowns across service boundaries. Once a single user request traverses multiple hops, local logs stop being enough. You need correlation IDs, trace propagation, cardinality-aware instrumentation, and engineers who know how to use them.
Without that, MTTR rises sharply because every incident starts with reconstructing the request path.
A fifth mistake is believing Kubernetes solves microservices complexity.
Kubernetes solves a class of scheduling and orchestration problems. It does not solve domain boundaries, contract design, on-call ownership, data consistency, or organizational coupling. In immature environments, Kubernetes can actually hide weak architecture under a veneer of operational sophistication.
This is why Kelsey Hightower repeatedly pushed teams to understand fundamentals before adding layers. Containers, orchestration, and service meshes are powerful tools. They do not make a poor system design less poor.
A sixth mistake is using microservices as a hiring signal.
This is more common than leaders admit. “We run microservices on Kubernetes” sounds more senior than “we run a Rails monolith with strict modular boundaries.” But one of those statements tells you very little about engineering effectiveness, and the other often correlates with a company that knows how to avoid unnecessary complexity.
GitHub is a useful example. For a long time, GitHub operated at enormous scale on a monolithic Rails application while making targeted infrastructure investments around it. That did not mean the architecture was simplistic. It meant the company was disciplined about where complexity belonged.
A seventh mistake is migrating by extraction without a forcing function.
Teams pick a subsystem, expose an API, create a new service, and celebrate progress. Then they realize the service still shares the same database, release train, and engineers. The extraction created another boundary, but not another independent unit.
Uber’s service explosion became famous partly because the company hit very real scale and team-complexity constraints, but the industry mostly copied the visible architecture and not the organizational or operational lessons. The result across the startup ecosystem was a decade of over-decomposition.
The cost is concrete:
- slower local development because full workflows require multiple services
- higher CI cost because integration environments are heavier
- longer incidents because ownership spans teams
- more regressions because contracts drift
- more roadmap friction because every cross-cutting feature becomes program management work
A company does not notice this in month one. It notices it when every roadmap estimate includes “cross-service coordination,” every postmortem includes “missing observability,” and every senior engineer becomes a human message bus.
04 THE FRAMEWORK
The right way to decide on microservices is not ideological. It is a pressure-test across architecture, org design, and operational readiness.
Use this framework before you split anything.
1. Prove the current bottleneck with delivery and reliability metrics
Do not start with architecture diagrams. Start with measurable pain.
Use the four DORA metrics as your baseline:
- deployment frequency
- lead time for changes
- change failure rate
- time to restore service
Nicole Forsgren, Jez Humble, and Gene Kim established these as leading indicators of software delivery performance in Accelerate. If your deployment frequency is low because releases are manual, your lead time is long because code review is backlogged, or your change failure rate is high because tests are weak, microservices will usually worsen all four before they improve any.
A practical threshold: if your primary application can already deploy at least daily with rollback in under 30 minutes for most incidents, architecture is probably not your first delivery bottleneck. If deploys happen weekly or less, fix release engineering before service decomposition.
2. Distinguish modularity needs from distribution needs
Ask two different questions:
- Do we need clearer domain boundaries in the code?
- Do we need runtime independence between components?
The first often means modular monolith. The second may justify services.
A modular monolith should have:
- enforced module boundaries
- explicit interfaces between domains
- separate ownership by team or tech lead
- the ability to test modules independently
- restrictions on direct cross-module database access
If you do not have these inside one codebase, distributing the code will not help. It will make refactoring slower because every design mistake crosses a network boundary.
Shopify is instructive here. Shopify scaled a large Rails monolith for years while investing deeply in performance, tooling, and developer workflows. The architecture was not accidental; it was a conscious tradeoff to preserve speed. Shopify’s public engineering output consistently shows a bias toward extracting complexity only where pressure is real.
3. Apply the “independent change” test
A service boundary is valid only if the majority of changes inside that boundary can ship without coordinated changes elsewhere.
Use this rule:
- If more than 30% of changes to a proposed service require synchronized code or schema changes in another service, the boundary is not ready.
- If a critical user flow crosses more than 5 synchronous service hops, the reliability and debugging overhead deserve explicit review.
- If one team owns more than 3–4 tightly related services and deploys them together, collapse them unless traffic scale demands separation.
These are practitioner thresholds, not standards, but they are useful because they force honesty. The point is not mathematical purity. The point is catching fake autonomy before it hardens into architecture.
Linear is a strong example of this discipline in spirit. Linear’s engineering culture has emphasized speed, product quality, and operational simplicity. The company is notable not for broadcasting maximal infrastructure complexity, but for making careful choices that preserve developer velocity. That restraint is an architectural capability.
4. Make data ownership the gating item
Most service migrations fail at the data layer, not the API layer.
Before creating a service, answer:
- Who owns the source of truth?
- Which writes are authoritative?
- What consistency is required for the user-facing workflow?
- What is the backfill and migration plan?
- What breaks if event delivery lags by 30 seconds? By 5 minutes?
If you cannot answer these, do not extract the service yet.
Database-per-service should be treated as an outcome of proven ownership, not a default checkbox. Shared databases across services create hidden coupling. Fully separate databases create consistency and duplication costs. Neither option is free.
Stripe’s API and systems discipline is relevant because payment systems are fundamentally data integrity systems. In domains where correctness matters more than nominal autonomy, boundary choices must serve invariants first. Architecture follows data truth.
5. Build the platform minimum before the fleet
Do not let every team reinvent service basics.
Before your service count grows beyond roughly 10 production services, put a small paved road in place:
- standard service template
- standard metrics, logs, and traces
- auth and secret management defaults
- health checks and readiness semantics
- canary or progressive deployment support
- runbook template
- ownership metadata in code and pager tooling
This does not require a large platform team. It does require one or two strong engineers treating internal developer experience as real product work.
HashiCorp’s product philosophy is relevant here even outside its own stack: operational primitives become leverage only when they are standardized. If every service uses a different pattern for config, secrets, retries, and telemetry, your platform does not exist. You have a collection of local decisions.
6. Price the true operational load, not just build effort
Every new service adds recurring cost in at least six buckets:
- CI/CD maintenance
- observability and alert tuning
- security patching and access control
- dependency upgrades
- on-call ownership
- incident coordination
Most architecture proposals account only for build cost.
For a startup in the 20–200 person range, a useful planning assumption is that every production service with real traffic creates a non-trivial maintenance tail. Even if the code is stable, the environment is not. Runtime versions change. Certificates rotate. dashboards drift. Alerts rot. Dependencies break. Engineers leave.
The right question is not “Can we build this service?” It is “Who owns this service at 2:13 a.m. nine months from now, and what tools do they have?”
Datadog’s ubiquity in service-heavy environments is not an accident. The need appears because fleets become hard to reason about fast. But buying observability tooling does not remove the need for ownership and instrumentation discipline.
7. Extract by business capability, not technical layer
The worst service boundaries are horizontal:
- auth service
- notification service
- user service
- common service
- reporting service
These names sound clean. They usually become traffic magnets and coordination traps.
Prefer boundaries that map to stable business capabilities with clear ownership and distinct scaling or release needs. Examples might include billing, document conversion, search indexing, fraud evaluation, or media processing. These capabilities often have clearer data ownership and more obvious reasons to scale independently.
Vercel’s architecture choices around edge delivery and platform concerns are useful because they are tied to actual product capabilities and latency constraints. The company’s platform is not fragmented for aesthetics; boundaries exist where runtime characteristics differ materially.
8. Keep the hot path boring
Do not put distributed complexity on the most latency-sensitive, most revenue-sensitive user path unless the simpler design has clearly failed.
For most B2B SaaS products, the hot path includes:
- login and session validation
- core read/write workflow
- billing-critical events
- primary search or document fetch
- AI request orchestration if the product is AI-native
Every synchronous service hop on these paths adds latency and failure probability. Tail latency compounds. Retries multiply load. Timeouts create ambiguous states.
A practical guardrail: if a p95 request already spans 3 or more synchronous services, challenge every additional hop. Push non-critical work to asynchronous processing where possible.
Cloudflare, Netflix, and Google SRE material all reinforce the same principle from different angles: minimize dependency chains on critical paths.
9. Use extraction sequencing that preserves reversibility
Good migrations are reversible for as long as possible.
A safer sequence is:
- isolate a module in the monolith
- enforce internal API boundaries
- assign single-team ownership
- mirror traffic or events to the future service
- validate correctness and observability
- cut over one workflow
- remove old paths only after stability is proven
Do not start with a hard cutover unless the blast radius is tiny.
GitHub’s long history of incrementalism is instructive. Large, high-scale systems stay maintainable not because they avoid change, but because they stage change in ways that preserve confidence. Mature architecture is often less about decomposition than about reversible movement.
10. Set a collapse rule
Every service architecture should have a policy for merging services back.
This sounds heretical only because teams treat decomposition as progress and recomposition as failure. It is not failure. It is correction.
Set explicit collapse criteria:
- service has under 2 meaningful deployments per month
- changes require another team more than 30% of the time
- on-call volume is negligible but maintenance overhead remains high
- the domain boundary is unclear after two quarters
- incidents repeatedly involve shared failure paths with sibling services
If those conditions persist, merge.
PostHog is relevant as a modern startup example because it has been unusually candid in public about building pragmatically, favoring speed and customer value over architectural theater. That mindset matters more than any specific topology.
05 STRATEGIC TAKEAWAY
Microservices should be treated as an organizational scaling tool with an infrastructure price tag, not as a default engineering maturity milestone. If you apply that lens, the decision this quarter changes from “Should we break up the monolith?” to “Which capability is suffering enough from shared runtime, shared ownership, or shared scaling that it justifies permanent distributed overhead?” Get that call right and you preserve velocity while creating real autonomy where it matters. Get it wrong and within two planning cycles your senior engineers are spending roadmap time on contract coordination, reliability plumbing, and incident glue work instead of shipping product.
06 IMPLEMENTATION ANGLE
Start with a 30-day architecture audit, not a migration plan.
Measure the last 50 production changes. How many required edits in multiple domains? How many needed synchronized deployment? How many incidents crossed service or module boundaries? Pull DORA baselines from your CI/CD and incident tooling. If you cannot quantify your current pain, you are not ready to justify a distributed fix.
Then create a bounded pilot. Pick one candidate capability with clear ownership, non-trivial independent scaling need, and low coupling to the revenue-critical hot path. Stand up the platform minimum first: template, tracing, deployment guardrails, pager ownership, and rollback. One good extraction teaches more than a six-month replatforming initiative.
Org design comes next. If a service does not have a durable owning team, do not create it. If your managers already struggle with on-call quality and service stewardship, adding more services is an org debt multiplier. This is the point where engineering leaders sometimes bring in external help to tighten team design and hiring for service ownership; if you are scaling the org alongside the architecture, Amplify can help engineering teams scale without importing more structural complexity than they can absorb.



