Contents
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.
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.
—Words you need
Shape
01Monolith, modular monolith or microservices?
| Shape | Good at | Bad at |
|---|---|---|
| Monolith | Simple deploy, one transaction, easy debugging, fast calls | Big teams step on each other; one bug can take everything down; scale all or nothing |
| Modular monolith | Clear module walls, one deploy, still one DB transaction; easy to split later | Walls need discipline; still one release train and one runtime |
| Microservices | Independent deploys per team, scale each part, use different tech, contain failures | Network 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 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
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 sign | Bad boundary sign |
|---|---|
| One team can ship it alone | Every feature needs 3 services to change together |
| Few calls cross it, mostly async | Chatty sync calls for every request |
| Owns its data fully | Reads another service's tables directly |
| Named after a business capability ("Billing") | Named after a tech layer ("DB service", "Validation service") |
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)
| Style | Use when | Watch out for |
|---|---|---|
| REST (HTTP + JSON) | Public APIs, simple CRUD, browsers | Text payloads, no strict contract unless you add OpenAPI |
| gRPC (HTTP/2 + Protobuf) | Internal service-to-service calls, streaming, strict typed contracts | Hard to call from browsers directly; needs a proxy |
| Async events | Caller does not need the answer now; many readers; spikes | Eventual 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.
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
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.
| Approach | How | Pros | Cons |
|---|---|---|---|
| Client-side | Client asks the registry, load-balances itself (Netflix Eureka + Ribbon style) | No extra hop; client can be smart | Discovery logic in every language you use |
| Server-side | Client calls a router / load balancer that knows the instances (AWS ALB, Kubernetes Service) | Clients stay simple | Extra hop; the router must be highly available |
| DNS | Name resolves to healthy instances (orders.service.consul, orders.prod.svc.cluster.local) | Works with any client | DNS caching can keep dead addresses (TTL) |
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
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 job | Azure pattern name | Example |
|---|---|---|
Send /orders/* to the orders service | Gateway Routing | Path-based routes |
| Join 3 backend calls into one response | Gateway Aggregation | Home screen in one round trip |
| Do TLS, auth, rate limits once | Gateway Offloading | JWT check at the edge |
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
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.
Show the full table
| Attempt | min(cap, base × 2^attempt) | Actual wait |
|---|---|---|
| Retry 1 | 200 ms | random 0 to 200 ms |
| Retry 2 | 400 ms | random 0 to 400 ms |
| Retry 3 | 800 ms | random 0 to 800 ms |
| Retry 4 | 1,600 ms | random 0 to 1.6 s |
| Retry 5 | 3,200 ms | random 0 to 3.2 s |
| Retry 6 | 6,400 ms | random 0 to 6.4 s |
sleep = random(0, min(cap, base * 2 ** attempt))
| Rule | Why |
|---|---|
| Set a timeout on every call, a bit above the callee's p99 latency | Stops threads and connections from piling up |
| Retry only safe (idempotent) operations, or use an idempotency key | A retried "charge card" must not charge twice (section 11) |
| Retry at one layer only | 3 retries at each of 5 layers can multiply load hugely |
| Cap total attempts and use a retry budget | Retries should not become the main traffic |
| Do not retry 4xx errors like 400 or 403 | They will fail again the same way |
| Have a fallback | Cached data, a default ("recommendations unavailable"), or a clear error |
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
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 setting | Default | Meaning |
|---|---|---|
failureRateThreshold | 50% | Open when half the calls in the window fail |
slidingWindowSize | 100 | Look at the last 100 calls (count-based) |
minimumNumberOfCalls | 100 | Do not judge before this many calls |
waitDurationInOpenState | 60000 ms | Stay open 60 s before half-open |
permittedNumberOfCallsInHalfOpenState | 10 | Test 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.
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
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".
| 2PC | Saga: choreography | Saga: orchestration | |
|---|---|---|---|
| Consistency | Atomic (all or nothing) | Eventual; in-between states are visible | Eventual; in-between states are visible |
| Locks | Held across services during the protocol | Only local, short | Only local, short |
| Coupling | Tight, every participant must support it | Loose, but the flow is hidden across services | Flow is explicit in one place |
| Good for | Few databases you control, low volume | 2 to 4 simple steps | Many steps, complex rules, clear monitoring |
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
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.
| Relay type | How it works | Pros | Cons |
|---|---|---|---|
| Polling publisher | A job runs SELECT … FROM outbox WHERE sent = false, publishes, marks sent | Simple, any database | Polling load and delay; ordering care with many pollers |
| Log tailing (CDC) | Debezium reads MySQL binlog or Postgres WAL (via logical decoding) and publishes changes | Low delay, no extra queries, keeps commit order | One 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).
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
| Pattern | Idea | Gain | Cost |
|---|---|---|---|
| CQRS | One model to change data, another (or many) to read it | Each side scales and is shaped for its job; reads become cheap lookups | Two models to keep in step; read-after-write may show old data |
| Event sourcing | Store every change as an event; current state = replay of events | Full audit trail, time travel, new views by replaying | Event schema changes are hard; need snapshots for long histories; querying needs CQRS |
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
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.
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 choice | Good 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) |
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
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.
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
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.
| Gain | Cost |
|---|---|
| Same resilience and security for every language | Extra hop and CPU/memory per pod |
| Canary traffic splits by config | A complex system to learn, upgrade and debug |
| Uniform metrics and tracing | Retries in both the app and the mesh can stack up |
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
| Pattern | How | Rollback | Cost / risk |
|---|---|---|---|
| Rolling | Replace instances a few at a time | Roll forward or back, slowly | Old and new run together for a while; no extra fleet. Kubernetes default: max surge 25%, max unavailable 25% |
| Blue-green | Run a full new copy (green) beside the old (blue); switch traffic at once | Switch back instantly | Double the machines during the switch; database changes must work for both |
| Canary | Send a small share of users to the new version, watch metrics, grow it | Send the share back to 0 | Needs good metrics and traffic splitting |
| Feature flags | Ship code turned off; turn it on per user or % at runtime | Turn the flag off, no deploy | Old 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.
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 |
|---|---|---|
| New product, small team | Modular monolith | Boundaries are still unknown; one deploy is cheap |
| Many teams blocked on one release train | Split into services along bounded contexts | Independent deploys per team |
| User is waiting for the answer | Sync call (REST / gRPC) with timeout + retry + breaker | Needs an immediate reply |
| Work can happen a moment later, many readers | Async events | Decoupling, spikes absorbed |
| Web and mobile need very different data | BFF per client | Each team shapes its own API |
| Calling a flaky dependency | Timeout, jittered retries, circuit breaker, bulkhead, fallback | Contain failures |
| Business transaction across services | Saga (orchestration if many steps) | No cross-service locks |
| Must save data and publish an event | Transactional outbox (+ CDC at scale) | No dual write |
| Reads far outnumber writes and need other shapes | CQRS read models | Cheap, shaped reads |
| Full audit history is required | Event sourcing | History is the data |
| Client may retry a POST | Idempotency key | No double charges |
| Replacing a legacy system | Strangler fig | Small, reversible steps |
| Many services, many languages, need mTLS | Service mesh | Uniform plumbing |
| Risky change to production | Canary + feature flag | Small blast radius, fast rollback |
—Common mistakes and traps
—Interview questions
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.
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.
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.
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%.
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.
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.
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.
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.
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.
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.
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.
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.
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".
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.
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.
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.
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.
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.
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.
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
| Fact | Value | Source |
|---|---|---|
| Microservices article | Lewis & Fowler, March 2014; "smart endpoints and dumb pipes" | martinfowler.com |
| MonolithFirst | Fowler, June 2015 | martinfowler.com |
| Circuit breaker states | Closed → Open → Half-open | Fowler, Azure |
| Resilience4j defaults | 50% failure rate, window 100, open 60 s, 10 half-open calls | resilience4j docs |
| Full jitter | random(0, min(cap, base * 2 ** attempt)) | AWS Architecture Blog |
| Retry amplification | 3 tries × 5 layers = up to 243x load | AWS Builders' Library |
| Chained availability | 0.999³ ≈ 99.7% | Arithmetic |
| Saga | Local transactions + compensating actions; choreography or orchestration | microservices.io |
| 2PC in microservices | "2PC is not an option" | microservices.io |
| Outbox relays | Polling publisher, or log tailing (CDC: binlog / WAL) | microservices.io, Debezium |
| Kubernetes rolling update | Max surge 25%, max unavailable 25% | kubernetes.io |
| Kubernetes Service DNS | svc.ns.svc.cluster.local (default domain) | kubernetes.io |
| Istio ambient (no sidecars) | GA in 1.24, Nov 2024 | istio.io |
| Stripe idempotency keys | Up to 255 characters; prunable after at least 24 h (API v1) | docs.stripe.com |
Idempotency-Key header | IETF Internet-Draft, not an RFC yet | datatracker.ietf.org |
| Hystrix | Maintenance mode; use Resilience4j | GitHub README |
| Feature toggle kinds | Release, experiment, ops, permissioning | Pete Hodgson, martinfowler.com |
—Sources
| What | Where |
|---|---|
| Microservices, monolith first, bounded context, circuit breaker, CQRS, event sourcing, strangler fig, blue-green, canary, feature toggles | martinfowler.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, strangler | microservices.io (Chris Richardson) |
| Retry, circuit breaker, bulkhead, saga, compensating transaction, sidecar, strangler fig, BFF, gateway patterns | Azure Architecture Center |
| Backoff and jitter, retries | AWS Architecture Blog, AWS Builders' Library |
| Circuit breaker defaults | resilience4j.readme.io, Hystrix README |
| CDC and outbox tooling | debezium.io |
| Kubernetes, Consul, Istio, Envoy | kubernetes.io, developer.hashicorp.com, istio.io, envoyproxy.io |
| Idempotency keys | docs.stripe.com, IETF draft |
| Not proven | 2PC "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 |