Back to Software Engineering & Delivery Mastery Series

Part 27: Monolith vs Microservices Delivery Architecture

May 13, 2026 Wasil Zafar 44 min read

The most debated architectural decision in software engineering. This article cuts through the hype to examine monoliths, modular monoliths, and microservices through the lens of delivery — how each architecture shapes your pipelines, team boundaries, and ability to ship safely at speed.

Table of Contents

  1. Introduction
  2. The Monolith
  3. The Modular Monolith
  4. Microservices
  5. Distributed Systems Challenges
  6. Conway's Law
  7. Service Mesh
  8. Decomposition Strategies
  9. Delivery Implications
  10. When to Choose What
  11. Exercises
  12. Conclusion & Next Steps

Introduction — The Most Debated Architectural Decision

"Should we use microservices?" is the question that launched a thousand conference talks, blog posts, and architectural arguments. For nearly a decade, "microservices" was synonymous with "modern" and "monolith" was synonymous with "legacy." But the industry is waking up to a more nuanced reality: architecture is a tradeoff, not a progression.

This article examines monoliths, modular monoliths, and microservices through the lens that matters most for this series: delivery. How does each architecture affect your ability to build, test, deploy, and operate software? What does each demand from your CI/CD pipelines, team structure, and observability?

Key Insight: Architecture is not chosen in isolation — it is a reflection of your team size, organisational structure, product maturity, and deployment capabilities. Choosing microservices before you have CI/CD, observability, and team autonomy is like buying a Formula 1 car before learning to drive.

The Pendulum Swings Back

The industry has gone through a clear cycle:

  1. 2005-2012: Large monoliths dominate. Companies like Amazon, Netflix, and eBay struggle with deployment speed.
  2. 2012-2018: Microservices revolution. Netflix, Uber, and Spotify evangelise the approach. Everyone wants to "do microservices."
  3. 2018-2023: Reality hits. Teams discover the "distributed systems tax." Many find they traded monolith problems for worse distributed problems.
  4. 2023-present: Nuance prevails. "Modular monolith" emerges as a middle ground. Amazon Prime Video famously moves back from microservices to a monolith for one service, reducing costs by 90%.

The Monolith

A monolith is a single deployable unit containing all application functionality. One codebase, one build process, one deployment artifact. When you deploy, you deploy everything.

Types of Monoliths

Type Description Internal Structure Example
Big Ball of Mud No internal structure. Everything calls everything. Spaghetti code, no boundaries Legacy enterprise apps
Layered Monolith Traditional MVC or N-tier architecture Horizontal layers (UI, service, data) Most Spring Boot / Rails apps
Modular Monolith Well-defined internal modules with clear boundaries Vertical slices by domain Shopify, Basecamp

When Monoliths Win

Monoliths have significant advantages that are often overlooked in the microservices hype:

  • Simple deployment — One artifact, one pipeline, one deploy. No service coordination required.
  • Easy debugging — Stack traces show the full call path. No distributed tracing needed.
  • No network calls between components — In-process function calls are millions of times faster than HTTP/gRPC.
  • Simple transactions — ACID transactions across the entire application. No saga patterns needed.
  • Lower operational overhead — One service to monitor, one set of logs, one scaling strategy.
  • Faster development for small teams — No service contracts, no API versioning, no service discovery.
# Monolith deployment: one command
docker build -t myapp:v2.3.1 .
docker push registry.example.com/myapp:v2.3.1
kubectl set image deployment/myapp myapp=registry.example.com/myapp:v2.3.1

# That's it. One image, one deployment, one rollback target.
Case Study

Amazon Started as a Monolith

Amazon.com ran as a monolithic C++ application called "Obidos" for years. It handled product catalogue, shopping cart, checkout, recommendations — everything. The monolith only became a problem when Amazon grew to hundreds of developers and needed independent deployment. They decomposed into services because of team scale, not because monoliths are inherently bad. The lesson: start with a monolith, decompose when the pain of coordination exceeds the pain of distribution.

Amazon Monolith First Scale

The Modular Monolith

A modular monolith is a single deployable unit with well-defined internal module boundaries. Each module owns a specific domain, has a clear public API, and cannot access the internals of other modules. Think of it as microservices within a single process.

