BizTechLab

IDEASINNOVATIONIMPACT

Tech #00718 min read6 August 2026 , Wednesday

The Microservices Hangover: Why Enterprises Are Quietly Returning to Modular Monoliths

After a decade of distributed-systems complexity, surprise DevOps bills, and network-latency taxes, engineering leaders at Amazon, Shopify, and 37signals are rediscovering the speed, simplicity, and economic sanity of well-structured unified codebases.

Rajnish Kumar

Rajnish Kumar

Editor-in-Chief & Founder

The Microservices Hangover: Why Enterprises Are Quietly Returning to Modular Monoliths — Tech dispatch hero image
Editor's Context

This is a reference-grade architectural teardown. Expect real cost numbers, latency benchmarks, and documented case studies from Amazon, Shopify, and 37signals — not just abstract theory. The goal is to give you a decision framework you can take into your next architecture meeting.

1

The Microservices Dream vs. The Production Reality

The Promise That Was Sold

Between 2012 and 2020, became the dominant architectural dogma of the enterprise software industry. The sales pitch was seductive and, in certain narrow contexts, entirely justified: decompose your monolithic application into small, independently deployable services, each owning its own data store and communicating over well-defined network APIs. Individual teams could deploy on their own schedule without coordination. Services could scale independently — the payment processor could scale to 100 instances while the user-profile service stayed at two. Technology heterogeneity was suddenly possible: team A could use Node.js while team B ran Go.

Netflix, Amazon, Google, and Uber became the canonical reference architectures. Conference talks showed sprawling service-dependency graphs as proof of engineering sophistication. The message, implicit and explicit, was that if you were still running a in 2018, you were architectural dead weight.

What those conference talks omitted — because the speakers were typically at companies with 500-person engineering organisations and problems most software teams will never have — was the bill. Not just the AWS bill, though that was real. The cognitive bill, the coordination bill, the debugging bill, the data-consistency bill. The cost that microservices extract in exchange for their genuine benefits is denominated not just in dollars, but in engineering hours, incident response complexity, and architectural decisions that become permanent the moment two services start talking to each other.

Microservices didn't fail as a pattern — they failed as a default.

Microservices solve an organisational scaling problem. Most teams that adopted them had a codebase problem — and paid an organisational tax they never needed.

The Distributed Systems Problem No One Warned You About

The moment you split an application into two processes communicating over a network, you have left the world of functions calling other functions and entered the world of distributed systems. This transition is not gradual — it is a hard categorical shift, and it drags with it a suite of failure modes that simply do not exist in a single process.

A function call either succeeds or throws an exception. A network call can succeed, fail, time out, partially succeed, succeed on the server but have the response lost in transit, or succeed slowly enough that the client gives up and retries — causing the operation to run twice. Every one of those outcomes is possible on every single inter-service call, and every team building microservices must eventually write code that handles all of them correctly.

Peter Deutsch's Eight Fallacies of Distributed Computing — written in 1994 — remain embarrassingly relevant today. The network is not reliable. Latency is not zero. Bandwidth is not infinite. The network is not secure. Topology does not stay constant. There is not one administrator. Transport cost is not zero. The network is not homogeneous. Microservices architecture forces every service and every calling team to contend with all eight of these fallacies simultaneously, for every inter-service boundary they introduce.

  • Partial failure: a downstream service can fail mid-operation, leaving data in an inconsistent state with no built-in rollback mechanism.
  • Timeout ambiguity: when a network call times out, the caller cannot know whether the server received the request, processed it, or never saw it.
  • Cascading failures: a single slow service causes upstream services to exhaust their connection pools and thread pools, triggering a system-wide outage from one degraded component.
  • Retry storms: well-intentioned retry logic, firing simultaneously across hundreds of service instances, can amplify load on a recovering service by 10x — preventing the recovery it was designed to survive.
2

The Hidden Tax: What Microservices Actually Cost

The Network Latency Tax

