Back to System Design Concepts
Q15Question 15 of 16

Monolith vs Microservices

Key Concepts
  • A monolith is a single deployable application where all modules share one process and database : simple to build, test, and deploy.
  • Microservices decompose the system into independently deployable services, each owning its data and communicating over the network.
  • Start with a monolith for simplicity and speed; extract services when a specific scaling or deployment pain appears.
  • Microservices enable independent scaling, independent deployment, fault isolation, and team autonomy at scale.
  • The costs are real: no distributed transaction by default, network failures, operational complexity (discovery, logging, tracing).
  • Premature microservices on a small team trade simple in-process calls for distributed-system complexity before it is justified.
  • Use the saga pattern (compensating actions) instead of distributed transactions across microservices.

Definition

Start with a monolith for simplicity and speed; move to microservices when scale, independent deployment, and team autonomy outweigh the added distributed-system complexity.
  • Monolith : a single deployable application where all modules run in one process and share one codebase and database.
  • Microservices : independently deployable services, each owning its data and communicating over the network.
  • Saga pattern : a sequence of local transactions where each step has a compensating action that undoes it if a later step fails.
  • Service mesh : infrastructure layer (Istio, Linkerd) that handles service-to-service communication, retries, and observability.

A monolith is like running National Bank of India out of one big building where every department shares the same filing room. It is simple when the bank is small. Microservices are like giving each department its own building, staff, and records, connected by couriers. The autonomy helps when the bank is huge, but now you must manage couriers, many buildings, and the risk that a courier is delayed.

Understanding the Concepts

What is a monolith?

A monolith keeps all functionality in one process and usually one database. The advantages early on are significant: a single codebase is easy to understand, a single deployment is simple, calls between modules are fast in-process method calls, and a transaction can span the whole system.

JAVA
package com.bank.monolith;

import java.math.BigDecimal;
import org.springframework.transaction.annotation.Transactional;

public class MonolithTransferService {
    @Transactional
    public void transfer(String from, String to, BigDecimal amount) {
        accountModule.debit(from, amount);    // same process
        accountModule.credit(to, amount);      // same transaction
        auditModule.record(from, to, amount);  // all commit together
    }
}

What are microservices?

Microservices split the system by business capability, each independently deployable with its own database, communicating over the network.

JAVA
package com.bank.microservice;

import java.math.BigDecimal;

public class MicroserviceTransferService {
    public void transfer(String from, String to, BigDecimal amount) {
        accountClient.debit(from, amount);      // network call
        accountClient.credit(to, amount);        // no shared DB transaction
        eventBus.publish("transfer.completed"); // async audit
    }
}

When is a monolith better?

For a small team or early-stage product, the monolith's simplicity and speed are exactly what you want. You ship faster, test easier, and debug simpler. Premature microservices are a classic mistake.

When are microservices needed?

Microservices become the right answer at scale and with many teams. They enable independent scaling, independent deployment, fault isolation, and team autonomy.

What are the costs of microservices?

No distributed transaction across services by default, so you need sagas. Network calls fail and add latency. Data is spread across services, complicating queries. You need mature operations: service discovery, centralized logging, tracing, and CI/CD per service.

JAVA
package com.bank.saga;

import java.math.BigDecimal;

public class TransferSaga {
    public void execute(String from, String to, BigDecimal amount) {
        try {
            accountService.debit(from, amount);     // step 1
            accountService.credit(to, amount);       // step 2
        } catch (Exception e) {
            accountService.compensateDebit(from, amount); // undo step 1
            throw new SagaFailedException(e);
        }
    }
}

Start with a well-modularized monolith. When a specific scaling or deployment pain appears, extract that module as a service along a clear seam. This is safer than starting distributed from day one.

How do you handle transactions across microservices?

Use the saga pattern: each step has a compensating action. If the audit service is down, the saga rolls back the debit and credit. This replaces the single ACID transaction of the monolith.

What should a senior engineer know?

A senior engineer knows that a well-modularized monolith first, with services extracted along clear seams only when pain actually appears, is almost always wiser than starting distributed. They also know about the strangler fig pattern for gradual migration.

Visual Overview

DIAGRAM
  Monolith                          Microservices
  ┌─────────────────────┐          ┌──────────┐ ┌──────────┐
  │  Single Application │          │ Transfer │ │ Account  │
  │                     │          │ Service  │ │ Service  │
  │  ┌────────┐         │          │ (own DB) │ │ (own DB) │
  │  │Transfer│         │          └────┬─────┘ └────┬─────┘
  │  │Module  │         │               │ REST/Kafka  │
  │  └───┬────┘         │          ┌────▼─────┐      │
  │  ┌───▼────┐         │          │  Audit   │◄─────┘
  │  │Account │         │          │ Service  │
  │  │Module  │         │          │ (own DB) │
  │  └───┬────┘         │          └──────────┘
  │  ┌───▼────┐         │
  │  │ Audit  │         │          Benefits:
  │  │Module  │         │          + Independent scaling
  │  └────────┘         │          + Independent deployment
  │                     │          + Fault isolation
  │  ONE DB, ONE TX     │          + Team autonomy
  │  Simple, fast calls │
  │  No network issues  │          Costs:
  └─────────────────────┘          - Distributed transactions
                                   - Network failures
                                   - Operational complexity

  Migration: modular monolith -> extract along seams

Key Takeaway

A monolith is a single deployable application where all modules share one process and database, while microservices decompose into independently deployable services each owning its own data. I am pragmatic about the choice because both are correct in different circumstances.

A monolith's advantages early on are substantial: one codebase, one deployment, fast in-process calls, and single-database transactions. At early-stage National Bank of India, the monolith let transfers, accounts, and audit all commit atomically with almost no operational overhead.

Microservices become right at scale and with many teams. They allow independent scaling, independent deployment, fault isolation, and team autonomy. But the costs are real: no distributed transaction by default (use sagas), network failures (use circuit breakers), and mature operations with discovery, logging, and tracing.

The senior lesson is that a well-modularized monolith first, with services extracted along clear seams when specific pain appears, is almost always wiser than starting distributed before you are big enough to operate it.

Remember one sentence: start with a modular monolith for speed and simplicity, then extract microservices along clear seams only when scaling or deployment pain actually justifies the distributed-system complexity.

---

Share
Use ← → arrow keys to navigate