# Modular monolith structure
src/
├── modules/
│   ├── orders/
│   │   ├── api/          # Public interface (what other modules can call)
│   │   ├── domain/       # Business logic (private)
│   │   ├── persistence/  # Database access (private)
│   │   └── events/       # Domain events published
│   ├── payments/
│   │   ├── api/
│   │   ├── domain/
│   │   ├── persistence/
│   │   └── events/
│   ├── inventory/
│   │   ├── api/
│   │   ├── domain/
│   │   ├── persistence/
│   │   └── events/
│   └── shipping/
│       ├── api/
│       ├── domain/
│       ├── persistence/
│       └── events/
├── shared/               # Shared kernel (minimal)
└── main.py               # Single entry point

The key rules of a modular monolith:

  • Modules communicate only through public APIs — no reaching into another module's database tables or internal classes.
  • Each module owns its data — separate schemas or separate tables with no cross-module joins.
  • Modules publish domain events — async communication between modules via an in-process event bus.
  • Compilation/linting enforces boundaries — tools like ArchUnit (Java), Packwerk (Ruby), or custom lint rules prevent violations.

Shopify's Modular Monolith

Shopify is the most prominent example of a modular monolith at scale. Their Ruby on Rails monolith serves millions of merchants and processes billions in transactions. Rather than decomposing into microservices, they invested in Packwerk — a tool that enforces module boundaries at the code level.

Shopify's Philosophy: "We chose to modularise our monolith rather than decompose into microservices because we get the organisational benefits of boundaries (team ownership, independent development) without the operational costs of distribution (network failures, service discovery, distributed tracing)."

Microservices

Microservices are independently deployable services, each owning a specific business capability. Each service has its own codebase, its own deployment pipeline, its own data store, and can be written in different programming languages.

Benefits When Done Right

  • Independent deployment — Change one service without touching others. Deploy 50 times a day if needed.
  • Independent scaling — Scale the payment service to 100 instances while keeping the admin panel at 2.
  • Technology diversity — Use Python for ML services, Go for high-performance APIs, Node.js for real-time features.
  • Fault isolation — If the recommendation service crashes, checkout still works.
  • Team autonomy — Teams own services end-to-end. No coordinated releases needed.

The Distributed Systems Tax

Every benefit of microservices comes with a corresponding cost — the "distributed systems tax" you pay regardless of whether you need the benefit:

Benefit You Get Tax You Pay
Independent deployment API versioning, contract testing, backward compatibility
Independent scaling Service discovery, load balancing, connection pooling
Technology diversity Multiple build pipelines, varied expertise needed
Fault isolation Circuit breakers, retries, timeouts, bulkheads
Team autonomy Cross-service debugging, distributed tracing, data consistency

Distributed Systems Challenges

The Eight Fallacies of Distributed Computing

In 1994, Peter Deutsch identified eight assumptions that developers make about networks — all of which are false:

  1. The network is reliable — Packets get lost. Connections drop. Cables get cut.
  2. Latency is zero — Every network call adds milliseconds. 50 service calls = 50× the latency.
  3. Bandwidth is infinite — Serialisation overhead, payload sizes, and congestion are real.
  4. The network is secure — Every service-to-service call is an attack surface.
  5. Topology does not change — Services move, IPs change, instances scale up and down.
  6. There is one administrator — Different teams own different services with different policies.
  7. Transport cost is zero — Serialisation/deserialisation, TLS handshakes, DNS lookups all cost CPU.
  8. The network is homogeneous — Different protocols, versions, and configurations across services.

The Saga Pattern — Distributed Transactions

In a monolith, you wrap a business operation in a database transaction: either everything succeeds or everything rolls back. In microservices, this is impossible — each service owns its own database. The Saga pattern provides eventual consistency through a series of local transactions with compensating actions:

