AT3 — Simplicity vs Flexibility: The Monolith Decision You Will Make Twice

A team of four engineers rewrote their monolith into 12 microservices over 18 months. Deployment complexity tripled. On-call load doubled. Feature velocity halved. Two years later, they merged nine of the twelve back together.

That story is not unusual. It plays out at companies of every size. The architecture that looked like progress becomes the system nobody wants to touch. The engineers who built it are proud of the technical purity. The engineers who maintain it are exhausted.

AT3 is the tradeoff between simplicity and flexibility. A monolith is simple to understand, deploy, and debug. Microservices are independently deployable and scalable. Choosing one means giving up something real in the other direction.

What a Monolith Actually Gives You

A monolith is one deployable unit. One process, one codebase, one database. When something goes wrong, you look at one set of logs. When you trace a request, you follow a call stack. When you run tests, you run one test suite.

MONOLITH

  ┌──────────────────────────────────┐
  │  Users  │  Orders  │  Payments  │
  │─────────┼──────────┼────────────│
  │         Shared DB                │
  └──────────────────────────────────┘
          │
       one deploy

Function calls are cheap. In a monolith, the order service calls the payment service with a function invocation. No network, no serialisation, no timeout. No contract to version. The payment record and the order record update in the same transaction.

This is not a consolation prize. It is a genuine advantage. Most systems fail at the boundaries between components. A monolith has fewer boundaries.

What Microservices Actually Cost

Microservices replace function calls with network calls. Each network call can fail, time out, or return a stale response. You need circuit breakers, retries, and backoff logic for code that used to be a function call.

MICROSERVICES (same three domains)

  ┌──────────┐    ┌──────────┐    ┌──────────┐
  │  Users   │    │  Orders  │    │ Payments │
  │  svc     │───▶│  svc     │───▶│  svc     │
  └──────────┘    └──────────┘    └──────────┘
       │               │               │
    DB (users)      DB (orders)    DB (payments)
       │               │               │
  deploy x1       deploy x2       deploy x3

  6 deploys, 3 databases, n² possible failure edges

Distributed tracing replaces stack traces. A request spanning three services requires correlation IDs, a tracing backend, and someone who can read a flame graph. That overhead did not exist in the monolith.

Transactions disappear. If the order service writes an order and the payment service then fails, you need a saga, a compensating transaction, or an eventual consistency strategy. None of those are simple.

Service contracts become breaking points. One team renames a field in their API response. Another team's service breaks silently. In a monolith, the compiler tells you. In microservices, production tells you.

Conway's Law Is the Real Driver

Melvin Conway observed in 1967 that organisations design systems mirroring their communication structures. A team with one shared codebase produces a monolith. Three teams with separate ownership produce three services.

This is structural pressure, not coincidence. A monolith forces all engineers to coordinate changes in one place. That is fast when the team is small. It becomes a bottleneck when eight teams are stepping on each other.

Microservices solve a coordination problem. Independent teams can release independently. They can choose their own deployment cadence. They can scale their service without asking permission from another team.

The question to ask is not "monolith or microservices?" The question is: how many teams do I have, and do they need independent deployment?

When Each Side Wins

A monolith wins when the team is small, the domain is unclear, and speed matters. You cannot draw clean service boundaries for a product you do not fully understand yet. Premature decomposition creates services with the wrong shape. Merging them later is expensive.

Microservices win when multiple teams need independent release cycles, when services have dramatically different scaling requirements, and when boundaries are stable and well-understood.

Condition Monolith Microservices
Team size 1–8 Wins Overkill
Unclear domain boundaries Wins Dangerous
Different scaling per service Difficult Wins
Multiple independent teams Bottleneck Wins
Fast iteration needed Wins Slows you

The Modular Monolith

The middle ground is underused. A modular monolith is a single deployable unit with strict internal boundaries. Each module has its own namespace, its own database tables, and a defined public interface. Modules cannot reach into each other's internals.

This gives you deployment simplicity with ownership clarity. When the day comes to extract a module into a separate service, the boundaries are already drawn. The migration becomes a deployment decision, not an architectural redesign.

Many teams skip this step. They go directly from a tangled monolith to microservices, carrying the tangled dependencies with them. The result is a distributed monolith: all the complexity of microservices, none of the independence.

The Decision You Will Make Twice

The first decision is easy. Build the monolith. Move fast. Prove the product works.

The second decision is harder. The team has grown. Some services carry heavy load. Independent deployment is starting to matter. Now you decide where to draw the first boundary — and whether the team has the operational maturity to run distributed systems.

Most teams make the second decision too early. They pay the distributed systems tax before the organisational pressure is real. They add the complexity before they earn the benefit.

The test is simple: can each candidate service be owned by a team that deploys it independently? If the answer is no, the time is not right.

Microservices solve an organisational problem. If you do not have the organisation, you just have the complexity.