KAHIBARO
Discord Login Register

25.3 Microservices

Why Microservices Exist

Monoliths, modular monoliths, and microservices all aim to solve the same problem: build and run a useful application. Microservices appear when a single deployable application starts to become too big, too slow to change, or too hard to understand.

Common pain points that push teams toward microservices:

Microservices try to solve these issues by splitting the system into many small, independently deployable services. Each service owns a specific part of the domain, for example:

Each service:

Microservices are not automatically “better.”
They introduce network complexity, data consistency issues, and operational overhead. Only consider them when a simpler architecture, such as a modular monolith, is not enough.

Characteristics of Microservices

Independent deployability

The core property of microservices is independent deployability.

Each microservice:

Example:

This is different from a monolith, where all modules compile into one application and must be deployed together.

Service boundaries around business capabilities

Microservices are usually organized around business capabilities, not technical layers.

Examples of business capabilities:

CapabilityPossible Microservice
User accountsuser-service
Product catalogcatalog-service
Ordersorder-service
Paymentspayment-service
Shippingshipping-service

Each service:

Technology and data independence

In a microservices system:

This is known as polyglot persistence.

Rule: “Database per service”
Each microservice should have its own database that is not shared with other services. External access to another service’s database is forbidden. Communication must go through service APIs or events, not direct DB queries.

Small and focused services

There is no strict size limit, but typical guidelines:

Examples:

Example: Microservices in an E‑Commerce System

Consider an e-commerce backend. In a monolithic design, you might have:

In a microservices design, you might have:

ServiceResponsibilitiesOwn Database Example
user-serviceUser accounts, profiles, authusers_db (PostgreSQL)
catalog-serviceProducts, categories, search indexingcatalog_db (PostgreSQL)
inventory-serviceStock levels, reservationsinventory_db (Redis + Postgres)
order-serviceCreating and managing orders, order historyorders_db (PostgreSQL)
payment-servicePayment processing, refundspayments_db (PostgreSQL)
shipping-serviceShipments, tracking numbers, shipping ratesshipping_db (PostgreSQL)
notification-serviceOrder confirmation emails, password reset mailsnotifications_db (MongoDB)

A typical flow when a customer places an order:

  1. Client calls order-service:
    • POST /orders
  2. order-service:
    • Calls user-service to verify user ID and address.
    • Calls catalog-service to confirm product prices.
    • Calls inventory-service to reserve stock.
    • Calls payment-service to charge the customer.
  3. order-service creates an order in its own database.
  4. order-service publishes an OrderCreated event.
  5. notification-service listens to OrderCreated and sends a confirmation email.
  6. shipping-service listens to OrderCreated and creates a shipment.

Notice that no service reads from another service’s database. They all use APIs or messages.

Synchronous vs Asynchronous Communication Between Services

Microservices communicate over the network. There are two main styles.

Synchronous communication

This is usually HTTP or gRPC calls where a service waits for a response.

Example:

http
POST /payments
Content-Type: application/json
{
  "order_id": "ord_123",
  "amount": 49.99,
  "currency": "USD",
  "user_id": "user_456"
}

payment-service responds:

http
201 Created
Content-Type: application/json
{
  "payment_id": "pay_789",
  "status": "completed"
}

Pros:

Cons:

Asynchronous communication

Services use a message broker or event bus, such as:

Example:

json
{
  "event_type": "OrderCreated",
  "order_id": "ord_123",
  "user_id": "user_456",
  "total": 49.99
}

Pros:

Cons:

A real microservices system usually uses both:

Data Management in Microservices

Database per service

As stated before, each service owns its data. This has several consequences:

Example limitation:

sql
SELECT u.email, o.total
FROM users u
JOIN orders o ON u.id = o.user_id;

if users and orders are in different databases.

Instead, you might:

Eventual consistency

In a monolith with a single database, you can often wrap everything in one transaction and be sure that all related changes commit together.

In microservices, with separate databases and network calls, this is not always possible. Instead, you often use eventual consistency.

Definition:

Example:

  1. order-service creates an order and emits OrderCreated.
  2. inventory-service receives the event and lowers stock.
  3. For a short time, another service might read:
    • The new order exists.
    • But inventory still shows the old stock.
  4. After inventory updates, everything aligns.

Important consistency rule
In microservices, you must design for inconsistencies over short periods of time and avoid assumptions that all parts of the system see the same data instantly. Use patterns like idempotent operations, retries, and compensating actions.

Distributed transactions and sagas

Traditional ACID transactions across multiple databases are complex and often avoided in microservices.

Instead, use sagas, which are sequences of local transactions with compensating actions.

Example: Order creation saga

  1. order-service creates an order with status pending.
  2. order-service requests:
    • inventory-service to reserve stock.
    • payment-service to charge the customer.
  3. If both succeed:
    • order-service sets status to confirmed.
  4. If payment fails:
    • order-service asks inventory-service to release the reserved stock.
    • order-service sets status to cancelled.

