35 min read Architecture & Decomposition Part 3 · Lesson 9 (9 / 12)
Course 11: Part 3 — Asynchronous & Distributed Systems

Lesson 11.9 — Monolith, Modular Monolith & Microservices: How to Decompose an Application

Should your system be engineered as a single unified deployable unit, or decomposed into dozens of autonomous services? Why the monolith is not an anti-pattern, how the Modular Monolith provides the golden middle ground, and the hidden taxes of microservices.

Key Learning Objectives

The Strength of the Monolith: Why monoliths remain the ideal default choice and identifying the exact breaking points when they begin to strain.
Modular Monolith as the Golden Mean: Preserving single-process deployment simplicity while enforcing domain boundaries and encapsulation.
Microservices & The Distributed Tax: Independent scaling and release cadence vs. network latency, distributed tracing, and Conway's Law.
Pragmatic Evolution Roadmap: Database-per-service vs. shared databases, avoiding the "Distributed Monolith" catastrophe, and gradual migration.
1

The Fundamental Architectural Dilemma

In previous lessons, we scaled web servers, tuned databases, configured caches, and integrated asynchronous message queues. Now comes one of the most consequential architectural questions in software engineering:

The Core Architectural Question:
"Should we build our system as a single, unified deployable application, or should we decompose it into dozens of independent services?"

In answering this question, the industry revolves around three primary architectural models:

1. Monolith

All business capabilities and domains are packaged and deployed together as a single executable artifact.

2. Modular Monolith

Deployed as a single executable artifact, but internally structured with strict domain boundaries and encapsulation.

3. Microservices

Autonomous, independently deployable services that communicate across network boundaries via APIs or events.

A widespread misconception among junior engineers is that "Monoliths are legacy and bad, while Microservices are modern and good." In reality, tech giants like Shopify, Basecamp, Stack Overflow, and GitHub powered billion-dollar platforms on monolithic architectures.

2

What is a Monolith, and Why is it NOT an Anti-Pattern?

Consider building an e-commerce platform. It requires several core business capabilities:

  • User Management: Authentication, authorization, and customer profiles.
  • Product Catalog: Products, categories, search, and inventory views.
  • Cart & Checkout: Item aggregation, discount vouchers, and taxes.
  • Order Management: Order placement, state transitions, and history.
  • Payment Integration: Third-party payment gateways and transaction ledgers.
  • Notifications: SMS confirmations and transactional emails.

In a Monolith, all of these capabilities exist inside a single process:

// Monolithic Application Architecture
[ Global Users (Web / Mobile Apps) ]
↓ (HTTPS / REST API)
[ Reverse Proxy / Load Balancer ]
↓
=== Single Deployable Process (Monolith) ===
User Module
Catalog Module
Order Module
Payment Module
// Inter-module communication = Fast In-Memory Function Calls (< 0.1ms)
↓
[( Relational Database — PostgreSQL / MySQL )]
Key Advantages of Monoliths:
  • Development Velocity & Onboarding: Single repository. A new developer clones the repository and runs the entire stack locally with a single command.
  • Zero Network Overhead: Calling another module is an in-memory function call executed in nanoseconds. No JSON serialization, HTTP handshakes, or network timeouts.
  • ACID Guarantees Out of the Box: Multi-table transactions execute atomically within a single SQL transaction block (`BEGIN ... COMMIT`). Zero two-phase commit or saga coordination needed.
  • Trivial Operations: One CI/CD pipeline, one Docker container, one health check endpoint. Operational complexity remains minimal.
  • Effortless Refactoring & Debugging: IDEs handle cross-module renames with complete type safety. A single stack trace pinpoints root causes immediately.
3

When and Why do Monoliths Strain?

Monoliths do not fail because of software architecture flaws; they strain when organizational and traffic scale cross specific thresholds:

1. The "Big Ball of Mud" & Tangled Dependencies

Without strict architectural discipline, boundaries erode over time. The Order module queries the User table directly, while the Inventory module mutates Payment objects, resulting in unmaintainable spaghetti code.

