Oh My Learning

Monolithic vs Distributed Architecture

fundamentals/09 mid drafted · 6 min read

Fundamentals of Software Architecture (Richards & Ford, O'Reilly 2nd ed. 2025), Chapter 10

Prerequisites fundamentals/08

Monolithic vs Distributed Architecture#

TL;DR#

Software architecture styles are divided into two fundamental topologies: monolithic (single deployment unit) and distributed (multiple, networked deployment units). While distributed architectures offer unparalleled scalability, fault isolation, and deployment flexibility, they introduce massive operational complexity, performance overhead, and consistency challenges. An architect must treat distribution as a costly trade-off, reaching for it only when a monolith's physical limits are genuinely breached.

The idea#

A common mistake in modern software engineering is assuming that monolithic architectures are obsolete and that distributed microservices are the default "best practice." In reality, both topologies represent different points on a spectrum of trade-offs.

A monolithic architecture houses all business capabilities within a single, deployable runtime artifact. It uses fast, in-memory function calls for communication and typically relies on a single database for data storage and transaction management.

A distributed architecture splits business capabilities into separate, physical deployment units that communicate over a network (using protocols like HTTP, gRPC, or AMQP). Each unit typically manages its own private data store.

The core rule of software architecture is: Do not distribute unless you absolutely have to. Distribution is not a badge of honor; it is an expensive solution to hard physical constraints (like extreme scale, organizational scale, or strict availability requirements). If a monolith can handle your traffic, team size, and reliability targets, it is almost always the superior choice due to its simplicity.

How it works#

Let's compare monolithic and distributed topologies across key architectural dimensions.

Monolithic Topology (In-Memory Communication)
+-------------------------------------------------+
|               Monolithic Application            |
|  [Component A] ---> [Component B] ---> [Comp C] |
+-------------------------------------------------+
                         v
                [Shared Database]

Distributed Topology (Network Communication)
+---------------+       Network       +---------------+
| Deployment A  | --(REST/gRPC/MQ)--> | Deployment B  |
| [Component A] |                     | [Component B] |
+---------------+                     +---------------+
        v                                     v
  [Database A]                          [Database B]

Comparative Analysis Matrix#

Dimension Monolithic Topology Distributed Topology
Development Complexity Low: Single codebase, simple debugging, atomic transactions. High: Network code, eventual consistency, distributed debugging.
Scalability Vertical / Coarse Horizontal: Must scale the entire monolith, even if only one component is hot. Fine-Grained Horizontal: Scale only the specific service under load.
Deployability Simple but Risky: One artifact to build and deploy, but a single bug can take down the whole app. Complex but Flexible: Services deploy independently, lowering blast radius.
Testability High: Simple to run end-to-end integration tests locally. Low: Requires mocking dependencies or spin-up of complex environments.
Performance Fast: Sub-microsecond, in-memory method calls. Slower: Millisecond-range network calls with serialization/deserialization overhead.
Consistency Strong (ACID): Local transactions guarantee immediate consistency. Eventual: Requires complex saga patterns and compensating actions.

Worked Example: Scaling the Ticket Catalog & Order Processing#

Consider an online ticketing platform (like Ticketmaster) during a major concert release. The system has two main flows: 1. CatalogBrowsing: Users search and view available events. This flow generates high read volume (95% of traffic). 2. OrderCheckout: Users purchase tickets. This flow generates low write volume (5% of traffic) but requires absolute ACID compliance and zero-concurrency errors (no double-selling).

Implementation 1: The Monolithic Approach#

Both components reside in a single codebase with a single SQL database.

  • Under Peak Load:
  • Millions of fans search for tickets, flooding CatalogBrowsing.
  • The application server runs out of memory/threads, crashing the entire monolith.
  • Users who were in the middle of checking out lose their carts.
  • To scale, we must spin up 50 instances of the entire monolith. This is highly inefficient because the OrderCheckout code is replicated 50 times, consuming expensive database connections and memory despite having low write traffic.

Implementation 2: The Distributed Approach#

We split the system into two distinct architectural quanta: 1. CatalogService (Optimized for read-scalability: uses a distributed cache and NoSQL database). 2. CheckoutService (Optimized for transactionality: uses a relational database with strict locking).

                      +-----------------------------+
                      |         API Gateway         |
                      +-----------------------------+
                        /                         \
            (read)     /                           \ (write)
                      v                             v
             [CatalogService]             [CheckoutService]
              - High Scale NoSQL           - ACID relational DB
              - 100 Instances              - 3 Instances
  • Under Peak Load:
  • Millions of read queries hit CatalogService. We scale CatalogService to 100 instances.
  • CheckoutService remains isolated on 3 highly tuned instances.
  • If CatalogService is overwhelmed and experiences latency, the CheckoutService is unaffected. Users can still complete purchases for tickets already in their carts.
  • Trade-off: We now must synchronize inventory changes from CheckoutService back to CatalogService asynchronously. This means catalog search results might show a ticket as "available" for a few seconds after it has been sold (eventual consistency).

Pros#

  • Zero Network Fallacies: No remote call failures, packet loss, or network latency (see fundamentals/10).
  • Simplicity of Code: Standard programming language structures (interfaces, classes, direct dependencies) work without complex network wrappers.
  • Easy Transaction Management: Database transactions (BEGIN, COMMIT, ROLLBACK) manage state transitions safely and atomically.

Cons#

  • Hard Scaling Boundaries: If one module has extreme memory or CPU requirements, the entire monolith must run on oversized, expensive hardware.
  • Loose Fault Boundaries: A null pointer exception or memory leak in a minor background task can crash the entire process.
  • Deployment Coordination: All teams must coordinate releases, leading to long release cycles and complex merge conflicts.

Alternatives#

  • The Modular Monolith: A hybrid approach where logical components are strictly separated and decoupled inside a single codebase and deployment unit (see fundamentals/12). This yields the simplicity of a monolith with the organizational decoupling of a distributed system.
  • Service-Based Architecture: A distributed topology with coarse-grained services (typically 3 to 8) sharing a single database. This offers a middle ground between monolithic simplicity and microservices distribution.

When to use it#

  • Early-Stage Startups: When validating a business model and requirements are changing rapidly. Speed of iteration is paramount.
  • Low-to-Medium Scale Applications: When traffic can be easily handled by vertical scaling (adding CPU/RAM) or simple active-passive replication.
  • Small Engineering Teams: Teams of fewer than 20 developers where organizational coordination is not a bottleneck.

When NOT to use it#

  • Massive, High-Scale Systems: Where scale constraints require different database technologies and independent horizontal scaling.
  • Vast Engineering Organizations: When hundreds of developers across multiple departments need to release features independently without step-locked coordination.
  • Heterogeneous Tech Stacks: When different components require different languages or runtime environments (e.g., Python for machine learning models, Go for high-throughput APIs).

Key takeaways / mental model#

Distribution is an architectural trade-off, not a default target. Monoliths are simple, fast, and easy to reason about, but they suffer from coarse-grained scaling and wide blast radiuses. Distributed systems solve scaling and deployment bottlenecks but introduce network failure modes, complex consistency issues, and high operational costs. Always start with a monolith; only distribute when the physical boundaries of a single deployment unit are no longer sufficient to meet your requirements.

Self-check questions#

  1. Why does migrating from a monolith to microservices often degrade performance?
  2. What are the key organizational triggers (Conway's Law) that justify moving to a distributed architecture?
  3. How does transaction management differ between monolithic and distributed topologies?

References#

  • Fundamentals of Software Architecture (Richards & Ford, O'Reilly 2nd ed. 2025), Chapter 10: Monolithic vs. Distributed Architectures
  • Cross-subject prerequisites: [fundamentals/08]
  • Cross-subject connections: [hard-parts/04], [system-design/02]