Course · 10

How do many small services act like one reliable app?

When to split an app, how the pieces find and call each other, how they survive each other's failures, and how they keep data correct without one shared database. Checked against microservices.io, martinfowler.com, Azure Architecture Center and vendor docs on 2026-10-25.

TL;DR

Microservices trade one big problem (a tangled codebase) for many network problems, so you only split when teams, not code, are the bottleneck.

  • Start with a modular monolith. Split along business boundaries (bounded contexts), each service owning its data.
  • Every network call needs a timeout, limited retries with backoff + jitter, and a circuit breaker.
  • No distributed transactions: use a saga (local steps + compensating steps), and the outbox to publish events safely.
  • Change safely: strangler fig to migrate, canary or blue-green to deploy, feature flags to release.
3states
circuit breaker: closed, open, half-open
50%
Resilience4j default failure rate that opens the breaker
25%
Kubernetes rolling update default max surge and max unavailable

—Words you need

Each hard word once, in plain words glossary

Microservice
A small app that owns one business job and its own data.
Monolith
One app, deployed as one piece.
Modular monolith
One deploy, but strict internal modules with clear walls.
Bounded context
A business area where each word has one meaning.
Service discovery
How a service finds the current address of another.
API gateway
The single front door for all outside requests.
BFF
Backend for frontend: one gateway shaped for one client type.
Circuit breaker
Stops calling a failing service for a while.
Bulkhead
Separate pools, so one failure cannot use up everything.
Jitter
Random extra wait, so retries do not all fire together.
Saga
A chain of local transactions with undo steps.
Compensating action
A step that undoes an earlier one: refund for a charge.
Outbox
A table of events saved with the data, published later.
CDC
Change data capture: reading a database's change log.
CQRS
Separate models for writing and for reading.
Sidecar
A helper process deployed next to each service.

Shape

01Monolith, modular monolith or microservices?

And when NOT to split interview favourite

Three ways to shape an app A monolith is one app on one database; a modular monolith is one app with walled modules on one database; microservices are separate apps, each with its own database. MONOLITH MODULAR MICROSERVICES One appone deploy one database One app order user one database Ordersown DB Usersown DB Paymentsown DB LEGEND Good default start Module with a wall Database
The middle one gets most of the clean-code benefit without the network.
ShapeGood atBad at
MonolithSimple deploy, one transaction, easy debugging, fast callsBig teams step on each other; one bug can take everything down; scale all or nothing
Modular monolithClear module walls, one deploy, still one DB transaction; easy to split laterWalls need discipline; still one release train and one runtime
MicroservicesIndependent deploys per team, scale each part, use different tech, contain failuresNetwork calls fail, data is spread out, distributed tracing needed, much more ops work

When NOT to split

  • A small team (one or two pizzas). Coordination is not your problem yet.
  • A new product. You do not know the right boundaries yet; a wrong split is expensive to undo.
  • No fast provisioning, no monitoring, no automated deploys. Fowler calls these prerequisites.
  • Two services that must always change together. That is one service split in two: a "distributed monolith".
In everyday words
A monolith is one big kitchen. Microservices are a food court: each stall runs alone, but now you need trays, signs and a cleaner who walks between them.
Actually
Lewis & Fowler (2014): microservices are "an approach to developing a single application as a suite of small services, each running in its own process". Fowler's MonolithFirst (2015): "you shouldn't start a new project with microservices, even if you're sure your application will be big enough". The Microservice Premium: microservices "introduce complexity on their own account". Prerequisites: rapid provisioning, basic monitoring, rapid application deployment. Richardson describes the modular monolith as modules per (sub)domain, "e.g. customers, orders, and deliveries".

In an interview, say why you split: team independence, different scaling needs, or fault isolation. "Because it is modern" is the wrong answer.

Source: Lewis & Fowler: Microservices · MonolithFirst · Microservice Premium · Prerequisites · microservices.io: modular monolith

02Service boundaries and database per service

Split by business area, not by layer core

A good boundary follows a business area: orders, payments, shipping. Domain-driven design (DDD) calls each area a bounded context: inside it, every word has one meaning. "Customer" in billing (who pays) and in shipping (where to deliver) can be two different models.

Database per service: each service owns its tables. Others may only use its API or its events, never its tables. Otherwise a schema change in one service breaks another, and you are back to a monolith.

Good boundary signBad boundary sign
One team can ship it aloneEvery feature needs 3 services to change together
Few calls cross it, mostly asyncChatty sync calls for every request
Owns its data fullyReads another service's tables directly
Named after a business capability ("Billing")Named after a tech layer ("DB service", "Validation service")
In everyday words
Each shop in the mall has its own stock room. You ask the shopkeeper; you do not walk into their store room.
Actually
Fowler: "Bounded Context is a central pattern in Domain-Driven Design". Lewis & Fowler: "Microservices prefer letting each service manage its own database". microservices.io: "Keep each microservice's persistent data private to that service and accessible only via its API." The cost: no joins and no ACID transaction across services, which is why sections 08–10 exist.

Owning your data is the rule that makes services independent, and the rule that makes cross-service consistency hard.

Source: Fowler: BoundedContext · microservices.io: Database per service

Talking

03Sync (REST, gRPC) vs async (events)

Who waits for whom core

