Design and Implementation Patterns
Patterns for structuring services and their edges: strangler fig, sidecar, ambassador, anti-corruption layer, backends for frontends, gateway routing, offloading and aggregation, external configuration, compute resource consolidation and leader election.
In a nutshell
- Strangler fig replaces a legacy system piece by piece behind a routing layer.
- Sidecar and ambassador move cross-cutting concerns (proxies, TLS, retries, logging) into a helper process next to the app.
- Anti-corruption layer translates between your model and a legacy or third-party one, so their quirks do not leak in.
- Gateway patterns (routing, offloading, aggregation) and backends for frontends shape what clients see.
Strangler fig
Rewriting a large legacy system in one go is risky. Instead, put a routing layer in front, build one feature at a time in the new system, and route that feature's traffic to it. The legacy system shrinks until it can be switched off, like the strangler fig vine that slowly replaces its host tree (Martin Fowler named the pattern after it).
Strangler fig migration
Diagram as text
- Users → Router / proxy
- Router / proxy → New service: migrated
- Router / proxy → Legacy app: not yet
Sidecar and ambassador
A sidecar is a helper process deployed next to each service instance (in the same pod or host), handling shared concerns: log shipping, metrics, TLS, configuration reloads. The app stays focused on business logic, in any language. An ambassador is a sidecar specialised for outgoing calls: retries, timeouts, circuit breaking, routing. Service meshes (Istio, Linkerd) run a proxy sidecar beside every service to provide these features everywhere.
App with sidecar proxies
Diagram as text
- Orders app → Sidecar proxy: localhost
- Sidecar proxy → Sidecar proxy: mTLS, retries
- Sidecar proxy → Payments app: localhost
Anti-corruption layer
When your clean new service must talk to a legacy system or an awkward third-party API, put a translation layer between them. It converts their data model, naming and error conventions into yours, so the rest of your code never depends on theirs, and replacing them later touches one place.
Backends for frontends
A mobile app, a web app and partner integrations need different data shapes and amounts. Instead of one general API that fits none of them well, give each client type its own thin backend (a BFF), owned by the team building that client, which calls the underlying services and returns exactly what its screens need.
Gateway routing, offloading and aggregation
- Gateway routing: one public entry point routes requests to the right backend by path or host, so clients do not know the internal layout.
- Gateway offloading: move shared work into the gateway: TLS termination, authentication, rate limiting, compression.
- Gateway aggregation: one client request fans out to several services and the gateway combines the answers, saving the client many round trips (especially on mobile networks).
These are the jobs of an API gateway; see the API gateway topic.
External configuration, consolidation, leader election
- External configuration store: keep settings and feature flags in a central service (with versioning and access control) instead of inside each deployment, so they can change without redeploying.
- Compute resource consolidation: run several small, low-load components together on shared compute to cut cost and overhead, while keeping heavy or critical ones isolated.
- Leader election: when exactly one instance must coordinate (run the scheduler, own a partition), elect it through a consensus-backed lease; see the Leader election and consensus topic.
| Pattern | Use when |
|---|---|
| Strangler fig | Replacing a legacy system without a big-bang rewrite |
| Sidecar | Many services need the same helper, in any language |
| Ambassador | Outgoing calls need retries, timeouts, routing |
| Anti-corruption layer | Integrating with a legacy or messy external model |
| Backends for frontends | Different clients need very different APIs |
| Gateway routing / offloading / aggregation | One entry point, shared edge work, fewer client calls |
| External configuration store | Config must change without redeploys |
| Compute resource consolidation | Many tiny workloads wasting resources |
| Leader election | Exactly one instance must coordinate |
Common mistakes
- Big-bang rewrites instead of incremental strangling.
- A BFF that grows business logic and becomes another monolith.
- Letting a third-party API's model spread through your codebase.
- Adding a service mesh before having enough services to justify it.
Interview questions
Try answering out loud before you open each one.
How would you migrate a monolith to services safely?
Use the strangler fig pattern: put a proxy or gateway in front, extract one capability at a time behind a clear interface (with an anti-corruption layer where the old model is messy), route its traffic to the new service, compare results, then delete the old code. Start with a low-risk, well-bounded capability.
Check yourself
Related topics
Further reading
- StranglerFigApplication Martin Fowler
- Sidecar pattern Microsoft Azure Architecture Center
- Backends for Frontends pattern Microsoft Azure Architecture Center
- Anti-corruption Layer pattern Microsoft Azure Architecture Center
Your notes
Saved in this browser as you type.
Finished this topic?
Mark it read to track your progress, then carry on with the next one.