2. Organizational Coupling & Deployment Blockers

When 20 different squads commit to the same repo, releases become high-risk events. A single failing test in a minor reporting module blocks the deployment of a critical payment security patch.

3. Skewed Resource Scaling Economics

Product browsing receives 15,000 RPS while Payment processing receives only 50 RPS. To scale the browsing workload, you must replicate 40 large instances of the entire monolith, unnecessarily duplicating memory-heavy payment modules.

4. Massive Blast Radius

A memory leak or unhandled exception in an auxiliary feature (such as PDF invoice generation) can exhaust the process heap and crash the entire monolith, bringing down core checkout flows.

4

Modular Monolith — The Golden Middle Ground

When a monolith begins to suffer from tangled code, jumping directly into a fleet of 25 microservices is often an overreaction that introduces staggering operational overhead.

The most pragmatic modern architecture is the Modular Monolith:

// The Modular Monolith Structure
[ Unified Application Gateway / Router ]
↓
=== Single Process with Enforced Module Boundaries ===
📦 User Module
• Public API Interface
• Internal Domain Logic (Private)
• User Tables (Private Schema)
📦 Order Module
• Public API Interface
• Internal Order Logic (Private)
• Order Tables (Private Schema)
📦 Payment Module
• Public API Interface
• Internal Payment Logic (Private)
• Payment Tables (Private Schema)
// Rule: No direct table cross-queries! Modules communicate strictly via exported Public Interfaces.
↓
[( Single PostgreSQL Database with Schema Isolation: `users.*`, `orders.*`, `payments.*` )]

In a Modular Monolith, the application still deploys as a single executable artifact. However, Module encapsulation is strictly enforced by compiler or linter tools (e.g., ArchUnit in Java, internal packages in Go, or project references in TypeScript/.NET). The Order module cannot directly import Payment internal classes or query Payment tables.

Why Modular Monoliths Excel:

You retain the operational ease of a single container deployment, instantaneous local test execution, and zero distributed system latency taxes. Yet, because boundaries are clean, extracting a module into an independent microservice later takes days rather than months.

5

Microservices Architecture — When and Why?

When an organization scales to hundreds of engineers organized into autonomous squads, single-process deployment coordination becomes an organizational bottleneck.

In a Microservices Architecture, the application is decomposed into discrete, independently deployable services organized around distinct Business Capabilities (Bounded Contexts).

// Microservices Architecture Topology
[ Public Client Apps (Web / iOS / Android) ]
↓ (HTTPS)
[ API Gateway (Auth, Routing, Rate Limiting) ]
↓ (Internal gRPC / REST)
User Service Go / gRPC · 5 Pods
[( User DB )]
Catalog Service Node.js · 25 Pods
[( Catalog DB + Redis )]
Payment Service Java / Spring · 3 Pods
[( Payment DB )]
↕ (Asynchronous Event Bus: Kafka / RabbitMQ)
[ Notification Service & Analytics Workers ]

Core Architectural Benefits

1. Independent Elastic Scaling

During a viral sale, catalog browsing traffic increases 30x. You independently scale the Catalog Service from 5 to 50 lightweight pods without spinning up unnecessary instances of the Payment or Notification services.

2. Independent Deployment Cadence

The Payment squad can release security patches and provider integrations 15 times a day without waiting for the next release train of the global platform.

3. Blast Radius Isolation

If the recommendation engine crashes under heavy load, users can still search products, add them to carts, and execute checkout transactions.

4. Polyglot Technology Stacks

Machine learning services utilize Python, ultra-low latency payment services use Go, and frontend aggregation layers use Node.js.

6

The Hidden "Tax" of Microservices

Microservices do not eliminate complexity; they relocate complexity from the code tier to the network and operations tiers. As Martin Fowler famously stated: "First Rule of Distributed Systems: Don't distribute your system unless you really have to."

