Field note / patterns

publishedupdated 2026-05-04#microservices#patterns#saga#outbox#kafka

Microservices Patterns

Principal-level microservices patterns for decomposition, sync vs async boundaries, outbox, idempotency, resilience, and operational safety.

TL;DR

  • Microservices only pay off when they remove a real scaling or coordination bottleneck.
  • Bounded contexts, outbox, idempotency, and observability are usually more valuable than any service-mesh slide.
  • I prefer fewer, better-bounded services over a large number of thin services with unclear ownership.
  • Principal-level microservices work is as much about team boundaries and failure containment as it is about APIs and code.

Context

Microservices create leverage when teams need independent deployability, targeted scaling, or clearer domain isolation. They create pain when they are adopted to feel modern or when a modular monolith would have solved the real problem more cheaply.

The question I care about is not “should we use microservices?” It is “what coupling are we removing, and what new operational complexity are we willing to own?”

The Approach

1. Start with bounded contexts, not technical slicing

I want each service to have:

  • clear domain ownership
  • its own write model
  • explicit upstream and downstream dependencies
  • a contract that can evolve without weekly coordination

2. Use sync for user-critical commands, async for propagation

My common pattern is:

  1. accept the user-critical command
  2. persist source-of-truth data
  3. publish a domain event
  4. let other services react independently

That keeps the latency-sensitive path simple while still allowing other domains to update without becoming part of one giant distributed transaction.

3. Outbox before dual-write pain arrives

If a service writes to a database and emits an event, I do not want those two actions drifting apart.

The outbox pattern gives me:

  • transactional safety at write time
  • asynchronous publication afterward
  • cleaner replay and recovery behavior
I use an outbox when the service must not commit business data without also recording the event to be published.

4. Use sagas and compensations instead of distributed transactions

For multi-step workflows, I design:

  • compensating actions
  • explicit state transitions
  • timeout handling
  • replay-safe commands

I assume retries and partial failure are normal, not exceptional.

5. Make resilience and observability part of the architecture

The system is incomplete without:

  • trace propagation
  • per-service SLIs and SLOs
  • retry and timeout policy
  • queue-depth or consumer-lag visibility
  • dead-letter and replay strategy

6. Avoid the distributed monolith trap

The anti-pattern I look for is a fleet of services that:

  • deploy together
  • share databases
  • require every feature to touch many repos
  • fail together anyway

That is not service architecture maturity. It is monolith complexity plus network overhead.

Trade-offs

  • More services improve isolation, but they also multiply deployment, observability, and contract-management cost.
  • Async workflows decouple teams well, but they are harder to debug without trace and event lineage.
  • Outbox and saga patterns improve safety, but they add implementation and operational overhead.
  • A modular monolith is often the better starting point until the team and domain boundaries are real.

References