A chain of calls versus events With sync calls the order service waits on payment, which waits on stock; with events the order service publishes to a broker, which delivers to payment and stock independently. SYNC · CHAIN OF CALLS CALL CALL ASYNC · EVENTS EMIT Orderwaits Paymentwaits Stockslow = all slow Ordermoves on BrokerKafka / SNS Paymentown pace Stockown pace LEGEND Caller Middleman Service
In a sync chain, delays add up and failures spread. Events cut the chain.
StyleUse whenWatch out for
REST (HTTP + JSON)Public APIs, simple CRUD, browsersText payloads, no strict contract unless you add OpenAPI
gRPC (HTTP/2 + Protobuf)Internal service-to-service calls, streaming, strict typed contractsHard to call from browsers directly; needs a proxy
Async eventsCaller does not need the answer now; many readers; spikesEventual consistency, duplicates, harder tracing

Availability math: if a request needs 3 services in a row, each up 99.9% of the time, the chain is up about 99.9% × 99.9% × 99.9% ≈ 99.7%. Every sync hop lowers the total.

In everyday words
A phone call needs both people free at once. A text message waits until the other person looks.
Actually
The chained-availability number is plain multiplication of independent probabilities, not a vendor figure; real failures are often correlated, so treat it as a rough guide. gRPC and REST details are on page 02; messaging details on page 09.

Use sync when the user is waiting for the answer. Use events for everything that can happen a moment later.

Source: Course page 02 · Course page 09 · Lewis & Fowler

04Service discovery

Finding an address that keeps changing core

Containers start, die and move. Their IP addresses change all the time. A service registry is a live list of healthy instances. Discovery is how a caller uses it.

Client-side versus server-side discovery In client-side discovery the client asks the registry for instance addresses and calls one itself; in server-side discovery the client calls a load balancer, which picks the instance. CLIENT-SIDE ASK IPS CALL SERVER-SIDE CALL ROUTE Clientpicks instance RegistryConsul, Eureka Instance10.0.3.7 Clientknows one name Router / LBk8s Service Instanceany healthy one LEGEND Registry Router picks for you Reply
Client-side: smart client, one less hop. Server-side: simple client, one more hop.
ApproachHowProsCons
Client-sideClient asks the registry, load-balances itself (Netflix Eureka + Ribbon style)No extra hop; client can be smartDiscovery logic in every language you use
Server-sideClient calls a router / load balancer that knows the instances (AWS ALB, Kubernetes Service)Clients stay simpleExtra hop; the router must be highly available
DNSName resolves to healthy instances (orders.service.consul, orders.prod.svc.cluster.local)Works with any clientDNS caching can keep dead addresses (TTL)
In everyday words
Client-side is looking up a shop's address in the mall map yourself. Server-side is asking the info desk to walk you there.
Actually
microservices.io: client-side discovery means "the client obtains the location of a service instance by querying a Service Registry"; server-side means the client "makes a request via a router (a.k.a load balancer) that runs at a well known location". Kubernetes: a Service gives pods one stable virtual IP (ClusterIP is the default type) and a DNS name of the form my-svc.my-namespace.svc.cluster-domain.example (usually cluster.local). Consul DNS: "Services that fail their health check … are omitted from the results."

On Kubernetes, say "server-side discovery via a Service and DNS"; you rarely build a registry yourself.

Source: Client-side discovery · Server-side discovery · Kubernetes Service · Kubernetes DNS · Consul DNS

05API gateway and backend for frontend

One front door, or one door per client interview favourite

Backends for frontends The web app talks to a web gateway and the mobile app to a mobile gateway; both gateways call the same internal services. ONE GATEWAY PER CLIENT PAGE VIEW Web appbrowser Mobile appiOS, Android Web BFFrich pages Mobile BFFsmall payloads Servicesorders, users LEGEND Client Gateway shaped for that client
Each BFF shapes, joins and trims data for its own screen.

An API gateway is the single entry point. It does the shared jobs once: routing, login checks (auth), rate limiting, TLS, request joining, caching. A BFF is a gateway per client type, owned by that client's team.