The Distributed Taxes You Must Pay:
  • 1. Network Latency & Fallibility: In-memory calls execute in < 0.1ms. Microservice HTTP/gRPC hops incur 15–80ms of network overhead each, introducing timeouts, retries, and dropped packets.
  • 2. Database-per-Service & The Death of SQL Joins: When each service owns its datastore, you cannot perform multi-table relational joins across domains. Aggregation requires application-level data stitching or distributed Sagas.
  • 3. Operational Burden: Managing 30 microservices demands 30 CI/CD pipelines, Kubernetes cluster orchestration, Service Meshes (Istio/Linkerd), and automated secret management.
  • 4. Observability Overhead: Troubleshooting a single user failure requires distributed tracing (OpenTelemetry/Jaeger), structured log correlation (`trace_id`), and centralized telemetry.
  • 5. Conway's Law: "Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations." If you lack autonomous squads, microservices will cripple your delivery speed.

The Catastrophic Anti-Pattern: Distributed Monolith

The worst architectural outcome occurs when an engineering team splits code into multiple microservices but retains synchronous, tightly coupled request chains:

// The Distributed Monolith Anti-Pattern
[ Order Service ] ──(Sync HTTP)──> [ Inventory Service ] ──(Sync HTTP)──> [ Payment Service ] ──(Sync HTTP)──> [ Fraud Service ]
Result: All the drawbacks of monoliths + all the operational failure modes and latency penalties of distributed systems!
7

Architectural Comparison: Monolith vs. Modular Monolith vs. Microservices

Dimension Traditional Monolith Modular Monolith Microservices
Deployment Artifact Single Executable Single Executable Multiple Containers/Pods
Inter-Module Communication In-Memory Function Calls (< 0.1ms) Enforced Interfaces / Events Network Calls (HTTP/gRPC/Kafka)
Scaling Granularity Entire Application Fleet Entire Application Fleet Autonomous Per-Service Scaling
Database Architecture Single Shared Database Single DB, Isolated Schemas Strict Database-per-Service
Transactional Integrity Standard Relational ACID Standard Relational ACID Eventual Consistency / Saga
Team Size Alignment 1 to 10 Engineers 10 to 50 Engineers 50+ Engineers, Multiple Squads
Infrastructure Overhead Minimal Minimal Substantial (K8s, API Gateways)
8

The Pragmatic Evolutionary Roadmap

Successful software organizations adhere to a fundamental engineering maxim: "Earn your architecture."

Stage 1
Simple Monolith (MVP & Early Traction)

One application codebase, one relational database. Maximize iteration velocity to discover product-market fit.

Stage 2
Modular Monolith + Caching & Async Workers

Enforce strict domain boundaries within the monolith. Offload slow operations to asynchronous worker queues (Celery/BullMQ) and add Redis caching.

Stage 3
Selective Extraction (The Strangler Fig Pattern)

Do not rewrite the whole system! Extract only modules that require independent scaling or specialized security isolation (e.g., Payment Gateway or Video Processing) into microservices.

Stage 4
Event-Driven Microservices (Enterprise Scale)

Multiple autonomous engineering squads deploying independent services connected asynchronously via Apache Kafka and monitored with distributed tracing.

Interactive Architecture & Scaling Lab

Experiment with inter-service latency, scaling economics, and Conway's Law decision matrices

Concept Check Quiz

Test your comprehension of architectural trade-offs and decomposition

Question 1

Why is a "Modular Monolith" often strongly recommended over Microservices for a 15-person engineering team?

Question 2

Why is a deep synchronous call chain across microservices (A → B → C → D) referred to as a "Distributed Monolith"?

Question 3

What critical danger arises when multiple autonomous microservices directly access the same shared database tables?

Next Step: Part 4 · Lesson 11.10

Cloud & DevOps: From Software Code to Production Systems

Our code and architecture are designed! But where do these services actually run in production? How do Docker, Kubernetes, CI/CD pipelines, autoscaling, and telemetry work together? Welcome to the grand opening of Part 4!