cheat-sheets / microservices

Microservice Patterns

API Gateway Pattern

An API Gateway is a single entry point for clients that routes requests to the right microservice and can handle cross-cutting concerns.

  • Why: Simplifies clients, reduces chatty client↔service calls, centralizes concerns.

  • Typical responsibilities:

    • Routing + request aggregation (compose data from multiple services)
    • AuthN/AuthZ, rate limiting, request validation
    • TLS termination, caching, response shaping
  • Good practices:

    • Keep it thin (avoid business logic creeping into the gateway)
    • Prefer BFF (Backend for Frontend) gateways when multiple client types exist (web, mobile)

Strangler Fig Pattern

A migration approach where you incrementally replace parts of a monolith by building new microservices around it until the monolith “shrinks away.”

  • How it works:

    • Route some functionality to new services while the rest still goes to the monolith.
    • Gradually move more endpoints/features over time.
  • Good practices:

    • Start with low-risk, high-value slices (read-only features, reporting, simple workflows)
    • Put routing rules in the gateway / edge layer so migration is transparent to clients

Database per Service

Each microservice owns its data and does not directly access another service’s database.

  • Why: Prevents tight coupling, enables independent deployment and scaling.
  • Trade-off: Cross-service queries become harder; you typically use events + read models.
  • Good practices:
    • Enforce ownership strictly (no “shared DB”)
    • Use async replication (events) to create materialized views for query needs

CQRS (Command Query Responsibility Segregation)

Separate the models for writes (commands) and reads (queries).

  • Why: Reads and writes often have different performance/scaling needs.
  • Pairs well with: Event Sourcing (events drive read projections), but CQRS can be used without it.
  • Good practices:
    • Keep commands task-focused (e.g., PlaceOrder, not UpdateOrder)
    • Expect eventual consistency on read models and design UI/UX accordingly

Outbox Pattern

Ensures reliable event publishing when you update a database and need to emit an event.

  • Problem: “DB commit succeeded but message publish failed” (or vice versa).
  • Solution: Write outgoing events into an outbox table in the same DB transaction, then a background worker publishes them.
sequenceDiagram
  participant S as Service
  participant DB as DB (Outbox)
  participant MQ as Message Broker
  S->>DB: Commit business data + Outbox event (same transaction)
  DB-->>S: Commit OK
  S->>DB: Background worker polls outbox
  S->>MQ: Publish event
  S->>DB: Mark outbox event as sent
  • Good practices:
    • Make publishing idempotent (safe to publish the same event again)
    • Use deduplication keys / event IDs and consumer-side dedupe

Idempotency Pattern

Design operations so repeating the same request produces the same result (or at least no harmful side effects).

  • Where it matters: retries, timeouts, “at least once” messaging.

  • Techniques:

    • Idempotency key (client-provided or generated)
    • Upserts instead of inserts where appropriate
    • Consumer dedupe using event IDs
  • Good practices:

    • Store idempotency records with a sensible TTL
    • Make side effects (emails, payments) explicitly idempotent

Service Discovery Pattern

Services find each other dynamically rather than via hard-coded addresses.

  • Client-side discovery: client (or SDK) queries registry and load balances.
  • Server-side discovery: client calls a load balancer which resolves services.
  • Good practices:
    • Prefer stable naming (e.g., inventory-service) and let infra resolve endpoints
    • Combine with health checks and timeouts (discovery alone doesn’t fix failures)

Microservice Good Practices (cross-cutting)

Observability (Logs, Metrics, Traces)

  • Distributed tracing: propagate trace_id / correlation_id across calls.
  • Structured logs: JSON logs with service name, request ID, user ID (if applicable).
  • Golden signals: latency, traffic, errors, saturation.

Contract Management

  • Use consumer-driven contracts to prevent breaking changes.
  • Prefer backward-compatible API evolution:
    • Add fields, don’t remove/rename
    • Version carefully (avoid “v2 everywhere” unless necessary)

Timeouts, Bulkheads, and Backpressure

  • Always set timeouts (client + server).
  • Use bulkheads (separate thread pools/connection pools per dependency).
  • Apply rate limiting and queue limits to avoid cascading failures.

Message/Event Hygiene

  • Use immutable events (don’t “edit history”)
  • Include metadata: event_id, occurred_at, schema_version, producer
  • Design consumers to be idempotent and tolerate out-of-order delivery