In a monolithic application, a function call costs roughly 1 nanosecond — a number so small it is effectively free in any real engineering budget. Crossing a network boundary in a microservices architecture costs, at minimum, 0.5 milliseconds on a low-latency internal network — often 1 to 10 milliseconds under real load. That is a difference of six to seven orders of magnitude. This is the — an overhead paid on every single inter-service call, whether or not that call does anything computationally expensive.

This latency is not a bug in your implementation. It is the physics of network communication: the speed of light, the overhead of TCP handshakes, TLS encryption, serialisation and deserialisation of request payloads (JSON parsing alone on a complex object can cost 0.1–1ms), and kernel context switches. No amount of engineering optimisation eliminates this floor. You can reduce it at the margins with gRPC and Protobuf over JSON, with HTTP/2 connection multiplexing, with service co-location in the same availability zone — but the floor remains.

None of this is a flaw in any particular team's implementation — it's the physics and economics of crossing a network boundary, paid again on every single call.

Call TypeTypical LatencyPer-Request Overhead on 5-Service ChainSerialisation Cost
In-process function call~1 ns~5 ns totalNone
In-process via message bus~100 ns~500 ns totalNone
Local network gRPC (Protobuf)~0.5 ms~2.5 ms total~0.05 ms
Local network REST (JSON)~1–3 ms~5–15 ms total~0.1–1 ms
Cross-AZ REST call~5–10 ms~25–50 ms total~0.1–1 ms
Cross-region REST call~50–150 ms~250–750 ms total~0.1–1 ms

ns = nanoseconds · ms = milliseconds · AZ = Availability Zone · gRPC = Google Remote Procedure Call · REST = Representational State Transfer · JSON = JavaScript Object Notation

A five-service request chain on a well-tuned internal network adds 5–50ms of pure overhead before your business logic has run a single line. At the p99 tail, under real production load, that number doubles or triples.

The Distributed Transaction Problem & The Saga Pattern

Relational databases provide transactions — Atomicity, Consistency, Isolation, Durability — as a core guarantee. Within a single database, you can update ten tables inside one transaction: either all ten updates commit together, or none of them do. There is no in-between state visible to any other database reader.

Microservices, by design, place different domain entities in different services with different databases. An e-commerce order creation that previously required one ACID transaction — deduct inventory, create order record, charge payment, send confirmation — now spans three or four services, each with its own data store. There is no native distributed transaction mechanism that provides ACID guarantees across service boundaries at scale — the closest available substitute is : accepting that the data will converge to a correct state, just not instantly and not inside a single atomic step.

The industry-standard solution is the : decompose the distributed transaction into a sequence of local transactions, each with a corresponding compensating transaction that reverses it if a later step fails. A choreography-based Saga publishes events that trigger subsequent steps; an orchestration-based Saga uses a central coordinator to explicitly invoke each step and its compensation.

  • Saga complexity cost: implementing a saga for a five-step business operation requires writing ten pieces of business logic — five forward steps and five compensating transactions, each of which must be tested, monitored, and debugged independently.
  • Idempotency requirement: every step must be — safe to call multiple times — because the orchestrator may retry a step after a timeout without knowing if the first attempt succeeded.
  • Visibility gap: between the moment step 2 commits and step 3 begins, the system is in an inconsistent intermediate state that is visible to other concurrent reads — something an ACID transaction would have prevented entirely.

Orchestration-Based Saga: Order Creation Across 3 Services

Saga Orchestrator

Central coordinator

Step 1: Reserve inventory

Inventory Service

Local DB write

Step 2: Charge payment

Payment Service

Local DB write

Step 3: Create order

Order Service

Local DB write

Step 2 fails → compensate

Inventory Service

Compensating TX: release reservation

The DevOps Complexity Multiplier

A monolithic application has one CI/CD pipeline, one deployment artifact, one set of environment variables, one log stream to query, one set of health checks, and one place to look when something goes wrong at 3am. The operational surface area is bounded by the size of the application.

