Monolith vs Microservices Architecture

The design tradeoffs between a unified codebase and a decentralized system of independent services.

BeginnerReliability & ScaleChapter: Reliability & Scalability10 min read

The Monolithic Architecture: All-in-One

A monolithic architecture organizes software so that the entire application, comprising the user interface, backend business logic, and database access layer, is compiled and deployed as a single, unified unit.

Inside a monolith, different domains (such as payments, users, and orders) communicate using fast in-memory function calls. The monolith usually connects to a single, shared relational database, allowing database queries to perform multi-table joins across distinct business domains.


Limitations of the Monolith

While monoliths are easy to develop, test, and deploy early on, they become difficult to manage at scale:

  • Scaling Bottlenecks: You cannot scale specific components independently. If the orders service is CPU-intensive, you must replicate the entire monolith container, which duplicates memory allocations for the rest of the application.
  • Team Deployment Collisions: As engineering teams grow, multiple developers modify the same codebase. Deployment pipelines slow down because testing the entire application takes hours, and a single bug in a minor component can crash the entire system.
  • Single Point of Failure: A memory leak or resource panic in one module takes down the entire application process, affecting all features.

The Microservices Alternative: Decentralization

A microservices architecture decomposes the application into small, independent services. Each service represents a distinct business capability (such as a billing service or search service) and operates as a standalone operating system process.

Monolithic Architecture Single Codebase / Process Orders Module Inventory Module In-Memory Function Call Shared Database Microservices Architecture Orders Service Process A HTTP/gRPC Call Inventory Service Process B Orders DB Inventory DB Decoupled Databases & Services

Key rules of microservices include:

  • Database Per Service: To ensure decoupling, a service must never read or write to another service's database directly. All access must flow through public API endpoints.
  • Independent Deployability: Teams can deploy updates to their service without coordinating with or redeploying the rest of the system.
  • Technology Heterogeneity: Services can use different programming languages or databases that are optimal for their specific tasks.

Network IPC: Replacing Memory Calls with Protocols

Because services are separated across network boundaries, modules can no longer communicate via standard in-memory programming calls. Instead, they use Inter-Process Communication (IPC) protocols:

  • REST over HTTP/1.1 or HTTP/2: Standard JSON-based requests, which are easy to implement but introduce significant text-parsing and network header overhead.
  • gRPC / Protocol Buffers: A binary protocol over HTTP/2 that enables high-performance, strongly typed RPC (Remote Procedure Call) patterns.
  • Asynchronous Message Brokers: Messaging platforms (such as RabbitMQ or Kafka) that process transactions asynchronously, improving decoupling.

The Hidden Costs: Operational Overhead

Microservices solve scaling and deployment issues, but they introduce significant operational challenges:

  • Distributed Transaction Complexity: Because databases are decoupled, you cannot run standard ACID transactions across services. Systems must use design patterns like the Saga Pattern (a sequence of local transactions with compensating rollback steps) to ensure consistency, which is complex to implement.
  • Network Latency: Nested service-to-service calls (e.g. Service A calls B, which calls C) accumulate network hop latency.
  • Infrastructure Overhead: You need sophisticated tooling like service discovery (to locate active node IP addresses) and distributed tracing (such as Jaeger or OpenTelemetry) to track performance across network boundaries.

Conway's Law: Code Follows Communication

The decision to choose a monolith or a microservice architecture is often organizational. Conway's Law states:

"Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations."

If you have a small startup team of 4 engineers, a monolith is optimal because they share context. If you split those 4 engineers across 5 microservices, they will spend most of their time maintaining complex integration APIs. Conversely, an enterprise with 500 developers split into independent product teams is well suited for microservices.


Migration Strategy: The Strangler Fig Pattern

To transition a legacy monolith to microservices, you should avoid a complete rebuild. Instead, engineers use the Strangler Fig Pattern.

This pattern places an API gateway or reverse proxy in front of the application. You intercept traffic going to the monolith, implement a single feature as a new microservice, and redirect that specific traffic path to the new microservice. Over time, more routes are routed to new microservices until the old monolith is completely deprecated.


Further Reading

Prerequisites

Code Examples

Core Literature References

Building Microservices: Designing Fine-Grained Systems

by Sam Newman — Chapter 1: Microservices, Chapter 4: Integration, pp. 1-22, 55-89

View source

Microservices: A Definition of This New Architectural Term

by Martin Fowler — Introduction to Microservices, pp. 1-15

View source