Guide

A monolith is a single deployable unit: one codebase, one build, usually one database. A microservice architecture splits the system into services that deploy independently. Each service owns one business capability and its own data, and services talk over the network. Between the two sits the modular monolith: one deployable unit with strict internal boundaries, so modules talk only through public interfaces.

Concept

Every boundary is either a function call inside one process or a network call between processes. A network call takes milliseconds, can fail, and needs a plan for failure. That difference drives most of the trade-offs below.

The big idea

The trade-offs are honest, and none of them is free:

MonolithModular monolithMicroservices
DeployOne artifact, everyone ships togetherOne artifactEach service independently
Calls between partsIn-process, nanosecondsIn-processNetwork, about 0.5 to 5 ms, can fail
TransactionsLocal ACID across everythingLocal ACIDDistributed: sagas and eventual consistency
ScalingScale the whole thingScale the whole thingScale hot services on their own
Team autonomyLow at scale: merge conflicts, release trainsMedium: ownership by moduleHigh: teams own services end to end
Operational costLowLowHigh: discovery, tracing, many pipelines
Changing boundariesEasy: move codeEasy-ishHard: boundaries become network APIs

The last row hides the most important asymmetry. In a monolith, a wrong boundary is a refactor. In microservices, it is a migration. Moving a function between modules takes an afternoon. Moving a responsibility between two services needs a new API, a data migration between databases, and a coordinated rollout across two teams.

Two companies, two answers

Shopify runs one of the largest Rails codebases in the world. In its 2019 write-up, it was a few million lines of code with hundreds of developers. Shopify did not split it into services. They restructured the monolith into components aligned with business domains, with public interfaces between them. They built a tool, Packwerk, to detect code that reaches across component boundaries. They kept in-process calls and one deployment, and got much of the ownership clarity that people usually look for in microservices.

Segment ran a separate service and queue for each destination integration. That grew to more than 140 services. Each service had its own copy of shared libraries, and the copies drifted apart. Operational load rose with every integration, and one slow destination could back up its queue. In 2018 Segment described consolidating into a single service. Splitting by destination multiplied identical services, and the copies cost more than the isolation they bought.

Shopify's talk on decomposing a huge Rails app explains the approach in detail, and Segment's write-up covers the consolidation.

Common mistakes

  • Choosing microservices for a team of eight. You pay the operational cost without the organizational gain.
  • Treating the modular monolith as a half-measure. It keeps the one-deploy benefits and gives clear module ownership.
  • Ignoring how hard boundaries are to move. A wrong service boundary can cost a quarter to fix.

Check yourself

  1. Why is a wrong boundary expensive in microservices but cheap in a monolith?

    Moving code between modules is a refactor. Moving a responsibility between services needs a new API, a data migration and a coordinated rollout.

  2. What does a modular monolith keep from a monolith, and what does it add?

    It keeps one deployable unit and in-process calls. It adds enforced module boundaries that the code must respect.

Key takeaways

  • Monolith, modular monolith and microservices are points on a spectrum of boundary strength.
  • Shopify kept one deployment and enforced boundaries inside it. Segment merged services that had grown identical.
  • The right choice depends on team size, scaling needs and operational maturity.

Further learning

Next: Conway's law and team shape, which explains why organization structure should shape the boundaries.