A microservices architecture multiplies that surface area by the number of services. Each service requires its own Docker image, its own CI/CD pipeline with its own secrets management, its own Kubernetes deployment manifest with its own resource limits and autoscaling policies, its own database migrations, its own API versioning strategy, and its own on-call runbook. The Kubernetes cluster itself — with its ingress controllers, es, certificate managers, horizontal pod autoscalers, and cluster autoscalers — is a full-time operational product requiring specialist expertise.

  • Service discovery overhead: services must find each other at runtime — via DNS, a service registry like Consul, or Kubernetes Service objects — adding a failure point that does not exist in a monolith.
  • Distributed tracing cost: following a single user request across ten services requires a tracing infrastructure (, Jaeger, or Zipkin), instrumentation in every service, and an engineer who knows how to read a trace waterfall — skills a monolith team never needs to develop.
  • Schema coordination: changing the API contract between two services requires coordinating deployments across two teams, implementing backward-compatible versioning, and often maintaining deprecated API versions for months — overhead that an in-process function signature change eliminates entirely.
  • Database-per-service explosion: ten services with ten databases means ten backup strategies, ten migration tools, ten connection pool configurations, and ten sets of database credentials to rotate.
Microservices transform one operational problem into N operational problems. For organisations with the platform engineering headcount to absorb that multiplication, it is a rational trade. For everyone else, it is a debt that compounds quietly until it becomes a crisis.
3

The Case Studies: Real Migrations, Real Numbers

Amazon Prime Video: The 90% Cost Reduction

In May 2023, Amazon Prime Video's engineering team published a technical blog post that became one of the most widely circulated architecture discussions of the year. The post described how their audio/video monitoring service — responsible for detecting quality defects in Prime Video streams in real time — had been built as a distributed microservices architecture using AWS Step Functions for orchestration and separate services for each analysis stage.

The system worked, but it had two structural problems. First, it hit the account-level limits of AWS Step Functions at scale, making horizontal scaling economically and technically constrained. Second, passing large video frame data between distributed components over the network created significant data-transfer costs and latency that a shared in-memory process would not have incurred.

The team's solution was to merge the distributed components into a single monolithic process, running the entire analysis pipeline in one EC2 instance with shared memory between stages. The result: infrastructure costs dropped by approximately 90%. The service became simpler to deploy, simpler to debug, and faster to execute — all simultaneously.

  • What changed: distributed Step Functions orchestration → single EC2 process with in-memory data passing between pipeline stages.
  • What stayed the same: the service is still deployed independently, still has its own codebase and team — the monolith is internal to that , not a company-wide architectural rollback.
  • The key lesson: when the primary communication pattern between components is high-volume data transfer (video frames), network serialisation is not just slow — it is economically irrational.
The Prime Video case study did not prove that microservices are always wrong. It proved that microservices are a tool with a specific application domain — and that applying them outside that domain incurs a real, measurable cost that is easy to avoid.

37signals: $3.2M Saved by Leaving the Cloud

David Heinemeier Hansson, creator of Ruby on Rails and co-founder of 37signals (makers of Basecamp and Hey), published a detailed accounting in 2023 of the company's decision to migrate off AWS and back onto owned hardware. The numbers were specific and verified: 37signals expected to save approximately $3.2 million per year by owning their own servers rather than renting compute from Amazon.

The economics were straightforward once laid out. Cloud computing trades capital expenditure for operating expenditure — instead of buying a server for $15,000 that lasts five years, you pay $8,000 per year to rent equivalent compute. For workloads with predictable, steady traffic — which most mature SaaS businesses have — the rental model becomes increasingly expensive over time relative to ownership.

37signals's Basecamp application is a Rails monolith. It serves millions of users. It has never needed to decompose into microservices because it was built with careful internal modular structure — clean boundaries between concerns, without the overhead of network-based service separation. The monolith runs on hardware the company owns and understands, with performance characteristics that are predictable rather than metered.

  • Hardware purchased: Dell servers with NVMe SSDs — high-performance, predictable latency, no egress fees.
  • Application architecture: Rails monolith with modular internal structure — not microservices, not a big-ball-of-mud. Clean boundaries enforced by Ruby module namespacing.
  • Operational model: small, senior engineering team with deep system knowledge — versus the sprawling on-call rotation a large Kubernetes cluster requires.
  • DHH's framing: this is not an argument against the cloud for every company — it is an argument that the cloud's value proposition weakens significantly as a business's traffic becomes stable and predictable.

