Microservices Patterns Guide (EN)
Microservices are not a goal. They are a tradeoff: you pay in distributed-systems complexity to gain independent deployability, team autonomy, and scalability of delivery.
This guide is âTL/Principal level theoryâ: mental models, decisions, pitfalls, and production checklists, with .NET-specific pointers.
0) First decision: should this be microservices?
Use microservices when you need (and can afford)
- Independent deployments across multiple teams
- Different scaling profiles per bounded context
- Fault isolation boundaries that matter
- Organizational scaling (many teams, clear ownership)
Prefer a modular monolith when
- Domain boundaries are unclear
- Team is small
- The main problem is product discovery, not scaling
- You canât invest in platform/operations maturity
Lead heuristic: if you cannot operate one service reliably (telemetry, CI/CD, incident response), donât multiply that problem by 20.
1) Boundary patterns: where services start and end
1.1 Bounded context (DDD) as the primary boundary
- A service should own a business capability with its own model.
- The boundary is defined by language and invariants, not by database tables.
1.2 Avoid âentity servicesâ
A service per entity (UserService, ProductService) tends to create a distributed monolith with chatty calls.
1.3 The âhigh-cohesion / low-couplingâ test
A good service boundary:
- changes frequently inside the boundary
- changes rarely across boundaries
2) Data patterns: ownership and consistency
2.1 Database-per-service (default)
Pattern: each service owns its data storage.
Benefits:
- independent schema evolution
- fault isolation
- clear ownership
Pitfalls:
- cross-service queries become harder (you need read models)
- you must accept eventual consistency
2.2 Shared database anti-pattern
Shared DB removes autonomy:
- deploy coordination
- coupling through tables
- hidden breaking changes
Exception (rare): transitional phase during a controlled migration.
2.3 Read model / materialized view
If Service A needs a view of data from Service B:
- B publishes events
- A builds a local read model (denormalized)
This is usually better than synchronous âlookupâ calls.
3) Distributed transactions: Saga pattern
3.1 What a saga is
A saga is a sequence of local transactions coordinated via messaging.
Two styles:
- Choreography: services react to events; no central coordinator
- Orchestration: a coordinator (process manager) commands participants
3.2 Choosing choreography vs orchestration
Choreography is simpler initially but can become hard to understand as flows grow.
Orchestration is better when:
- the business process is complex
- you need explicit state machine visibility
- you need strong governance over steps and retries
3.3 Compensating actions
Sagas rely on compensations:
- âcancel reservationâ
- ârefund paymentâ
- ârelease inventoryâ
Pitfall: not all actions are reversible; you may need âhuman-in-the-loopâ or âmanual reviewâ states.
4) Reliability for messaging: Outbox + Inbox
4.1 The problem
If you write DB state and publish an event separately, you can fail in-between:
- DB committed, message not published (lost event)
- message published, DB not committed (phantom event)
4.2 Transactional Outbox pattern
Pattern: store outgoing messages in the same DB transaction as your business change. Then a background dispatcher publishes them.
Key properties:
- at-least-once publishing
- requires idempotent consumers
4.3 Inbox / de-duplication
Consumers need to handle duplicates:
- store processed message IDs
- ignore repeats
5) Messaging patterns: semantics and operations
5.1 Delivery semantics
- At-most-once: may lose messages (rarely acceptable)
- At-least-once: duplicates possible (most common)
- Exactly-once: usually an illusion; it becomes âexactly-once processingâ with strict constraints
Lead principle: design for at-least-once + idempotency.
5.2 Retry + dead letter queue (DLQ)
- transient failures â retry with backoff
- poison messages â DLQ
Pitfall: infinite retries create hidden outages.
5.3 Ordering
Ordering across partitions/consumers is expensive. Most systems choose:
- ordering per key (e.g., OrderId)
- or no global ordering
6) Resilience patterns (service-to-service)
Use the classic set:
- Timeouts (always)
- Retries (only for safe/idempotent operations)
- Circuit breaker (stop cascading failures)
- Bulkheads (isolate resources)
- Rate limiting (protect dependencies)
Pitfall: âretry stormâ when many clients retry simultaneously.
.NET pointer
In .NET, resilience is typically implemented via libraries like Polly (conceptually): policy composition, jittered backoff, and per-dependency policies.
7) Observability patterns (must-have for microservices)
Microservices require tracing:
- Correlation IDs propagated across boundaries
- Distributed tracing (spans)
- Structured logs with consistent fields
- Metrics aligned to SLOs
Lead rule: if a request crosses 3 services, you must be able to answer âwhere did time go?â within minutes.
8) API and event evolution (versioning)
8.1 Backward compatibility contract
- Additive changes (optional fields) are usually safe
- Breaking changes require:
- versioned contract
- migration period
- deprecation policy
8.2 Events are contracts too
Treat event schemas like public APIs.
Pitfall: publishing âinternalâ domain objects as events (leaks invariants).
9) Deployment strategies (production maturity)
Common patterns:
- Blue/Green: fast rollback, higher cost
- Canary: gradual exposure, requires telemetry
- Feature flags: decouple deploy from release
Lead principle: do not deploy what you cannot observe.
10) .NET-specific implementation notes (conceptual)
This repo is theory-first, but the lead-level mapping in .NET is:
- Messaging: MassTransit/NServiceBus-style patterns (saga, outbox)
- Resilience: Polly-style policies (timeouts/retries/circuit breakers)
- Observability: OpenTelemetry + structured logging
- Data: EF Core with transaction boundaries and outbox table
11) Interview angle (Tech Lead)
Typical questions:
- âWhen would you choose microservices over a monolith?â
- âHow do you ensure data consistency?â
- âExplain saga vs outbox.â
- âHow do you prevent cascading failures?â
- âHow do you debug production issues across services?â
Strong answers include:
- boundaries (DDD)
- eventual consistency + read models
- outbox + idempotency
- resilience + observability + deploy strategies
12) Review checklist (production)
- [ ] Service boundaries are based on bounded contexts.
- [ ] Database-per-service enforced; shared DB is treated as a migration smell.
- [ ] Events and APIs have versioning and deprecation policy.
- [ ] Messaging is at-least-once; consumers are idempotent (inbox/dedup).
- [ ] Outbox pattern used for critical events.
- [ ] Timeouts exist everywhere; retries are bounded and safe.
- [ ] Circuit breakers/bulkheads prevent cascades.
- [ ] Tracing/logging/metrics are consistent with correlation IDs.
- [ ] Deployment strategy includes rollback and requires telemetry.