Gateway jobAzure pattern nameExample
Send /orders/* to the orders serviceGateway RoutingPath-based routes
Join 3 backend calls into one responseGateway AggregationHome screen in one round trip
Do TLS, auth, rate limits onceGateway OffloadingJWT check at the edge
In everyday words
A hotel reception desk: guests never wander the corridors to find the kitchen or laundry. A BFF is a separate desk for groups and for families.
Actually
microservices.io: "Implement an API gateway that is the single entry point for all clients", and BFF is a variation that "defines a separate API gateway for each kind of client". Sam Newman (2015): "one backend per user experience". Risk: the gateway becomes a new monolith if business logic creeps into it; keep it to cross-cutting work. Rate limiting algorithms are on page 03.

Gateway = cross-cutting concerns at the edge. BFF = when web and mobile need very different shapes of data.

Source: microservices.io: API Gateway / BFF · Sam Newman: BFF · Azure: BFF · Gateway Aggregation · Course page 03

Resilience

06Timeouts, retries with backoff and jitter, fallbacks

Fail fast, retry politely interview favourite

Every remote call can hang. A timeout caps how long you wait. Without one, threads pile up waiting and your service dies too. A retry fixes short glitches. But retries add load to a service that is already struggling.

Exponential backoff: wait longer after each failure (100 ms, 200 ms, 400 ms…). Jitter: pick a random wait up to that limit, so a thousand clients do not retry in the same millisecond.

Longest wait before each retry, full jitter

base 100 ms, cap 10 s. The real wait is a random number between 0 and the bar. Hover for numbers.

Retry 1
200ms
Retry 2
400ms
Retry 3
800ms
Retry 4
1.6s
Retry 5
3.2s
Retry 6
6.4s
Show the full table
Attemptmin(cap, base × 2^attempt)Actual wait
Retry 1200 msrandom 0 to 200 ms
Retry 2400 msrandom 0 to 400 ms
Retry 3800 msrandom 0 to 800 ms
Retry 41,600 msrandom 0 to 1.6 s
Retry 53,200 msrandom 0 to 3.2 s
Retry 66,400 msrandom 0 to 6.4 s
Full jitter (Brooker, AWS Architecture Blog)
sleep = random(0, min(cap, base * 2 ** attempt))
RuleWhy
Set a timeout on every call, a bit above the callee's p99 latencyStops threads and connections from piling up
Retry only safe (idempotent) operations, or use an idempotency keyA retried "charge card" must not charge twice (section 11)
Retry at one layer only3 retries at each of 5 layers can multiply load hugely
Cap total attempts and use a retry budgetRetries should not become the main traffic
Do not retry 4xx errors like 400 or 403They will fail again the same way
Have a fallbackCached data, a default ("recommendations unavailable"), or a clear error
In everyday words
If a shop is crowded, you do not knock every second. You come back later, and not at the same time as everyone else.
Actually
Brooker's post concludes jittered backoff should be "considered a standard approach for remote clients", with full jitter doing best overall. The AWS Builders' Library: "Retries are 'selfish'", and "our best practice is to retry at a single point in the stack"; its 5-layer example multiplies load up to 243x (3 tries per layer, 3^5). Azure Retry: handle transient failures "by transparently retrying a failed operation".

Timeout + capped retries + jitter + one retry layer. Say all four in an interview.

Source: AWS: Exponential Backoff and Jitter · Builders' Library: Timeouts, retries and backoff · Azure: Retry

07Circuit breaker and bulkhead

Stop hammering a broken service interview favourite

Circuit breaker states Closed lets calls through and trips to open when failures pass a limit; open fails calls at once and moves to half-open after a wait; half-open lets a few test calls through and goes back to closed on success or to open on failure. TRIP WAIT FAIL SUCCESS Closedcalls pass Openfail fast Half-openfew test calls LEGEND Calls reach the service Calls blocked at once
Open protects both sides: the sick service gets a rest, and your threads are not stuck waiting.

When failures pass a limit, the breaker opens: calls fail at once without touching the network. After a wait it goes half-open and lets a few test calls through. Success closes it; failure opens it again.

Resilience4j settingDefaultMeaning
failureRateThreshold50%Open when half the calls in the window fail
slidingWindowSize100Look at the last 100 calls (count-based)
minimumNumberOfCalls100Do not judge before this many calls
waitDurationInOpenState60000 msStay open 60 s before half-open
permittedNumberOfCallsInHalfOpenState10Test calls allowed in half-open

Bulkhead

A bulkhead gives each dependency its own small pool (threads, connections). If the "recommendations" service hangs, it can use up only its own 20 threads. Checkout keeps working with its own pool.

In everyday words
A circuit breaker is the fuse box: when wires overheat, it cuts power before the house burns. A bulkhead is the walls inside a ship: one flooded room does not sink it.
Actually
Fowler: a call "in the half-open state results in a trial call, which will either reset the breaker if successful or restart the timeout". Azure: in half-open, "a limited number of requests from the application are allowed to pass through". Azure Bulkhead: "Isolate the elements of an application into pools so that if one element fails, the others continue to function." Netflix Hystrix "is no longer in active development, and is currently in maintenance mode"; its README points to Resilience4j.

Retries handle blips. Breakers handle outages. Bulkheads stop one outage from becoming yours.

Source: Fowler: CircuitBreaker · Azure: Circuit Breaker · Azure: Bulkhead · Resilience4j · Hystrix README

Data across services

08Distributed transactions: 2PC vs saga

Choreography, orchestration and compensating actions interview favourite

An order needs: charge the card (payment service), reserve stock (stock service), confirm the order (order service). Three databases. How do you keep them consistent?

Two-phase commit (2PC)

A coordinator asks every database "can you commit?" (prepare). If all say yes, it says "commit". It is all-or-nothing. But each participant holds locks between the two phases. If the coordinator dies in the middle, they wait. Many modern stores (Kafka, most NoSQL databases, SaaS APIs) cannot even join one.

Saga

A saga is a chain of local transactions. Each one commits on its own. If a later step fails, earlier steps are undone by compensating actions (refund the charge, release the stock). There is no rollback, only a forward "undo".

Choreography saga with a compensation The order service emits OrderCreated; payment charges and emits Paid; stock fails and emits OutOfStock; payment refunds and emits Refunded, and the order service cancels the order. ORDER CREATED PAID OUT OF STOCK REFUNDED Orderthen cancels Paymentcharge, refund Stockreserve fails LEGEND Event (via a broker) Result of a compensating step
Choreography: no boss. Each service reacts to events and publishes its own.
Orchestration saga with a compensation An orchestrator tells payment to charge and gets OK, tells stock to reserve and gets a failure, then tells payment to refund and order to cancel. CHARGE OK RESERVE FAIL REFUND CANCEL Orchestratorsaga state Payment Stock Order LEGEND Central coordinator Reply Compensating command
Orchestration: one place holds the saga's state and decides the next step.
2PCSaga: choreographySaga: orchestration
ConsistencyAtomic (all or nothing)Eventual; in-between states are visibleEventual; in-between states are visible
LocksHeld across services during the protocolOnly local, shortOnly local, short
CouplingTight, every participant must support itLoose, but the flow is hidden across servicesFlow is explicit in one place
Good forFew databases you control, low volume2 to 4 simple stepsMany steps, complex rules, clear monitoring
In everyday words
Booking a trip: you book the flight, then the hotel. If the hotel is full, you cancel the flight. You never hold both "half booked" while waiting for a referee.
Actually
microservices.io: "A saga is a reliably coordinated sequence of local transactions"; in choreography "each local transaction publishes domain events that trigger local transactions in other services"; in orchestration "an orchestrator (object) tells the participants what local transactions to execute"; and for microservices "2PC is not an option". Azure: "If a step fails, a series of compensating transactions undoes the changes that the completed steps made." The 2PC blocking problem (participants stuck holding locks if the coordinator fails after prepare) is standard textbook material, but I did not find a primary-source quote for it today: not proven here. Tools: Temporal, AWS Step Functions, Camunda are common orchestrators.

Design each step with its undo. Some steps cannot be undone (an email sent), so put them last.

Source: microservices.io: Saga · Azure: Saga · Azure: Compensating Transaction · Course page 08

09Transactional outbox and CDC

Save data and publish the event, both or neither deep dive

A saga step must save its data and publish an event. Writing to the database and then to Kafka is a dual write: a crash between them loses the event. The outbox fix: insert the event into an outbox table in the same local transaction. A separate relay publishes it.

Outbox published by change data capture The service inserts business and outbox rows in one transaction; Debezium tails the database log and publishes outbox rows to Kafka, which delivers events to idempotent consumers. INSERT TAIL PUBLISH EVENT Serviceone local tx Databaseoutbox + WAL Debeziumreads the log Kafkaorder events Consumersidempotent LEGEND Database CDC relay Broker
The database's own change log becomes the source of events. No polling query needed.
Relay typeHow it worksProsCons
Polling publisherA job runs SELECT … FROM outbox WHERE sent = false, publishes, marks sentSimple, any databasePolling load and delay; ordering care with many pollers
Log tailing (CDC)Debezium reads MySQL binlog or Postgres WAL (via logical decoding) and publishes changesLow delay, no extra queries, keeps commit orderOne more system (Kafka Connect + Debezium) to run

Either way the relay can publish an event twice (crash after publish, before marking sent). So consumers must be idempotent (page 09).

In everyday words
You do not post the letter yourself. You leave it in the outbox tray in the same move as filing the paperwork. The office runner empties the tray.
Actually
microservices.io: the service "first stores the message in the database as part of the transaction that updates the business entities". Log tailing: "Tail the database transaction log and publish each message/event inserted into the outbox to the message broker." Debezium's MySQL connector "reads the binlog, produces change events for row-level INSERT, UPDATE, and DELETE operations"; its outbox event router exists to "safely and reliably exchange data between multiple (micro) services".

Outbox gives you "save and publish" without 2PC. Pair it with idempotent consumers.

Source: Transactional outbox · Transaction log tailing · Polling publisher · Debezium outbox router · Debezium MySQL

10CQRS and event sourcing

Separate reads from writes; store changes, not state deep dive

CQRS with an event log Commands go to the write model, which appends events to a log; a projection turns the events into a read model that queries use. WRITE APPEND PROJECT READ CommandsPlaceOrder Write modelchecks rules Event logappend only QueriesGetOrderList Read modelready-made view LEGEND Source of truth Fast view, a little behind
The read model lags the write model by a moment: eventual consistency.
PatternIdeaGainCost
CQRSOne model to change data, another (or many) to read itEach side scales and is shaped for its job; reads become cheap lookupsTwo models to keep in step; read-after-write may show old data
Event sourcingStore every change as an event; current state = replay of eventsFull audit trail, time travel, new views by replayingEvent schema changes are hard; need snapshots for long histories; querying needs CQRS
In everyday words
A bank passbook is event sourcing: every deposit and withdrawal, and the balance is the sum. The balance printed at the top is the read model.
Actually
Fowler: event sourcing means "Capture all changes to an application state as a sequence of events." On CQRS he warns: "for most systems CQRS adds risky complexity" and "you should be very cautious about using CQRS". microservices.io: CQRS defines "a view database, which is a read-only 'replica' that is designed specifically to support that query".

Use CQRS when reads and writes look very different. Use event sourcing when history itself is the product (ledgers, audits).

Source: Fowler: CQRS · Fowler: Event Sourcing · microservices.io: CQRS · microservices.io: Event sourcing

11Idempotency keys

Make a retried POST safe core

GET, PUT and DELETE are meant to be safe to repeat. POST ("create a payment") is not. If the client times out and retries, the server may charge twice. An idempotency key is a unique id the client sends with the request. The server remembers the key and the first result. A repeat with the same key gets the stored result, not a second charge.

Client sends a key; server stores key → result
POST /payments
Idempotency-Key: 7f3c9a1e-2b5d-4e8f-9c10-aa21f0b3d6e4
{ "amount": 4999, "currency": "INR" }

server:
  BEGIN
  INSERT INTO idem_keys(key, status) VALUES (:key, 'in_progress')   -- unique: a repeat fails here
  ... charge card, save payment ...
  UPDATE idem_keys SET status='done', response=:resp WHERE key=:key
  COMMIT
  -- repeat with same key → return the saved response
Design choiceGood answer
Who makes the key?The client, once per user action (a UUID), reused on every retry
Same key, different body?Reject with an error; it is a client bug
Request still running when the repeat arrives?Return "in progress" (e.g. 409) instead of running it twice
How long to keep keys?Longer than any client retry window (Stripe: prunable after at least 24 hours)
In everyday words
A token number at a ticket counter. Show the same token twice and you get the same ticket back, not a second one.
Actually
Stripe API v1: "you can remove keys from the system automatically after they're at least 24 hours old"; keys are "up to 255 characters long". The IETF header Idempotency-Key is still an Internet-Draft (revision -08, 22 Oct 2026), not an RFC: it "can be used to make non-idempotent HTTP methods such as POST or PATCH fault-tolerant".

Retries (section 06) are only safe with idempotency. Mention both together.

Source (checked 2026-10-25): Stripe: Idempotent requests · IETF draft: Idempotency-Key

Change safely

12Strangler fig migration

Replace a monolith one route at a time core

Strangler fig Clients call a routing facade, which sends the orders route to a new service and every other route to the old monolith. Clientssame URLs Routing facadeproxy, gateway New service/orders Old monolitheverything else LEGEND Routing switch New code Legacy, shrinking
Move one route at a time. If the new service breaks, flip the route back.

Steps: put a facade in front; build one capability in a new service; route its traffic there (maybe a small % first); repeat; delete the old code when nothing calls it. The big-bang rewrite is the thing to avoid.

In everyday words
A strangler fig vine grows around an old tree. Years later the tree is gone and the vine stands in its shape.
Actually
Azure Strangler Fig: "Incrementally migrate a legacy system by gradually replacing specific pieces of functionality with new applications and services." microservices.io calls it the strangler application. Fowler renamed his article to "Strangler Fig Application" because of the old name's violent sound. Hard part: shared data. The new service often reads the old database at first, then takes ownership with sync via CDC.

Migration answer in one line: facade, move one slice, measure, repeat, delete.

Source: Azure: Strangler Fig · Fowler: StranglerFigApplication · microservices.io: Strangler application

13Sidecar and service mesh

Move network plumbing out of your code brief

Service mesh with sidecars Each service runs next to an Envoy sidecar proxy in the same pod; service A's traffic goes through its Envoy to service B's Envoy over mutual TLS; a control plane pushes configuration to both proxies. POD A POD B TLS Control planeistiod Service Ayour code Envoysidecar Envoysidecar Service Byour code LEGEND Proxy that does the plumbing Config push
Your code calls localhost; the sidecars handle retries, TLS, metrics and routing.

A sidecar is a helper process deployed next to each service (same pod). A service mesh is many sidecars plus a control plane. It gives every service the same retries, timeouts, mutual TLS (both sides prove who they are), traffic splitting and metrics, in any language, without code changes.

GainCost
Same resilience and security for every languageExtra hop and CPU/memory per pod
Canary traffic splits by configA complex system to learn, upgrade and debug
Uniform metrics and tracingRetries in both the app and the mesh can stack up
In everyday words
Every shop gets the same trained security guard at its door. The shopkeeper just sells.
Actually
Azure Sidecar: "Deploy application components into a process or container separate from the main application to provide isolation and encapsulation." Istio: "Envoy proxies are deployed as sidecars to services". Envoy is "an L7 proxy and communication bus designed for large modern service oriented architectures". Istio also has a sidecar-less ambient mode, GA in Istio 1.24 (November 2024).

Mention a mesh when there are many services in many languages. For five services, a library is enough.

Source: Azure: Sidecar · Istio architecture · Istio ambient GA · What is Envoy

14Deployment patterns

Blue-green, canary, rolling, feature flags core

Canary release Users go through a load balancer that sends most traffic to version 1 and a small share to the new version 2, which is watched before more traffic moves. Usersall traffic Load balancerweighted Version 1e.g. 95% Version 2e.g. 5% canary LEGEND Current version New version, watched closely
The split is an example, not a rule. Grow it step by step while error rates stay normal.
PatternHowRollbackCost / risk
RollingReplace instances a few at a timeRoll forward or back, slowlyOld and new run together for a while; no extra fleet. Kubernetes default: max surge 25%, max unavailable 25%
Blue-greenRun a full new copy (green) beside the old (blue); switch traffic at onceSwitch back instantlyDouble the machines during the switch; database changes must work for both
CanarySend a small share of users to the new version, watch metrics, grow itSend the share back to 0Needs good metrics and traffic splitting
Feature flagsShip code turned off; turn it on per user or % at runtimeTurn the flag off, no deployOld flags pile up as dead code; test both paths

Deploy is not release. Deploy = the code is on the servers. Release = users see the feature. Flags let you separate the two.

In everyday words
A canary is the miners' bird: a few users go first, and if they are fine, everyone follows.
Actually
Fowler (Danilo Sato) on canary: "slowly rolling out the change to a small subset of users before rolling it out to the entire infrastructure". Kubernetes Deployments: "at most 125% of the desired number of Pods are up (25% max surge)", and maxUnavailable "The default value is 25%." Pete Hodgson names four toggle kinds: release, experiment, ops and permissioning toggles.

Pair any rollout with automatic checks: error rate and latency compared with the old version, and a one-click way back.

Source: Fowler: BlueGreenDeployment · CanaryRelease · Feature Toggles · Kubernetes Deployment

Practice

—When to use what

Situation → pick → why decision table

SituationPickWhy
New product, small teamModular monolithBoundaries are still unknown; one deploy is cheap
Many teams blocked on one release trainSplit into services along bounded contextsIndependent deploys per team
User is waiting for the answerSync call (REST / gRPC) with timeout + retry + breakerNeeds an immediate reply
Work can happen a moment later, many readersAsync eventsDecoupling, spikes absorbed
Web and mobile need very different dataBFF per clientEach team shapes its own API
Calling a flaky dependencyTimeout, jittered retries, circuit breaker, bulkhead, fallbackContain failures
Business transaction across servicesSaga (orchestration if many steps)No cross-service locks
Must save data and publish an eventTransactional outbox (+ CDC at scale)No dual write
Reads far outnumber writes and need other shapesCQRS read modelsCheap, shaped reads
Full audit history is requiredEvent sourcingHistory is the data
Client may retry a POSTIdempotency keyNo double charges
Replacing a legacy systemStrangler figSmall, reversible steps
Many services, many languages, need mTLSService meshUniform plumbing
Risky change to productionCanary + feature flagSmall blast radius, fast rollback

—Common mistakes and traps

What interviewers listen for traps

Trap
Distributed monolith
Services that must deploy together. All the cost, none of the gain.
Trap
Shared database
Two services writing one table. A schema change breaks both.
Trap
No timeouts
One slow dependency holds every thread. Your service dies with it.
Trap
Retry storms
Retries at every layer, no jitter. A small blip becomes an outage.
Trap
Retrying non-idempotent calls
Double charges. Add an idempotency key first.
Trap
Saga with no undo plan
Every step needs a compensating step, or must come last.
Trap
Dual write
"Save, then publish" loses events on a crash. Use the outbox.
Trap
Logic in the gateway
The gateway grows into a new monolith every team must change.
Trap
Big-bang rewrite
Years with nothing shipped. Strangle it route by route.

—Interview questions

20 questions, easy to design-level. Try first, then open the answer practice

1. Monolith vs microservices: when would you choose each? easy
Show answer

Start with a (modular) monolith for a new product or a small team: one deploy, ACID transactions, simple debugging, fast in-process calls. Move to microservices when the organisation is the bottleneck: many teams blocked on one release, parts with very different scaling needs, or a need to isolate failures. The cost is real: network failures, eventual consistency, distributed tracing, more ops. Fowler's MonolithFirst says not to start new projects with microservices. Prerequisites: automated deploys, monitoring, fast provisioning. Follow-up: "how do you pick boundaries?" → bounded contexts.

2. What is a modular monolith and why is it a good default? easy
Show answer

One deployable app split into strict modules by business area (orders, users, payments), each with its own internal code and ideally its own tables, talking only through module interfaces. You keep a single deploy and real database transactions, but you get clear ownership and low coupling. If one module later needs independent scaling or a separate team, it can be extracted with little rework because its boundary already exists. The risk is that walls erode without enforcement, so use build rules or architecture tests that block cross-module imports.

3. Why should each service own its database? easy
Show answer

So services can change and deploy independently. If two services share tables, a schema change in one can break the other, and they must be released together, which is a distributed monolith. Owning data also lets each service pick the right store (SQL for orders, Redis for sessions, a search index for products). The cost: no joins or ACID transactions across services. You replace joins with API calls, events or read models (CQRS), and transactions with sagas plus the outbox. Owning a database can mean a separate schema on a shared server; the rule is about access, not hardware.

4. REST vs gRPC vs messaging between services? easy
Show answer

REST over HTTP/JSON for public and browser-facing APIs: simple, cacheable, readable. gRPC (HTTP/2 + Protobuf) for internal calls: strict typed contracts, smaller payloads, streaming, generated clients; harder from browsers. Messaging (Kafka, SQS) when the caller does not need an immediate answer: it decouples availability and absorbs spikes, at the cost of eventual consistency and duplicates. Rule of thumb: sync for queries a user waits on, async for side effects ("send the receipt", "update analytics"). Each sync hop multiplies availability: three 99.9% services in a row ≈ 99.7%.

5. Client-side vs server-side service discovery? medium
Show answer

Client-side: the client queries a registry (Consul, Eureka) for healthy instances and load-balances itself. No extra hop and smart choices (zone-aware), but discovery code is needed in every language. Server-side: the client calls a stable name or load balancer (Kubernetes Service, AWS ALB) that knows the instances. Clients stay simple, at the cost of one more hop and a component that must be highly available. In Kubernetes you get server-side discovery for free: a Service with a stable ClusterIP and a DNS name like orders.prod.svc.cluster.local. Watch DNS TTL caching of dead addresses.

6. What does an API gateway do, and what is a BFF? medium
Show answer

The gateway is the single entry point: routing by path, authentication, TLS termination, rate limiting, request aggregation, caching, and hiding internal service layout from clients. A BFF (backend for frontend) is a gateway per client type, owned by that client's team, so mobile can get small payloads and web can get rich pages without one API serving both badly. Risks: the gateway is a single point of failure (run it redundantly) and can become a monolith if business logic creeps in. Keep it to cross-cutting work.

7. How do you set a timeout for a downstream call? medium
Show answer

Base it on the callee's measured latency: a bit above its p99 (or p99.9), so normal slow requests pass but hung ones are cut. Then check it fits inside your own deadline: if your API promises 500 ms and you make two sequential calls, they cannot each have 400 ms. Propagate a deadline down the chain (gRPC deadlines do this) so inner services stop work nobody waits for. Set connect and read timeouts separately. Too short a timeout causes false failures and retries; too long ties up threads. Always pair with a circuit breaker.

8. Explain exponential backoff with jitter. Why the jitter? medium
Show answer

Backoff doubles the wait after each failure (100, 200, 400 ms…) up to a cap, giving the callee time to recover. Without jitter, every client that failed at the same moment retries at the same moment, creating synchronised waves that keep the service down. Full jitter picks a random wait between 0 and the backoff value: sleep = random(0, min(cap, base * 2 ** attempt)), which AWS's analysis found spreads load best. Also cap attempts, retry at one layer only (AWS shows 3 tries at 5 layers can mean 243x load), and only retry idempotent operations.

9. Walk me through the circuit breaker states. medium
Show answer

Closed: calls pass and failures are counted in a sliding window. When the failure rate passes a threshold (Resilience4j default 50% over the last 100 calls, after at least 100 calls), it goes Open: calls fail immediately without network, protecting your threads and giving the callee rest. After a wait (default 60 s) it goes Half-open and lets a few test calls through (default 10). If they succeed it closes; if they fail it opens again. Pair with a fallback (cached or default response). Slow calls can count as failures too.

10. What is the bulkhead pattern? medium
Show answer

Isolate resources per dependency or per client so one failure cannot exhaust everything. Example: a service has 200 request threads and calls payments and recommendations. If recommendations hangs, all 200 threads can end up waiting on it and checkout dies. With bulkheads, recommendations gets its own pool of 20 threads (or a semaphore of 20 concurrent calls); when full, extra calls fail fast to a fallback, and checkout still has its own pool. The same idea applies at larger scale: separate clusters per tenant tier, or cell-based architecture. Cost: idle capacity in each pool.

11. Why not just use two-phase commit across services? medium
Show answer

2PC needs every participant to support the protocol and hold locks between prepare and commit. That couples availability: one slow or dead participant, or a coordinator failure after prepare, blocks the others with locks held. It also does not scale to high throughput, and many stores (Kafka, most NoSQL, third-party APIs like Stripe) cannot take part. microservices.io simply says 2PC is not an option for microservices. Use a saga: local transactions plus compensating actions, accepting that in-between states are visible for a short time.

12. Choreography vs orchestration sagas: which do you pick? medium
Show answer

Choreography: each service listens for events and emits its own; no central coordinator. Loose coupling, good for 2 to 4 simple steps, but the overall flow is spread across services and hard to see or change, and cyclic event chains can appear. Orchestration: one orchestrator (a service or a workflow engine like Temporal or AWS Step Functions) sends commands and tracks saga state. The flow is explicit, easy to monitor, timeouts and compensations live in one place; the risk is the orchestrator accumulating business logic. For a checkout with payment, stock, shipping and loyalty steps, I would pick orchestration.

13. What is a compensating action? Give examples and pitfalls. medium
Show answer

A business-level undo of a step that already committed: refund a charge, release reserved stock, cancel a booking. It is not a database rollback, because the original commit is visible and may have side effects. Pitfalls: some steps cannot be undone (an email sent, a parcel shipped), so put them last (a "pivot" step after which the saga only moves forward). Compensations must be idempotent and retried until they succeed. Concurrency: another saga may read the in-between state, so use semantic locks such as an order status of "PENDING".

14. Explain the transactional outbox. Polling or CDC? medium
Show answer

To avoid the dual-write problem, the service writes its business row and an outbox row in the same local transaction. A relay publishes outbox rows to the broker. A polling publisher queries unsent rows on a schedule: simple, works anywhere, but adds DB load and delay. CDC (Debezium reading MySQL binlog or Postgres WAL) streams committed changes with low delay and in commit order, but adds Kafka Connect to operate. Both may publish twice after a crash, so consumers must be idempotent. Clean up sent outbox rows on a schedule.

15. What is CQRS and when is it worth it? hard
Show answer

Command Query Responsibility Segregation: separate models for writes (enforce rules, normalised) and reads (denormalised views shaped for each screen), often kept in sync by events. Worth it when reads and writes differ a lot: read-heavy dashboards, search, or views that join data owned by several services. Costs: two models, sync code, and eventual consistency, so a user may not see their own write instantly (fix with read-your-writes tricks such as reading the write model for that user briefly). Fowler warns it adds risky complexity for most systems, so do not use it by default.

16. What is event sourcing, and what problems does it bring? hard
Show answer

Instead of storing current state, store every change as an immutable event (Deposited 100, Withdrew 30); state is rebuilt by replaying them. Gains: full audit trail, time travel, new read models by replay, natural fit with events for other services. Problems: event schemas live forever, so you need versioning and upcasting; long streams need snapshots; queries need CQRS projections; deleting personal data (GDPR) is hard in an append-only log (crypto-shredding helps). Good fits: ledgers, bookings, audit-heavy domains. Poor fit: simple CRUD.

17. Design an idempotent "create payment" API. hard
Show answer

The client generates a UUID per user action and sends it in an Idempotency-Key header on every retry. The server inserts the key with status "in progress" under a unique constraint in the same transaction as the payment record. On a repeat: if done, return the stored response; if in progress, return 409 so the client waits; if the body differs, return an error. Keys are kept longer than the client's retry window (Stripe allows pruning after at least 24 hours). Calls to the card network also carry our own idempotency id, so a crash mid-charge can be reconciled.

18. How would you migrate a large monolith to microservices? hard
Show answer

Strangler fig, never a big-bang rewrite. Put a routing facade (gateway or proxy) in front. Pick a first slice that is valuable, has clear boundaries and few dependencies (e.g. notifications). First modularise it inside the monolith, then extract it; route its traffic to the new service gradually (canary), with a quick way back. Data: let the new service own its tables; sync with CDC from the old database during transition. Add tracing and contract tests before the first split. Repeat slice by slice and delete old code once traffic is zero. Measure deploy frequency and incidents to prove it helps.

19. Blue-green vs canary vs rolling: how do you choose? medium
Show answer

Rolling replaces instances gradually (Kubernetes default 25% max surge, 25% max unavailable): cheap, no extra fleet, but old and new run together and rollback is slow. Blue-green runs a full second environment and flips traffic at once: instant rollback, but double capacity during the switch, and the database schema must work for both. Canary sends a small share of traffic to the new version and grows it while metrics stay healthy: smallest blast radius, needs good metrics and weighted routing. In all cases make schema changes backward compatible (expand, then contract). Feature flags separate deploy from release.

20. Design: checkout calls payment, stock and shipping. Payment is slow at peak. What breaks and how do you fix it? hard
Show answer

With sync calls and no limits, slow payment holds checkout threads, the pool fills, and checkout fails for everyone (cascading failure); retries make it worse. Fixes: timeouts slightly above payment's p99; one retry with jitter only for idempotent calls with an idempotency key; a circuit breaker so checkout fails fast when payment is down; a bulkhead so payment cannot use every thread. Architecturally, make it an orchestrated saga: accept the order as PENDING, charge asynchronously via a queue that levels the peak, reserve stock, compensate on failure, and notify the user. Use the outbox for events and watch queue lag.

—Cheat sheet

Facts to memorise (checked 2026-10-25) reference

FactValueSource
Microservices articleLewis & Fowler, March 2014; "smart endpoints and dumb pipes"martinfowler.com
MonolithFirstFowler, June 2015martinfowler.com
Circuit breaker statesClosed → Open → Half-openFowler, Azure
Resilience4j defaults50% failure rate, window 100, open 60 s, 10 half-open callsresilience4j docs
Full jitterrandom(0, min(cap, base * 2 ** attempt))AWS Architecture Blog
Retry amplification3 tries × 5 layers = up to 243x loadAWS Builders' Library
Chained availability0.999³ ≈ 99.7%Arithmetic
SagaLocal transactions + compensating actions; choreography or orchestrationmicroservices.io
2PC in microservices"2PC is not an option"microservices.io
Outbox relaysPolling publisher, or log tailing (CDC: binlog / WAL)microservices.io, Debezium
Kubernetes rolling updateMax surge 25%, max unavailable 25%kubernetes.io
Kubernetes Service DNSsvc.ns.svc.cluster.local (default domain)kubernetes.io
Istio ambient (no sidecars)GA in 1.24, Nov 2024istio.io
Stripe idempotency keysUp to 255 characters; prunable after at least 24 h (API v1)docs.stripe.com
Idempotency-Key headerIETF Internet-Draft, not an RFC yetdatatracker.ietf.org
HystrixMaintenance mode; use Resilience4jGitHub README
Feature toggle kindsRelease, experiment, ops, permissioningPete Hodgson, martinfowler.com

—Sources

WhatWhere
Microservices, monolith first, bounded context, circuit breaker, CQRS, event sourcing, strangler fig, blue-green, canary, feature togglesmartinfowler.com (articles and bliki pages linked in each section)
Patterns catalogue: database per service, gateway, BFF, discovery, saga, outbox, log tailing, polling publisher, CQRS, idempotent consumer, stranglermicroservices.io (Chris Richardson)
Retry, circuit breaker, bulkhead, saga, compensating transaction, sidecar, strangler fig, BFF, gateway patternsAzure Architecture Center
Backoff and jitter, retriesAWS Architecture Blog, AWS Builders' Library
Circuit breaker defaultsresilience4j.readme.io, Hystrix README
CDC and outbox toolingdebezium.io
Kubernetes, Consul, Istio, Envoykubernetes.io, developer.hashicorp.com, istio.io, envoyproxy.io
Idempotency keysdocs.stripe.com, IETF draft
Not proven2PC "blocking coordinator" wording (no primary quote found; only "2PC is not an option"); modular monolith page on microservices.io sits under a draftZZZ path; chained availability assumes independent failures