Saga Pattern: Order Processing
sequenceDiagram
    participant O as Order Service
    participant P as Payment Service
    participant I as Inventory Service
    participant S as Shipping Service
    
    O->>O: Create Order (PENDING)
    O->>P: Reserve Payment
    P-->>O: Payment Reserved
    O->>I: Reserve Inventory
    I-->>O: Inventory Reserved
    O->>S: Schedule Shipping
    S-->>O: Shipping Scheduled
    O->>O: Mark Order CONFIRMED
    
    Note over O,S: If any step fails...
    O->>S: Cancel Shipping (compensate)
    O->>I: Release Inventory (compensate)
    O->>P: Release Payment (compensate)
    O->>O: Mark Order FAILED
                            

Conway's Law

"Any organisation that designs a system will produce a design whose structure is a copy of the organisation's communication structure." — Melvin Conway, 1967

This is not just an observation — it is a force of nature in software engineering. If you have three teams, you will end up with three services (or three modules) regardless of what the architecture diagram says. Conway's Law has profound implications for architecture decisions:

  • Small team (5-8 people)? → A monolith matches your communication structure perfectly. You all talk to each other daily anyway.
  • Multiple teams (20+ people)? → Microservices align with team boundaries. Each team owns a service.
  • Inverse Conway Maneuver → Deliberately structure your teams to produce the architecture you want. Want three microservices? Create three teams.
Anti-Pattern: "Micro-team, Microservices" — A team of 4 engineers maintaining 12 microservices. Each engineer "owns" 3 services but cannot deeply understand any of them. Deployment coordination is constant. This is worse than a monolith in every way. One team should own at most 2-3 services.

Service Mesh

A service mesh is an infrastructure layer for service-to-service communication. It provides traffic management, security (mTLS), and observability without requiring changes to application code. The mesh works by deploying a sidecar proxy alongside each service instance.

Service Mesh Sidecar Pattern
flowchart LR
    subgraph Pod A
        A[Service A] --> PA[Proxy A]
    end
    subgraph Pod B
        PB[Proxy B] --> B[Service B]
    end
    subgraph Pod C
        PC[Proxy C] --> C[Service C]
    end
    subgraph Control Plane
        CP[Mesh Controller]
    end
    
    PA -->|mTLS| PB
    PA -->|mTLS| PC
    CP -->|Config| PA
    CP -->|Config| PB
    CP -->|Config| PC
                            

Major service mesh implementations:

Mesh Sidecar Key Features Complexity
Istio Envoy Full-featured, traffic management, canary deployments High
Linkerd linkerd2-proxy (Rust) Lightweight, fast, simple to operate Low
Consul Connect Envoy Multi-platform, service discovery built-in Medium
When You Need a Service Mesh: If you have fewer than 10 microservices, you probably do not need a service mesh. The overhead is not justified. A mesh becomes valuable at 20+ services when managing mTLS certificates, retry policies, and traffic routing manually becomes unsustainable.

Decomposition Strategies

When you decide to break a monolith apart, how you decompose matters enormously. Do it wrong and you get a "distributed monolith" — the worst of both worlds.

The Strangler Fig Pattern

Named after strangler fig trees that gradually envelop their host, this pattern lets you incrementally replace monolith functionality with new services:

Strangler Fig Decomposition
flowchart TD
    subgraph Phase 1
        LB1[Load Balancer] --> M1[Monolith
Handles Everything] end subgraph Phase 2 LB2[Load Balancer] --> M2[Monolith
Most Features] LB2 --> S1[New Service
Orders] end subgraph Phase 3 LB3[Load Balancer] --> M3[Monolith
Legacy Only] LB3 --> S2[Orders Service] LB3 --> S3[Payments Service] LB3 --> S4[Users Service] end

Key decomposition strategies:

  • Strangler Fig — Route traffic to new service for specific paths. Gradually move functionality until the monolith is empty.
  • Domain-Driven Decomposition — Identify bounded contexts using DDD. Each bounded context becomes a service candidate.
  • Event Storming — Workshop technique to discover domain events, aggregates, and bounded contexts. Excellent for finding natural service boundaries.
  • Database Seam — Find tables that are accessed by only one part of the codebase. Extract that code and those tables into a service.
Case Study

Segment's Return to the Monolith

Segment (now part of Twilio) famously moved from microservices back to a monolith in 2018. With 3 engineers maintaining 140+ microservices, the operational overhead was crushing. Each service needed its own CI pipeline, monitoring, on-call rotation, and dependency management. By consolidating back to a monolith, they reduced operational load by 80% and improved developer productivity. Their blog post "Goodbye Microservices: From 100s of Problem Children to 1 Superstar" became one of the most-shared engineering posts of the year.

