Microservices Design Patterns I Have Used in Real Enterprise Projects

An experience-based guide to the microservice patterns I have used across travel, retail master-data, financial services, procurement/RFx, e-auction and modernization projects, with links to detailed production implementations and code.

Romharshan Singh
Romharshan SinghSenior Solution Architect • AI & Cloud Mentor
1 September 20268 min read0 viewsUpdated 1 Sept 2026
Microservices Design Patterns I Have Used in Real Enterprise Projects

Microservices Design Patterns I Have Used in Real Enterprise Projects

Why I am writing this differently from a pattern catalogue

I have spent many years building and modernizing systems in travel, retail, financial services, procurement, RFx/e-auction, data distribution and other enterprise environments.

Over that time I have used Spring Boot, Node.js, NestJS, Java, Kafka, Redis, MySQL/PostgreSQL/MongoDB, Kubernetes and several API gateway technologies.

The lesson I keep coming back to is simple:

microservices are not created by splitting one codebase into many repositories.

A microservice architecture becomes useful only when the boundaries, data ownership, failure behavior, deployment ownership and operational model are clear.

This series is intentionally written from my own architecture perspective. Project examples are generalized so I can explain the engineering decisions without publishing confidential client implementation details.

Examples include:

  • travel-booking work at Thomas Cook, where flight, hotel, payment, supplier and booking workflows made distributed transactions and adapters important;
  • a Lululemon product/location master-data environment where cloud API management and distributed integration patterns mattered;
  • RFx/e-auction work associated with Reliance / Tech Mahindra, where supplier identity, bidding, auditability, concurrency and duplicate operations mattered;
  • financial-services work where Kong and stronger gateway/security policy were useful;
  • modernization of project/tender-information style platforms where a Strangler migration was safer than a full rewrite.

1. What I call a microservice

I do not define a microservice by line count.

I look for:

plaintext
business capability
      |
clear ownership
      |
owned data
      |
stable API/event contract
      |
independent deployability
      |
independent failure/scaling behavior

A service that cannot deploy without coordinating six other services and writes directly into their tables is usually a distributed monolith.

2. The first pattern: API Gateway

I have used different gateways because the surrounding platform was different:

plaintext
Kong
AWS API Gateway
Azure API Management
Spring-based gateway
NestJS / Express BFF or edge layer

The gateway centralizes appropriate cross-cutting concerns:

plaintext
routing
authentication
rate limits
quotas
correlation
TLS
public API structure

It does not own core domain behavior.

Detailed guide:

3. Service discovery and runtime configuration

Once instances become dynamic, hard-coded IP addresses stop working.

In Kubernetes I normally use native Service/DNS discovery:

plaintext
booking-service
      |
http://payment-service
      |
Kubernetes Service
      |
ready payment pods

Configuration and secrets are separate concerns. Non-secret runtime values belong in external configuration; sensitive values belong in Secrets/Vault/cloud secret managers.

Detailed guide:

4. Saga for real business transactions

Travel booking is a classic distributed transaction:

plaintext
reserve flight
reserve hotel
capture payment
confirm booking

When payment fails after suppliers have committed, there is no shared SQL rollback.

I use a Saga with persisted state and compensating actions.

Procurement/RFx workflows have the same characteristic over longer time periods.

Detailed guide:

5. Idempotency: because networks repeat operations

A booking request can succeed on the server and time out on the phone.

An auction bid can be retried.

A payment callback can arrive twice.

A Kafka consumer can redeliver.

I therefore design critical side effects around stable operation identity.

plaintext
same operation + same idempotency key
          |
          v
same effective result

Detailed guide:

6. Transactional Outbox

The dual-write problem:

plaintext
save DB       SUCCESS
publish Kafka FAILURE

is one of the easiest ways to lose a business event.

Outbox changes this to:

plaintext
local transaction:
  save business state
  save event intent

publisher:
  send event

Consumers remain idempotent because duplicate publication is still possible.

Detailed guide:

7. CQRS when reads and writes genuinely differ

I use CQRS conceptually before I use it physically.

Commands express business change:

plaintext
CreateBooking
PlaceBid
CloseAuction

Queries answer optimized views:

plaintext
SearchBookings
SupplierDashboard
BidHistory

Only when performance/shape justify it do I add independent projections/read stores.

Detailed guide:

8. Timeout, retry, circuit breaker and bulkhead

These are not independent decorations.

My dependency policy usually looks conceptually like:

plaintext
overall request deadline
        |