Shopify: The Majestic Monolith That Never Needed to Change

Shopify's core commerce platform is a Ruby on Rails monolith — a fact the company has been transparent about for years and has never apologised for. At peak traffic (Black Friday 2023 processed over $9.3 billion in merchant sales in a single day), the Shopify monolith handled a volume of transactions that most microservices architectures would struggle to match.

The secret is not that Shopify has a simple codebase — it is one of the most complex Rails codebases in production. The secret is that the engineering team invested relentlessly in making the monolith modular: enforcing strict component-level boundaries through static analysis tools, separating database access patterns, and treating internal modules as if they were external services in terms of API discipline — without paying the network tax of actually making them external.

Shopify did eventually introduce some service separation for specific high-traffic subsystems — but the core commerce logic remained a unified deployment. DHH's 2016 essay 'The Majestic Monolith', written while at Basecamp, articulated what Shopify had already been quietly practicing: a well-structured, internally disciplined monolith can outperform a microservices architecture on every dimension that matters to a product team.

A monolith is not automatically evidence that a team couldn't adopt microservices — sometimes it's evidence they knew exactly what they didn't need.

The Majestic Monolith is not a failure to adopt microservices. It is a deliberate architectural choice to invest complexity budget in product features rather than distributed systems infrastructure.
4

What Is a Modular Monolith, Actually?

What a Modular Monolith Actually Is

The is frequently misunderstood as simply 'a monolith with good code organisation' — which undersells the architectural discipline it requires. A genuine modular monolith enforces hard boundaries between internal components that are structurally identical to the contracts between microservices, with one critical difference: those boundaries are crossed by in-process function calls rather than network requests.

Each module in a modular monolith owns its own domain model, its own data access layer (and ideally its own schema within a shared database), and exposes a well-defined public API to other modules. No module reaches directly into another module's internal implementation — the same rule that governs inter-service communication in microservices, enforced here at compile time rather than at runtime across a network.

This distinction is consequential. A — the worst outcome of a poorly executed microservices migration — has all the operational complexity of microservices with none of the independence: services that are technically separate but so tightly coupled that they must be deployed together anyway. A modular monolith avoids this failure mode entirely by keeping the coupling within a single process, where it is visible, refactorable, and free of network failure modes.

A modular monolith isn't just "a monolith with good organisation" — it's microservices-grade discipline enforced by the compiler instead of the network.

  • Module boundary rule: no module may import from another module's internal packages — only from its explicitly published public interface.
  • Data isolation: each module owns its own database schema. Cross-module data queries go through the owning module's API, not direct SQL joins across schema boundaries.
  • Deployment unit: the entire modular monolith deploys as one artifact — one container, one binary — eliminating per-service deployment coordination entirely.
  • Extraction path: a well-bounded module in a modular monolith can be extracted into an independent service in days, because its API contracts and data boundaries are already cleanly defined.

Modular Monolith Internal Architecture

Single Deployment Process

Orders Module

Public API only

Payments Module

Public API only

Inventory Module

Public API only

Notifications Module

Public API only

Shared PostgreSQL (separate schemas per module)

orders.*

schema

payments.*

schema

inventory.*

schema

Enforcing Module Boundaries in Practice

Declaring that modules should not depend on each other's internals is easy. Enforcing it as code evolves under deadline pressure is the actual engineering problem. Several tools and patterns have emerged specifically to solve this in monolithic codebases.

  • Ruby/Rails: Packwerk — Shopify open-sourced , a static analysis tool that enforces package boundaries in Rails apps. Running in CI, it fails any build where a module imports from another module's private namespace.
  • Java: ArchUnit — a test library that allows teams to write architecture rules as unit tests: 'no class in com.app.orders may depend on a class in com.app.payments.internal'. Failures break the build.
  • Go: module graph analysis — Go's explicit package import graph makes cross-module dependencies visible and auditable with standard tooling.
  • TypeScript: path aliases + ESLint rules — barrel exports per module combined with ESLint import boundary rules prevent cross-module internal access.
  • Database: schema-per-module — physically separating module data into distinct PostgreSQL schemas prevents cross-module raw SQL joins and makes data ownership unambiguous.
