🏛️

Microservices Patterns Guide (EN)

System Architecture Intermediate 5 min read 900 words

Tech Lead / Principal-level microservices patterns: boundaries, data consistency, messaging, resilience, observability, and deployment

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.

📚 Related Articles