Here, the “compensating action” for reserving stock is releasing stock.

Sagas can be:

Microservice API Design and Versioning

Because services communicate via APIs, contracts between them are critical.

API contracts

You should treat inter-service APIs as stable contracts:

Example order-service API:

http
POST /orders
Content-Type: application/json
{
  "user_id": "user_123",
  "items": [
    {"product_id": "prod_1", "quantity": 2},
    {"product_id": "prod_2", "quantity": 1}
  ]
}

Response:

http
201 Created
Content-Type: application/json
{
  "order_id": "ord_987",
  "status": "pending_payment"
}

Other services rely on this contract. Changing it can break them.

Backward compatibility and versioning

Because services are deployed independently, you must support backward compatible changes where possible:

Versioning approaches:

Example:

json
{
  "order_id": "ord_987",
  "status": "pending_payment"
}
json
{
  "shipment_estimate": "2026-09-01"
}

If you only add this field, existing clients are not broken.

API rule
When multiple services depend on an API, treat it as a public contract. Avoid breaking changes, or provide a clear versioning strategy and migration period.

Operational Challenges in Microservices

Microservices trade in-process calls for network calls. This creates new challenges.

Observability

With many services, you need strong observability:

Example:

Network reliability

In microservices, failures are common:

You must design for this with:

Example of a simple retry strategy:

  1. Call payment-service.
  2. If timeout:
    • Wait 200 ms, retry.
    • If fails again, wait 400 ms, retry.
    • If fails again, mark as failure.

Service discovery

In a dynamic environment, services often:

You need a way to find where each service lives:

Example:

Configuration management

Each service has:

Microservices make configuration management more complex because of the number of services. Common approaches:

Microservices vs Modular Monolith

Because this course also covers modular monoliths, it is important to compare.

AspectModular MonolithMicroservices
Deployment unitSingle applicationMany services
CommunicationIn-process function callsNetwork calls (HTTP, gRPC, messaging)
DatabasesOften shared DBDatabase per service
TransactionsLocal, simple ACIDDistributed, sagas, eventual consistency
ComplexityLower operational complexityHigher operational complexity
PerformanceNo network latency between modulesNetwork latency and overhead
Team independenceSome independence, but shared deploysHigh independence, separate deployments
Best forSmall to medium systems, early-stage productsLarge, complex systems and many teams

Guideline
Start with a modular monolith. Move to microservices only when:

  • You have clear module boundaries.
  • You face real problems that microservices can solve, such as:
    • Independent team deployments.
    • Different scalability needs for different modules.

When Microservices Make Sense

Microservices are helpful when:

Examples:

Common Pitfalls of Microservices

Microservices are often misused. Typical mistakes include:

Splitting too early

Teams adopt microservices:

Result:

Too many tiny services

An extreme “nano-service” approach creates:

Better:

Shared database

A very common anti-pattern:

This leads back to monolithic coupling, but with added network and deployment complexity.

Ignoring consistency and failures

Treating microservices as if they were function calls leads to:

You must design for:

Simple Microservices Example with HTTP

Consider two simple FastAPI services: user-service and order-service.

user-service:

python
# user_service/main.py
from fastapi import FastAPI, HTTPException
app = FastAPI()
fake_users = {
    "user_1": {"id": "user_1", "email": "alice@example.com"},
    "user_2": {"id": "user_2", "email": "bob@example.com"},
}
@app.get("/users/{user_id}")
def get_user(user_id: str):
    user = fake_users.get(user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

order-service:

python
# order_service/main.py
from fastapi import FastAPI, HTTPException
import httpx
app = FastAPI()
fake_orders = {}
USER_SERVICE_URL = "http://user-service:8000"
@app.post("/orders")
async def create_order(user_id: str):
    async with httpx.AsyncClient() as client:
        try:
            resp = await client.get(f"{USER_SERVICE_URL}/users/{user_id}", timeout=2.0)
        except httpx.RequestError:
            raise HTTPException(status_code=503, detail="User service unavailable")
        if resp.status_code == 404:
            raise HTTPException(status_code=400, detail="Invalid user_id")
        if resp.status_code != 200:
            raise HTTPException(status_code=502, detail="Error calling user service")
    order_id = f"ord_{len(fake_orders) + 1}"
    fake_orders[order_id] = {"id": order_id, "user_id": user_id, "status": "created"}
    return fake_orders[order_id]

This example shows:

Summary

Microservices are an architectural style where:

As a backend developer, you should:

In later chapters, concepts like service layers, repository pattern, and event-driven architecture will connect directly to these microservice ideas and show how to use them in practice.

Views: 6

Comments

Please login to add a comment.

Don't have an account? Register now!