Module 10 opens with the architectural pattern that defined a decade of engineering discourse and then quietly reversed itself. From roughly 2015 to 2020, the default answer to “how should we structure our backend” was “microservices, obviously”. By 2021 the same companies that had been the loudest evangelists were publishing posts about consolidating back to monoliths, or to a hybrid the industry started calling the modular monolith. The pendulum did not swing because the original arguments were wrong. It swung because the costs that nobody had fully measured turned out to be larger than the benefits for most teams.
This lesson is about that arc, what each shape is good for, and how to decide which one your team should be in. It is the opener of Module 10 because every later lesson in the module (event-driven architecture in lesson 74, multi-region in lesson 75, and the rest) builds on top of a chosen service-decomposition strategy. Picking the wrong shape locks you into the wrong set of trade-offs for years.
What “microservices” actually means
Strip the branding and a microservice architecture is one where a single product is split into many small, independently deployable services that communicate over the network, each with its own data store, its own deploy pipeline, and ideally its own team.
The contrast is with a monolith: one large codebase, one deployable artefact, one database (or a small set), one deploy pipeline. The monolith is the default starting shape from lessons 1 to 6. Microservices are what teams reach for when the monolith starts to hurt.
The argument for microservices in the mid-2010s was a bundle of real benefits. Independent deployability: team A ships its service without coordinating with team B. Independent scalability: the read-heavy service runs on a different fleet from the compute-heavy one. Polyglot persistence (lesson 24): the user service uses Postgres, the search service uses Elasticsearch, the cart service uses Redis. Polyglot programming languages: the ML team writes Python, the payments team writes Java, the front-of-the-stack team writes Go. Smaller services are easier for one team to own end-to-end.
Each benefit is real. The honest version of the story is that each benefit also has a cost the marketing did not advertise.
Conway’s law and the org chart
The clearest predictor of what an architecture will look like is not the design diagram but the organisation chart. Conway’s law, from a 1967 paper by Melvin Conway, states it directly: any organisation that designs a system will produce a design whose structure is a copy of the organisation’s communication structure.
In the microservices boom this worked in both directions. Big tech companies with hundreds of teams genuinely did need many independently-owned services, because their org chart had many independent teams. Smaller companies copying the pattern ended up with thirty services owned by twelve engineers, and the architecture mismatched the org. The result was a perpetual coordination tax: every change touched five services, every five services were owned by two people, and the two people spent their time in meetings instead of shipping.
The reverse Conway manoeuvre, popular in the 2018 to 2020 period, was to redesign the org chart to match the desired architecture. Sometimes it worked. More often it produced an org chart full of teams of two who could not realistically own a service end-to-end, and the architecture suffered the same way as before. Conway’s law is descriptive, not prescriptive: it tells you what will happen, not what you should aim for.
The 2021+ pushback
Starting around 2021, a series of public posts from companies that had previously championed microservices began to walk the position back. The three most-cited:
Shopify had been running a Rails monolith for over a decade and wrote a series of posts (“Deconstructing the Monolith”, Shopify Engineering, 2019, https://shopify.engineering/deconstructing-monolith-designing-software-maximizes-developer-productivity, retrieved 2026-05-01) about how they kept it manageable as the company grew. The Shopify pattern is what they call componentisation: clear internal module boundaries inside one deployable, with strict rules about which module can call which. The argument is that they got most of the benefits of microservices without paying the operational cost.
Basecamp ran a similar argument in louder language. David Heinemeier Hansson coined the term “majestic monolith” and made the case in writing and at conferences that a small team should never split into microservices, because the operational cost of coordination is larger than any benefit.
Amazon Prime Video published a 2023 post (“Scaling up the Prime Video audio/video monitoring service and reducing costs by 90%”, AWS Open Source Blog, March 2023, https://www.primevideotech.com/video-streaming/scaling-up-the-prime-video-audio-video-monitoring-service-and-reducing-costs-by-90, retrieved 2026-05-01) describing how their monitoring service had been built as a serverless microservices pipeline, hit a scaling and cost wall, and was rebuilt as a single monolithic service running on EC2. Costs dropped by 90 percent. The post was striking because it came from inside Amazon, the company most associated with the original microservices push.
The pattern across the three is not “microservices were a mistake”. It is “microservices have a cost we underestimated, and many teams that adopted them did not need them in the first place”.
When microservices win
The honest list of cases where the architecture pays for itself:
Independent team ownership at scale. When you have ten teams that need different deploy cadences, different on-call rotations, and different tech stacks, splitting into ten services is the architecture that lets them work in parallel. Below that scale, the coordination is cheaper than the split.
Different scaling profiles per component. When the read service is doing a million reads per second and the analytics service is doing a thousand long-running aggregations, putting them in the same deployable means scaling the analytics service buys you read capacity you do not need, or vice versa. Splitting lets each fleet match its load.
Polyglot persistence per service. When the user service is fundamentally relational, the activity feed is fundamentally a stream, and the recommendations service is fundamentally a vector store, splitting lets each team pick the data store that fits the problem.
Polyglot programming languages. When part of the system has hard performance requirements that need a compiled language and part of the system has rapid-iteration requirements that need a scripting language, splitting lets each team make the local choice without forcing the same choice on everyone else.
Fault isolation across critical paths. When a failure in the search service must not take down the checkout service, the network boundary becomes a fault boundary. A bug in one service does not crash the other.
The pattern across these cases is that they are about scale: scale of team, scale of load, scale of complexity. Below the scale, the gains are small and the costs are real.
When microservices lose
The complementary list, drawn from the failure modes that drove the 2021+ pushback:
Small team, many services. When five engineers own twelve services, every cross-cutting change is a multi-service rollout, and the team’s attention is fragmented across services nobody fully understands.
The distributed-systems tax. Every cross-service call introduces network failure modes. The eight fallacies from lesson 9 (the network is reliable, latency is zero, bandwidth is infinite, and so on) all return as production incidents the team has to engineer around. Timeouts, retries, circuit breakers, idempotency, dead-letter queues. None of those are needed in a monolith because the function call inside one process does not fail in those ways.
Debugging across services. A user reports “the page is slow”. In a monolith you read the request log and the slow query log. In microservices the request crosses six services, and you need a distributed tracing system to see where the time went. The tracing system itself is infrastructure you have to build and run.
Local development complexity. “Spin up thirty services to run the app on your laptop” is a real failure mode. Teams end up with elaborate Docker Compose files, mocking layers, or tilt-and-skaffold setups, just to make development possible. New engineers spend their first week on environment setup.
Versioning and contract drift. Service A and service B share an API. When A changes the API, B must be updated. Multiply by N services and the coordination cost grows quadratically.
Operational cost. Each service has its own deployment, its own monitoring dashboard, its own log aggregation, its own alerting, its own on-call. The fixed cost per service is non-trivial.
The phrase that captured this in the late 2010s was “you must be this tall to ride the microservices ride”, attributed in various forms to Martin Fowler and others. The point was that microservices presuppose a level of platform maturity (CI/CD, observability, container orchestration, service discovery, secrets management, internal API tooling) that small teams do not have and cannot build cheaply.
The middle path: modular monolith
The architecture that came out of the 2021+ pushback as the new default for most teams is the modular monolith. One deployable, one database (or a small set), one deploy pipeline. Internal module boundaries are strong: each module owns its domain, exposes a defined interface, and cannot reach into another module’s internals. The compiler or runtime enforces the boundaries.
The implementations vary by language and framework. Shopify’s components use Ruby gem boundaries with custom tooling to enforce dependencies. Rails engines partition a Rails app into mounted sub-applications. Java packages and Maven modules give the same shape with stronger compile-time enforcement. .NET projects, Go internal packages, and Python namespace packages all play the same role.
The benefits the modular monolith preserves:
- One deployable, one deploy pipeline, one log stream, one trace, one debugger session.
- Refactoring across module boundaries is a normal code change, not a multi-service migration.
- Performance is whatever a function call costs, not whatever a network round-trip costs.
- The team can move all of the code at once when the design needs to change.
The benefits the modular monolith gives up, compared to true microservices:
- Independent deployability across teams (every change ships in the same deploy).
- Per-module scaling (the whole monolith scales together).
- Polyglot persistence per service (you can still use multiple data stores, but they are coupled to the same deployable).
- Polyglot programming languages.
The escape hatch is the path the modular monolith makes cheap: when one module genuinely needs independent deploy cadence or independent scaling, extract it into a service. Not before. The discipline is to keep the option open and not pre-emptively pay the cost.
Picking by shape
The decision tree that falls out of all of this:
flowchart TD
A[Are you a small team less than 20 engineers?] -->|Yes| B[Modular monolith]
A -->|No| C[Do teams need independent deploy cadence?]
C -->|No| B
C -->|Yes| D[Do components have very different scaling profiles?]
D -->|No| E[Modular monolith with selective extraction]
D -->|Yes| F[Microservices for the components that need it]
F --> G[Keep everything else in the modular monolith]
The same idea on a team-size axis: at one engineer to five engineers, monolith. Five to fifty, modular monolith. Fifty to a few hundred, modular monolith with a handful of extracted services for the components that justify it. A few hundred and up, microservices become the default and the question flips.
The cross-references close the loop. Lesson 8 covered three companies (Stripe, Shopify, Basecamp) that stayed simpler than the industry expected and shipped at scale on a monolith. Lesson 56 covered Stripe’s deployment pipeline, where a single monorepo with internal services lets them ship hundreds of times a day without paying the full microservices coordination cost. Both cases point at the same lesson: the architecture that wins is usually the one that matches the team and the load, not the one that wins the architecture-blog war.
Citations
- “Deconstructing the Monolith”, Shopify Engineering, 2019,
https://shopify.engineering/deconstructing-monolith-designing-software-maximizes-developer-productivity, retrieved 2026-05-01. - “Scaling up the Prime Video audio/video monitoring service and reducing costs by 90%”, Amazon Prime Video Tech, March 2023,
https://www.primevideotech.com/video-streaming/scaling-up-the-prime-video-audio-video-monitoring-service-and-reducing-costs-by-90, retrieved 2026-05-01. - David Heinemeier Hansson, “The Majestic Monolith”, Signal v. Noise,
https://m.signalvnoise.com/the-majestic-monolith/, retrieved 2026-05-01. - Martin Fowler, “Microservices”,
https://martinfowler.com/articles/microservices.html, retrieved 2026-05-01. - Sam Newman, “Building Microservices, 2nd Edition”, O’Reilly, 2021. Standard reference for the trade-offs.
- Conway, M. E. “How Do Committees Invent?”, Datamation, 1968. The original Conway’s law paper.