concurrency/bulkhead
        |
circuit breaker
        |
small retry policy
        |
per-attempt timeout
        |
dependency

For Spring Boot I have used Resilience4j style policies.

For Node/NestJS the same concepts can be implemented with Opossum and explicit concurrency control.

Detailed guides:

9. Backpressure

Kafka can accept data faster than a database or external API can process it.

The correct response is not always more consumers.

plaintext
Kafka input 20k/sec
      |
bounded consumer concurrency
      |
database sustainable 3k/sec

I let Kafka lag grow safely while protecting the downstream system.

Detailed guide:

10. Retry topics and DLQ

Transient failure and poison data are different.

plaintext
main topic
  |
retry 1 minute
  |
retry 10 minutes
  |
DLQ + human/controlled replay

A DLQ needs ownership and replay tooling, not just a topic name.

Detailed guide:

11. Database per service

The rule I care about:

one service owns writes to its business data.

That does not mean every service needs a separate physical RDS server.

It means other services cannot bypass the owner and update its tables directly.

Detailed guide:

12. API composition and BFF

For small read aggregations I use API composition.

For web/mobile client-specific shapes I use a BFF.

When fan-out becomes large, I prefer a prepared read model rather than turning the BFF into a distributed SQL engine.

Detailed guides:

13. Adapter and Anti-Corruption Layer

Travel suppliers and legacy systems rarely share my internal domain model.

I isolate them:

plaintext
domain
 |
internal interface
 |
adapter
 |
external supplier

This is especially useful when replacing a legacy system because I do not want old field names/status codes to become the vocabulary of the new service.

Detailed guide:

14. Cache Aside

Redis can protect repeated reads, but cache invalidation and stale-data semantics must be explicit.

I add a cache after measuring the bottleneck, not because a microservice diagram expects Redis.

Detailed guide:

15. Strangler migration

For legacy tender/project information systems, I prefer progressive extraction:

plaintext
gateway
  |- new capability -> microservice
  `- remaining      -> monolith

Capability by capability, traffic and data ownership move.

Detailed guide:

How the patterns combine in one booking flow

A realistic simplified architecture:

plaintext
Mobile / Web
     |
API Gateway
     |
Booking Service
     |
Saga Orchestrator
 |       |        |
Flight  Hotel   Payment
Adapter Adapter Adapter
 |       |        |
breaker/retry/bulkhead
     |
external suppliers

Booking DB
  |
Outbox
  |
Kafka
  |
idempotent consumers
  |
notifications / audit / analytics

Kubernetes provides service discovery.

Config/secret stores provide environment and credentials.

Metrics/logs/traces expose the behavior.

The anti-pattern I avoid: using every pattern

A system does not become better because it contains:

plaintext
gateway
Kafka
CQRS
Saga
event sourcing
Redis
service mesh
five databases

If the business problem is a small internal workflow, a modular monolith can be far safer.

I use patterns when a specific constraint appears.

My microservice production checklist

Before I call a service production-ready I want answers to:

  1. What business capability does it own?
  2. What data does it own?
  3. Who can write that data?
  4. What is its API/event contract?
  5. How is it discovered?
  6. What is its request deadline?
  7. Which errors are retryable?
  8. Is the operation idempotent?
  9. What happens when a dependency is slow?
  10. What happens when Kafka is unavailable?
  11. How does a distributed transaction compensate?
  12. How are events published reliably?
  13. How are poison events handled?
  14. What protects a slow database?
  15. Can it deploy independently?
  16. Is DB schema backward compatible?
  17. Can I trace one business operation end-to-end?
  18. Does the team know how to recover it?

All deep-dive articles in this microservices series

Architect's final take

What changed most over my career is not the number of frameworks. It is my attention to failure.

Earlier architecture discussions often focused on:

plaintext
what technology?
what framework?
what database?

Today I start with:

plaintext
what happens if this call repeats?
what happens if it times out?
what if the DB commits and Kafka does not?
what if this provider is slow for 20 minutes?
what if we need to roll back while v1 and v2 coexist?

That is where design patterns stop being interview answers and become engineering tools.

Was this article useful?

Your feedback helps prioritize deeper technical content.

Romharshan Singh
ABOUT THE AUTHOR

Romharshan Singh

Senior Solution Architect and Full Stack Technology Leader with 20+ years of enterprise engineering experience across AI, cloud, distributed systems, Java, Node.js, React, Angular, Kafka and Kubernetes.