Segment Monolith Return Operational Cost

Delivery Implications

Your architecture fundamentally shapes your delivery process. Here is a direct comparison:

Aspect Monolith Microservices
CI Pipeline One pipeline, one build N pipelines (one per service)
Build Time Grows with codebase (can get long) Small per service (but N builds total)
Testing Full integration test suite runs every deploy Contract tests + service-level tests + E2E
Deployment All-or-nothing (entire app) Independent per service
Rollback Rollback entire application Rollback individual service
Release Coordination Everyone coordinates on release day Teams release independently
API Versioning Not needed (in-process calls) Critical (breaking changes break callers)
Observability Simple (one set of logs/metrics) Complex (distributed tracing required)
Incident Debugging Single stack trace Trace across N services

When to Choose What

There is no universal "best" architecture. The right choice depends on your context:

Architecture Decision Flowchart
flowchart TD
    A[New Project?] --> B{Team Size?}
    B -->|1-8 engineers| C{Product Maturity?}
    B -->|8-30 engineers| D{Clear Domain Boundaries?}
    B -->|30+ engineers| E[Microservices likely appropriate]
    
    C -->|Early/MVP| F[Start with Monolith]
    C -->|Mature/Scaling| G[Modular Monolith]
    
    D -->|Yes| H{CI/CD + Observability mature?}
    D -->|No| G
    
    H -->|Yes| I[Consider Microservices]
    H -->|No| G
    
    F --> J[Evolve when pain appears]
    G --> K[Extract services only when justified]
                            
Choose This When
Monolith Small team, new product, unclear domain boundaries, speed of iteration is priority
Modular Monolith Growing team (8-20), clear domains, want team ownership without operational complexity
Microservices Large org (30+), mature CI/CD, strong observability, teams need independent deployment, different scaling requirements per component
Martin Fowler's Advice: "Almost all the successful microservice stories have started with a monolith that got too big and was broken up. Almost all the cases where I've heard of a system that was built as a microservice system from scratch, it has ended up in serious trouble."

Exercises

Exercise 1

Audit Your Current Architecture

Draw the architecture of a system you work with (or a well-known open-source project). Classify it: big ball of mud, layered monolith, modular monolith, or microservices. Identify: How many deployable units exist? How many teams contribute? What is the deployment frequency? Would a different architecture improve delivery speed?

Exercise 2

Design Module Boundaries

Take an e-commerce application (products, orders, payments, shipping, users, reviews). Design it as a modular monolith: define module boundaries, public APIs between modules, which module owns which database tables, and how modules communicate (sync calls vs domain events). Draw the dependency graph — are there circular dependencies?

Exercise 3

Apply Conway's Law

You have 4 teams of 5 engineers each. Map them to either: (a) a monolith with each team owning specific modules, or (b) microservices with each team owning 2-3 services. For each option, describe: the deployment process, how cross-cutting changes work, and what happens when an incident spans team boundaries.

Exercise 4

Plan a Strangler Fig Migration

You have a monolithic order management system that needs to extract the "payments" capability into its own service. Write a 6-step migration plan using the Strangler Fig pattern: what to extract first, how to route traffic during migration, how to handle the shared database, and how to verify correctness during the transition.

Conclusion & Next Steps

The monolith vs microservices debate is not about which is "better" — it is about which fits your context. A monolith is not legacy; microservices are not modern. Both are tools with tradeoffs. The modular monolith offers a compelling middle ground that gives you module boundaries and team ownership without the operational tax of distribution.

The most important takeaway: architecture should follow team structure and delivery capability. If you do not have CI/CD maturity, observability, and team autonomy, microservices will make everything harder, not easier. Build the delivery foundations first (Parts 1-26 of this series), then let your architecture evolve to match your growing capabilities.

In Part 28: Release Architecture & Multi-Environment Systems, we will explore how software moves through multiple environments — from developer laptops through staging to production — and how to design promotion pipelines, manage environment parity, and handle configuration across environments.