System DesignAdvanced 15 min Lesson 31 of 42

Microservices

Independent services with their own data and deployments: the real benefits, the considerable costs, and when the trade is worth making.

System Design · Lesson 31 of 42
0/42 done(0%)

What is it? #

Microservices split a system into separately deployable services, each owning its data and communicating over the network.

The benefit is independence. Teams deploy on their own schedule, choose their own runtime, and scale their service to its own needs.

The cost is that you have converted function calls into network calls. Every call can now be slow, fail, or partially succeed, and you no longer have transactions across services.

The honest guideline: microservices solve organisational problems more than technical ones. If one team can deploy the whole system comfortably, you are paying the cost without receiving the benefit.

Think of it like this #

Splitting one restaurant into several specialist kitchens in different buildings, each with its own supplies and staff.

Each kitchen can be upgraded without closing the others. But an order spanning three kitchens now needs couriers, tracking and a plan for what happens when one of them is closed.

Simple example #

An e-commerce platform with separate services for catalogue, orders, payments and notifications. Placing an order spans three of them, which means the transaction from the monolith lesson no longer exists.

Code #

TEXT
                        ┌─────────────┐
      clients ─────────▶│ API gateway │
                        └──────┬──────┘
            ┌──────────────────┼──────────────────┐
            ▼                  ▼                  ▼
      ┌───────────┐      ┌──────────┐      ┌────────────┐
      │ catalogue │      │  orders  │      │  payments  │
      │  own DB   │      │  own DB  │      │   own DB   │
      └───────────┘      └────┬─────┘      └────────────┘
                              │ events
                        ┌─────▼──────────┐
                        │ notifications  │
                        └────────────────┘
PYTHON
# The transaction is gone. A saga coordinates with compensating actions.
def place_order(cart, user):
    order = orders_db.create(user_id=user.id, lines=cart.lines, status="pending")

    try:
        reservation = inventory_client.reserve(cart.lines, timeout=2)
    except Exception:
        orders_db.update(order.id, status="failed")
        raise OrderFailed("could not reserve stock")

    try:
        payment = payment_client.charge(user.id, order.total,
                                        idempotency_key=f"order-{order.id}",
                                        timeout=5)
    except Exception:
        inventory_client.release(reservation.id)      # compensating action
        orders_db.update(order.id, status="failed")
        raise OrderFailed("payment failed")

    orders_db.update(order.id, status="paid", payment_id=payment.id)
    events.publish("order.placed", {"order_id": order.id, "email": user.email})
    return order

# Every step needs a timeout, an idempotency key, and an undo path.
# The monolith version of this was one transaction block.
TEXT
What you gain                     What you pay

independent deployment            network latency and partial failure
independent scaling               no cross-service transactions
technology choice per service     distributed debugging and tracing
smaller codebases per team        data duplication and eventual consistency
fault isolation, if done well     many pipelines, environments and dashboards
clear team ownership              versioning between service interfaces

How it works #

Each service owns its database. That is the defining rule: if two services share a database, they are one service with extra network hops, because neither can change its schema independently.

The saga replaces the transaction. Each step is durable and each has a compensating action to undo it. Reserving stock is undone by releasing it; a charge is undone by a refund.

Notice how much machinery each call needs: a timeout, an idempotency key so retries are safe, and an explicit failure path. In the monolith this was one transaction block, and the database guaranteed correctness.

Consistency becomes eventual. Between reserving stock and confirming payment, the system is in an intermediate state that must be represented and recoverable — which is why the order has a pending status at all.

Events handle the non-essential follow-ups, which keeps the synchronous path short. Notifications does not need to be available for an order to succeed.

Debugging requires distributed tracing, because one user action now produces work in several services with separate logs. A correlation ID propagated in headers is the minimum.

Real-world use #

Microservices work well in large organisations where independent deployment is the binding constraint. When fifty engineers share one pipeline, the coordination cost is real and services relieve it.

They work poorly when adopted for perceived modernity at small scale. A five-person team running twelve services spends its time on infrastructure rather than product.

Boundaries should follow business capabilities and team ownership. Services split by technical layer — a "database service", a "validation service" — produce chatty systems where every request crosses several hops.

The infrastructure requirement is substantial: service discovery, centralised logging, distributed tracing, per-service pipelines, contract testing and a deployment platform. All of it must exist before the first service is useful.

A widely repeated lesson from teams who have done it: start with a well-structured monolith, extract services when a concrete problem demands it, and expect the first extraction to teach you where the boundaries really are.

Common mistakes #

  • Adopting microservices for a team small enough to deploy a monolith comfortably.
  • Sharing one database between services, which removes the independence entirely.
  • Splitting by technical layer instead of business capability.
  • No distributed tracing, making cross-service debugging nearly impossible.
  • Assuming a network call behaves like a function call — no timeouts, no retries, no compensation.

Practice #

Take the order flow from the monolith lesson and rewrite it as a saga across three services. For each step, specify the timeout, the idempotency key and the compensating action. Then write down what the user sees if the payment service is unreachable.

Quick quiz

  1. 1. What is the defining rule of microservices?

  2. 2. What replaces a database transaction across services?

  3. 3. What problem do microservices primarily solve?

  4. 4. Why is splitting by technical layer a poor boundary?

  5. 5. What is required before the first service is genuinely useful?

Summary

  • Microservices are independently deployable services that own their data.
  • They trade function calls for network calls, with all that implies.
  • Transactions become sagas with compensating actions.
  • They solve organisational problems more than technical ones.
  • Start with a modular monolith and extract for a concrete reason.