5

The Distributed Systems Fallacies That Bit Everyone

The Eight Fallacies of Distributed Computing

L. Peter Deutsch originally compiled what became known as the Eight Fallacies of Distributed Computing while at Sun Microsystems in the 1990s. They remain the most concise description of why distributed systems — including microservices — are categorically harder to reason about than single-process systems. Every team that adopted microservices without internalising these fallacies paid for the education in production incidents.

Every one of these eight assumptions is quietly true inside a single process — which is exactly why splitting that process in two is a bigger decision than it looks like on a whiteboard.

FallacyThe Wrong AssumptionThe Real-World Consequence
1. The network is reliableNetwork calls succeed or fail cleanlyPartial writes, phantom timeouts, split-brain state — operations that appear to fail but actually succeeded
2. Latency is zeroService calls are effectively instant5-service chain = 5–50ms overhead minimum; p99 tail latency spikes unpredictably under load
3. Bandwidth is infiniteSending large payloads is freeVideo/image data passed between services costs money and causes congestion (the Prime Video case study)
4. The network is secureInternal network traffic is trustedEast-west traffic between services requires mTLS, service mesh, and zero-trust policies
5. Topology doesn't changeService IPs and hostnames are stableKubernetes pod restarts change IPs; DNS TTLs cause stale routing; service discovery must be dynamic
6. There is one administratorOne team owns and understands the system10 services = 10 teams = 10 deployment schedules = coordination overhead grows with every new service
7. Transport cost is zeroNetwork calls are freeEgress fees, serialisation CPU cost, and connection pool exhaustion all have real dollar values
8. The network is homogeneousAll services communicate consistentlyMixed protocols (REST, gRPC, WebSockets, message queues) create interoperability surface area

mTLS = mutual Transport Layer Security · DNS = Domain Name System · TTL = Time-to-Live · CPU = central processing unit · REST = Representational State Transfer · gRPC = Google Remote Procedure Call

6

Industry Problems & Engineering Bottlenecks

The Distributed Monolith: The Worst of Both Worlds

The most dangerous outcome of a microservices migration is not a failed migration — it is a partially completed one. When teams decompose a monolith into services without investing the time to establish clean bounded contexts and truly independent data ownership, the result is a distributed monolith: multiple separately deployed processes that are still tightly coupled to each other's implementation details.

A distributed monolith deploys services independently in theory, but in practice must coordinate deployments because service A depends on a specific internal schema or behaviour of service B. It has all the operational complexity of microservices — separate pipelines, separate logs, distributed tracing requirements — with none of the actual independence that justifies that complexity.

  • Detection symptom: if two services must always be deployed together after a change, they are a distributed monolith in those components.
  • Detection symptom: if service A calls service B synchronously on every request and service B going down causes service A to go down, your 'microservices' are a tightly coupled distributed system.
  • Detection symptom: if you cannot explain in one sentence what business capability each service exclusively owns, the boundaries are wrong.
  • Fix: merge tightly coupled services back into a single deployment unit and establish genuine bounded contexts before attempting to re-extract them.

Service Mesh Complexity Overhead

At sufficient microservices scale, teams introduce a service mesh — Istio, Linkerd, or Cilium — to handle cross-cutting concerns that cannot reasonably be implemented individually in every service: mutual TLS between services, traffic routing and load balancing, rate limiting, , and distributed tracing injection.

A service mesh is itself a significant distributed system that must be operated, upgraded, and debugged. Istio, the most widely adopted mesh, runs a control plane (Pilot, Citadel, Galley) plus a data-plane Envoy sidecar proxy injected into every pod. The mesh adds latency (typically 1–5ms per request through the proxy), resource overhead (the Envoy sidecar consumes CPU and RAM on every node), and a new failure domain: if the control plane degrades, certificate rotation fails and services begin rejecting each other's connections.

