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:
business capability
|
clear ownership
|
owned data
|
stable API/event contract
|
independent deployability
|
independent failure/scaling behaviorA 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:
Kong
AWS API Gateway
Azure API Management
Spring-based gateway
NestJS / Express BFF or edge layerThe gateway centralizes appropriate cross-cutting concerns:
routing
authentication
rate limits
quotas
correlation
TLS
public API structureIt 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:
booking-service
|
http://payment-service
|
Kubernetes Service
|
ready payment podsConfiguration 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:
reserve flight
reserve hotel
capture payment
confirm bookingWhen 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.
same operation + same idempotency key
|
v
same effective resultDetailed guide:
6. Transactional Outbox
The dual-write problem:
save DB SUCCESS
publish Kafka FAILUREis one of the easiest ways to lose a business event.
Outbox changes this to:
local transaction:
save business state
save event intent
publisher:
send eventConsumers 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:
CreateBooking
PlaceBid
CloseAuctionQueries answer optimized views:
SearchBookings
SupplierDashboard
BidHistoryOnly 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:
overall request deadline
|
concurrency/bulkhead
|
circuit breaker
|
small retry policy
|
per-attempt timeout
|
dependencyFor 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:
- Circuit Breaker with Resilience4j in Spring Boot
- Circuit Breaker in Node/NestJS with Opossum
- Retry with Exponential Backoff and Jitter
- Bulkhead Pattern
9. Backpressure
Kafka can accept data faster than a database or external API can process it.
The correct response is not always more consumers.
Kafka input 20k/sec
|
bounded consumer concurrency
|
database sustainable 3k/secI let Kafka lag grow safely while protecting the downstream system.
Detailed guide:
10. Retry topics and DLQ
Transient failure and poison data are different.
main topic
|
retry 1 minute
|
retry 10 minutes
|
DLQ + human/controlled replayA 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:
domain
|
internal interface
|
adapter
|
external supplierThis 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:
gateway
|- new capability -> microservice
`- remaining -> monolithCapability by capability, traffic and data ownership move.
Detailed guide:
How the patterns combine in one booking flow
A realistic simplified architecture:
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 / analyticsKubernetes 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:
gateway
Kafka
CQRS
Saga
event sourcing
Redis
service mesh
five databasesIf 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:
- What business capability does it own?
- What data does it own?
- Who can write that data?
- What is its API/event contract?
- How is it discovered?
- What is its request deadline?
- Which errors are retryable?
- Is the operation idempotent?
- What happens when a dependency is slow?
- What happens when Kafka is unavailable?
- How does a distributed transaction compensate?
- How are events published reliably?
- How are poison events handled?
- What protects a slow database?
- Can it deploy independently?
- Is DB schema backward compatible?
- Can I trace one business operation end-to-end?
- Does the team know how to recover it?
All deep-dive articles in this microservices series
- API Gateway: Kong, AWS, Azure, Spring and NestJS
- Service Discovery: Kubernetes DNS, Consul and Cloud
- Saga Pattern in Flight, Hotel, Payment and Procurement
- Idempotency in Payments, Bookings, Bids and Events
- Transactional Outbox with MySQL/PostgreSQL and Kafka
- CQRS Without Overengineering
- Circuit Breaker with Resilience4j in Spring Boot
- Circuit Breaker in Node/NestJS with Opossum
- Retry: Exponential Backoff, Jitter and Budgets
- Kafka Backpressure in NestJS
- Bulkhead Pattern
- Strangler Pattern for Monolith Modernization
- Adapter and Anti-Corruption Layer
- Kafka Retry Topics and DLQ
- Database per Service and Data Ownership
- API Composition
- Backend for Frontend
- Cache Aside with Redis
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:
what technology?
what framework?
what database?Today I start with:
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.
Your feedback helps prioritize deeper technical content.