The service mesh is the infrastructure you build to solve the problems that microservices created. A modular monolith needs none of it — security, routing, and observability are handled at the process level, not the network level.

The On-Call Incident Complexity Problem

Debugging a production incident in a monolithic application follows a familiar sequence: check the application logs, find the stack trace, identify the failing function, examine the code. The entire investigation lives in one codebase, one log stream, and one deployment artifact. A senior engineer joining a company can diagnose most production incidents within hours of gaining access.

Debugging a production incident in a microservices architecture requires: identifying which service in a dependency graph is the root cause (not just the first service to surface an error to the caller), correlating log entries across multiple separate log streams using a trace ID, reading a distributed trace waterfall in Jaeger or Zipkin to find which service introduced the latency spike, potentially reproducing the issue in a local environment that requires running eight Docker containers simultaneously, and understanding the deployment history of multiple services to identify which recent change introduced a regression.

On-call cognitive load compounds with every service added to the graph. The Mean Time to Resolution (MTTR) for microservices incidents is consistently higher than for equivalent monolithic incidents, even when the root cause is no more complex — because the investigation tooling is more complex.

None of this complexity means anyone did microservices wrong — it's the standard price of the pattern, worth paying only when the organisation is actually large enough to need what it buys.

7

Decision Framework: When to Use What

The Decision Framework: Monolith First

Martin Fowler, who co-authored the original microservices article that helped launch the pattern into mainstream adoption, has been clear in subsequent writing about the correct sequencing: almost all successful microservices implementations started as a monolith that was decomposed when the organisation and codebase had both grown large enough to justify it. Almost all failed microservices implementations started as microservices from day one, before the team understood where the genuine domain boundaries were.

The '' principle is not a rule against ever using microservices. It is a sequencing argument: a modular monolith with clean internal boundaries is a lower-risk starting point for any new system, because it preserves optionality. If the system grows to the scale where microservices benefits are genuine — truly independent scaling requirements, organisational teams large enough to own independent services — a well-bounded module can be extracted into a service. If the system never reaches that scale, nothing was wasted on premature distributed systems complexity.

This is also where does its quiet work — an organisation's service boundaries tend to mirror its communication structure whether or not that was ever a deliberate design decision.

SignalSuggests MonolithSuggests Microservices
Team size< 20 engineers on the system> 50 engineers across multiple autonomous teams
Deploy frequencyMultiple times per day from one pipelineMultiple teams need independent deploy cadences
Scaling patternUniform — the whole system needs more capacityHeterogeneous — one component needs 100x another
Domain clarityBounded contexts not yet fully understoodBounded contexts stable and well-defined for 2+ years
Failure toleranceStrict consistency required across all operationsEventual consistency acceptable for most operations
Org structureSingle team, shared codebase ownershipConway's Law already forcing service boundaries
Traffic profilePredictable, steady-stateHighly variable, component-specific spike patterns
Data modelShared relational model works wellEach domain clearly owns separate, independent data

Conway's Law: organisations design systems that mirror their communication structure. Microservices work best when your org chart already matches the service boundary you want.

When Microservices Are Genuinely the Right Answer

This article is not an argument that microservices are always wrong. They are the correct architectural choice in a specific, narrow set of circumstances — and the companies that first popularised them (Netflix, Amazon, Google) genuinely inhabit those circumstances. The problem was not the pattern; it was the indiscriminate application of a pattern designed for 500-person engineering organisations to teams of five building their first product.

The mistake was never choosing microservices — it was choosing them as a default instead of as the answer to a problem the team actually had.

  • Organisational independence: when you have 50+ engineers on a system and the coordination overhead of shared codebase ownership is measurably slowing down feature velocity, service boundaries create genuine team autonomy.
  • Heterogeneous scaling requirements: when one component (video transcoding, payment processing, real-time bidding) genuinely needs to scale to 100x the rest of the system, independent deployment units are necessary.
  • Technology heterogeneity: when a specific component genuinely benefits from a different runtime — a latency-sensitive service needing Rust, a ML pipeline needing Python — service separation is justified.
  • Fault isolation requirements: when a non-critical feature failing must be guaranteed not to crash the core product — circuit breaking and bulkhead isolation across service boundaries is the correct tool.
  • Regulatory compliance boundaries: when different parts of a system must meet different compliance regimes (PCI-DSS for payments, HIPAA for health data) and isolation is a legal requirement, not an engineering preference.

The Extraction Path: Monolith to Services When Ready

A modular monolith built with clean boundaries is not a dead end — it is the correct first step on a path that can lead to selective service extraction when the genuine signals for doing so appear. The extraction process from a well-structured monolith is dramatically less risky than decomposing a tightly coupled legacy codebase.

The — named by Martin Fowler after the fig tree that grows around an existing tree, eventually replacing it — provides the canonical migration path. New functionality is built as an independent service from the start, and existing monolith functionality is migrated incrementally, one bounded context at a time, with an API gateway routing requests to old and new implementations simultaneously during the transition.

Strangler Fig Migration Pattern

All Traffic

API Gateway / Reverse Proxy

Monolith

Legacy routes still handled here

New Service

Extracted module, now independent

Over time: traffic shifts 100% to new service

New Service (standalone)

Monolith route decommissioned

8

Comparative Summary Matrix

Comparative Summary Matrix

Architecture is not a binary choice between monolith and microservices — it is a spectrum, and the right position on that spectrum is determined by your organisation's size, your domain's complexity, and the genuine scaling pressures your system faces.

DimensionSingle MonolithModular MonolithMini-Services (2–5)Microservices (10+)
Deployment complexityMinimal (1 artifact)Minimal (1 artifact)Low (2–5 pipelines)High (N pipelines + Kubernetes)
Local dev setup1 command1 commanddocker-composeMinikube / k3d + 10+ containers
Inter-component latency~1 ns (function call)~1 ns (function call)~1 ms (local network)~1–10 ms (network + serialisation)
Distributed transactionsNative ACIDNative ACIDSaga Pattern neededSaga Pattern required everywhere
Debugging / observability1 log stream, 1 stack trace1 log stream, 1 stack trace2–5 logs, basic tracingDistributed tracing required (Jaeger/Zipkin)
Independent scalingNot possibleNot possibleLimited (by service)Full per-service autoscaling
Team autonomyLow (shared codebase)Medium (module ownership)High (per-service teams)Full (independent deploy cadence)
Operational overheadVery lowVery lowModerateVery high (service mesh, K8s, etc.)
Right org size1–10 engineers5–50 engineers20–100 engineers50–500+ engineers
Failure blast radiusFull system on crashFull system on crashPartial (circuit breakers)Minimal (fault isolation per service)

ns = nanoseconds · ms = milliseconds · ACID = Atomicity, Consistency, Isolation, Durability · K8s = Kubernetes · N = number of services

Conclusion: Architecture is a Tool, Not an Identity

The microservices hangover is real — measurable in the AWS bills that Amazon Prime Video cut by 90%, in the $3.2M that 37signals recovered by owning their own hardware, in the incident post-mortems that trace cascading failures across a dozen services back to one misconfigured timeout. But the hangover is not an indictment of distributed systems as a concept. It is an indictment of the cargo-culting of an organisational scaling solution into contexts where the organisation never needed to scale in the first place.

Shopify did not fail to adopt microservices. Shopify made a deliberate, informed choice to invest its engineering complexity budget in product depth rather than distributed infrastructure — and built one of the most reliable, high-throughput commerce platforms in the world as a result.

The correct engineering disposition is neither reflexively pro-monolith nor reflexively pro-microservices. It is to understand what problem each pattern solves, to be honest about whether your organisation actually has that problem today, and to choose the simplest architecture that your current scale genuinely requires — while building it with the internal modularity that keeps all future options open.

Start with a modular monolith. Extract services when — and only when — you have a genuine, measured reason to. The companies that got microservices right did exactly that. The companies that are quietly returning to monoliths skipped the first step.

The best architecture is the simplest one that solves the problem you actually have today — not the one you imagine you might have at ten times your current scale.

Have a technical response or architectural perspective to share with the engineering desk?

Submit Engineering Feedback