API Composition
A deep, beginner-friendly walkthrough of the API Composition pattern — why microservices need it, how it works under the hood, and how companies like Netflix, Amazon, Uber, and Google use it at scale.
One Screen, Many Services Behind It
API Composition is the microservices pattern that hides the complexity of “many independent backends” behind a single, unified response to the client.
Imagine you order a pizza online. The app shows you the price, the estimated delivery time, the driver’s name, and your loyalty points — all on one screen. That single screen almost certainly did not come from one place. Behind the scenes, a “pricing service” knows the price, a “logistics service” knows the delivery time, a “driver service” knows the driver’s name, and a “loyalty service” knows your points. Somebody has to go ask each of these services a question and glue the answers together into one neat response. That “someone” is API Composition.
API Composition is a design pattern used mainly in microservices architectures, where a single client request is fulfilled by calling multiple backend services, and then combining (“composing”) their individual responses into one unified result before sending it back to the client.
Think of a restaurant waiter. You do not walk into the kitchen and talk to the chef, the bartender, and the dessert cook separately. You tell the waiter what you want, the waiter visits each station, collects your food and drink, arranges everything nicely on a tray, and brings it all to your table at once. The waiter is doing “composition” — visiting multiple sources and combining the results into one delivery.
Where Did This Pattern Come From?
To understand why API Composition exists, we need a tiny bit of history. In the early 2000s, most large applications were built as monoliths — one big codebase, one big database, one deployable unit. If you wanted the price and the delivery time of an order, you simply ran one SQL query that joined a few tables in the same database. Easy.
Around 2011–2014, companies like Netflix, Amazon, and Uber began breaking their monoliths into microservices — small, independently deployable services, each owning its own private database. This solved a lot of problems (teams could move faster, services could scale independently, failures were isolated), but it created a brand new problem: data that used to live in one table now lives in five different databases, owned by five different teams, that cannot be joined with SQL anymore.
API Composition emerged directly as an answer to this gap. It became one of the standard patterns documented in microservices literature (notably popularized by Chris Richardson’s microservices.io pattern catalog) alongside close relatives like the API Gateway pattern and the Backend-for-Frontend (BFF) pattern.
Early 2000s — The Monolith Era
Single database, SQL joins solve everything. No composition needed.
2011–2014 — Rise of Microservices
Netflix, Amazon, and others split monoliths into independently owned services and databases.
~2014 — The “Database per Service” Problem
Cross-service joins become impossible. Teams need a way to combine data from multiple services.
2015–2018 — Pattern Formalized
API Composition, API Gateway, and BFF patterns get named and documented as standard solutions.
2018–Today — GraphQL & Federation
GraphQL and Apollo Federation offer a more declarative, standardized way to do composition at scale.
Why Microservices Need Composition in the First Place
Let’s make the problem concrete with our pizza-delivery example. Composition exists to solve a specific, sharp pain that only appears once you split a monolith into microservices.
In a monolith, getting an order’s full details might be one query:
SELECT o.id, o.price, d.eta, u.name AS driver, l.points
FROM orders o
JOIN delivery d ON d.order_id = o.id
JOIN users u ON u.id = d.driver_id
JOIN loyalty l ON l.user_id = o.user_id
WHERE o.id = 42;One database, one query, done. But once orders, delivery, users, and loyalty each live inside their own microservice with their own private database, this SQL simply cannot exist anymore — because a database cannot JOIN across a network boundary into someone else’s database. That is the core motivation.
You could let the mobile app call the Order, Delivery, Driver, and Loyalty services directly and merge the answers itself. This is called “client-side composition,” and it sounds simple — but it means every mobile app, web app, and partner integration has to duplicate the same merging logic, handle four separate network failures, and expose internal service URLs to the public internet. This gets painful fast, especially on slow mobile networks.
What Problem Does API Composition Actually Solve?
Fragmented Data
Related data is scattered across services that each own a private database, so no single query can retrieve it all.
Client Complexity
Without composition, every client (mobile, web, partner) must know about and call many internal services itself.
Chatty Networks
Mobile clients making 5+ round trips over a weak connection leads to slow, unreliable user experiences.
Leaky Internals
Exposing every microservice directly to the public internet increases the attack surface and couples clients to internal architecture.
API Composition solves all four by introducing a single component — the composer (often called an “API Composer” or “aggregator”) — that sits between the client and the microservices, does all the fan-out calling and merging work once, and gives the client one clean response.
The Composition Vocabulary, in Plain English
Before going further, let’s define the vocabulary clearly. These terms recur through every remaining section, so getting them straight now pays off.
API Composer
The component (a service, a gateway, or a piece of backend code) responsible for calling multiple downstream services and merging their responses. Also called the “aggregator.”
Provider Service
Any of the underlying microservices that the composer calls to gather a piece of the data — e.g., the Order service or the Loyalty service.
Composite Response
The single, unified object sent back to the client, built by combining data from all provider services.
Fan-out
The act of the composer sending requests out to multiple provider services at once (or in sequence).
Scatter-Gather
Another name for the same overall technique: “scatter” requests out to many services, then “gather” the results back together.
Partial Failure
When some provider services succeed and others fail or time out — a defining challenge of composition that does not exist with a single database query.
Client-Side vs Server-Side Composition
There are two flavors of this pattern, and understanding the difference matters a lot:
✓ Server-Side Composition
- A backend component (API Gateway, BFF, or dedicated Composer service) does the fan-out and merging.
- Client makes just one request.
- Internal service topology stays hidden from the client.
- Most common and generally recommended approach.
✗ Client-Side Composition
- The client (browser, mobile app) itself calls multiple services directly.
- Multiple round trips, especially painful on mobile networks.
- Internal services exposed publicly, harder to secure and evolve.
- Usually only reasonable for very simple or internal tools.
Why the Distinction Really Matters
The choice between client-side and server-side composition is not just a stylistic preference — it directly affects how easily you can evolve your microservice architecture. With server-side composition, you can split a service in two next quarter, rename an internal endpoint, or move it to a different region, and no external client ever notices; the composer absorbs the change. With client-side composition, every one of those internal moves risks breaking mobile app builds still in use by customers who have not upgraded, which is why nearly every mature production system converges on the server-side variant as its default posture.
Where the Composer Actually Sits
Let’s zoom out and look at where the composer physically sits in a typical microservices architecture.
Notice the composer sits behind the API Gateway (sometimes the composer is logic embedded inside the gateway itself, sometimes it is a standalone service). Let’s break down each piece.
API Gateway
The single public entry point for all client traffic. Handles routing, authentication, rate limiting, and often hosts composition logic.
API Composer
The orchestrating brain: knows which provider services to call, in what order, and how to merge their fields into one response shape.
Provider Services
Independent microservices, each with a narrow responsibility and its own database, exposing a small API.
Service Registry
A directory (like Eureka or Consul) that helps the composer find the current network address of each provider service.
Cache Layer
Sits between composer and providers (or in front of the composite response) to avoid re-fetching unchanged data on every call.
Circuit Breaker
A safety mechanism that stops the composer from repeatedly hammering a provider service that is already failing.
Where Composition Logic Physically Lives
| Placement | Description | When It Fits |
|---|---|---|
| Inside API Gateway | Composition rules configured or coded directly in the gateway (e.g., Kong, Apigee plugins) | Simple merges, small teams, few clients |
| Dedicated Composer Service | A standalone backend service whose only job is orchestration and merging | Complex business logic, reused across many clients |
| Backend-for-Frontend (BFF) | One composer per client type (mobile-BFF, web-BFF), tailored to that client’s exact needs | Very different client needs (mobile vs. web vs. smartwatch) |
| GraphQL Gateway | A GraphQL layer (e.g., Apollo Federation) declaratively stitches together data from multiple services | Large orgs with many teams and evolving query needs |
What Actually Happens When a Request Arrives
Let’s trace exactly what happens, technically, when a composed request arrives — from the client’s single HTTP call to the merged JSON response coming back.
Request Arrives
The client sends GET /orders/42/summary to the API Gateway.
Composer Identifies Required Data
The composer inspects the request and determines it needs data from the Order, Delivery, Driver, and Loyalty services.
Fan-out (Scatter)
The composer sends requests to each provider — ideally in parallel, using async I/O or thread pools, not one after another.
Await Responses
The composer waits for all responses, applying a timeout per call so one slow service cannot stall everything forever.
Merge (Gather)
Successful responses are stitched into one object matching the shape the client expects.
Handle Partial Failures
If a non-critical service (e.g., Loyalty) failed or timed out, the composer may return the rest of the data with that field marked null or omitted, rather than failing the whole request.
Return Composite Response
One JSON object goes back to the client — it never knows four services were involved.
Java Example: A Simple Parallel Composer
Here is a simplified Java example using CompletableFuture to fan out requests in parallel and merge them — this is the heart of how a real-world composer works.
public class OrderSummaryComposer {
private final OrderServiceClient orderClient;
private final DeliveryServiceClient deliveryClient;
private final DriverServiceClient driverClient;
private final LoyaltyServiceClient loyaltyClient;
public OrderSummary getOrderSummary(String orderId) {
// 1. Fan-out: fire all calls in parallel, non-blocking
CompletableFuture<Order> orderFuture =
CompletableFuture.supplyAsync(() -> orderClient.getOrder(orderId));
CompletableFuture<Delivery> deliveryFuture =
CompletableFuture.supplyAsync(() -> deliveryClient.getDelivery(orderId));
// Driver depends on delivery.getDriverId(), so it chains after delivery
CompletableFuture<Driver> driverFuture = deliveryFuture.thenCompose(delivery ->
CompletableFuture.supplyAsync(() -> driverClient.getDriver(delivery.getDriverId())));
// Loyalty is "best effort" — allowed to fail without breaking the response
CompletableFuture<Loyalty> loyaltyFuture =
CompletableFuture.supplyAsync(() -> loyaltyClient.getPoints(orderId))
.exceptionally(ex -> Loyalty.unavailable()); // fallback on failure
// 2. Gather: wait for everything, with an overall timeout
CompletableFuture.allOf(orderFuture, deliveryFuture, driverFuture, loyaltyFuture)
.orTimeout(2, TimeUnit.SECONDS)
.join();
// 3. Merge into one composite response
return new OrderSummary(
orderFuture.join(),
deliveryFuture.join(),
driverFuture.join(),
loyaltyFuture.join()
);
}
}If each of the four calls takes 100 ms and you call them one after another (sequentially), the total wait is 400 ms. If you call them all at once (in parallel) and just wait for the slowest one, the total wait is roughly 100 ms — a 4x speedup. This is the single most important performance decision in a composer.
Dependency-Aware Fan-out
Notice one subtle detail in the code above: the Driver call does not run in the very first batch, because it needs the driver ID that only comes back inside the Delivery response. Real composers frequently have this shape — a mix of independent calls that can go in parallel from the start, plus one or two “dependent” calls that must chain after an earlier response. A well-written composer is really a small directed acyclic graph of calls, where every branch that can run in parallel does, and only the truly dependent hops wait for their upstream input.
One Request, Many Fates
Let’s visualize the full lifecycle of a single composed request, including the tricky bit: what happens when one service fails while the others succeed.
Notice that even though the Loyalty service failed, the overall request still succeeds — this is a deliberate design decision called graceful degradation, and it is one of the most important lifecycle behaviors of a well-built composer.
The Lifecycle of a Composite Field
- Request mapping — the composer decides which downstream calls the incoming request requires.
- Dispatch — calls go out, typically via HTTP/REST, gRPC, or message queues.
- Timeout & retry — each call has its own timeout budget and possibly a retry policy.
- Response validation — the composer checks each response is well-formed before using it.
- Merge / transform — fields are renamed, reshaped, or computed (e.g., converting cents to dollars) to match the client’s expected contract.
- Caching decision — the composer may cache the composite result for a short TTL to save future calls.
- Response delivery — the final JSON (or GraphQL) payload is returned to the client.
What Composition Gives You — and What It Costs
API Composition is a high-leverage pattern, but it introduces its own operational surface area. Here is what you gain and what you pay.
✓ Advantages
- Clients make one request instead of many — simpler client code, fewer round trips.
- Internal microservice topology is hidden from the outside world.
- Centralizes cross-cutting concerns like auth, caching, and error handling.
- Provider services stay small, focused, and independently deployable.
- Enables graceful degradation — partial data beats a total failure.
✗ Disadvantages
- Adds an extra network hop and a new component to build and operate.
- The composer can become a bottleneck or single point of failure if not built carefully.
- Overall latency is bound by the slowest provider call in the fan-out.
- Handling partial failures correctly adds real complexity.
- Risk of the composer becoming a “mini-monolith” if too much business logic accumulates there.
Key Trade-offs to Think About
| Trade-off | Choice A | Choice B |
|---|---|---|
| Call style | Parallel (fast, more complex code) | Sequential (simple, slower) |
| On partial failure | Return partial data (available, less complete) | Fail the whole request (consistent, less resilient) |
| Where logic lives | Inside API Gateway (fewer moving parts) | Dedicated Composer service (more scalable, more to operate) |
| Query flexibility | GraphQL federation (flexible, more setup cost) | Fixed REST composite endpoint (simple, less flexible) |
Fast Composites Are Engineered, Not Accidental
Because a composed request depends on multiple downstream calls, performance needs deliberate engineering — it does not happen by accident.
The Golden Rule: Parallelize Independent Calls
As shown earlier, calls that do not depend on each other’s results should always run concurrently. Calls that do depend on a previous result (like needing the delivery record before you can look up the driver) must be chained, but everything else should fan out at once.
Timeout Budgets
A composer should set an overall deadline for the whole composite request (say, 2 seconds), and divide that budget sensibly across the calls it makes, rather than letting any single slow provider eat the whole budget.
Scaling the Composer
- Stateless design — composers should hold no session state, so you can run many identical instances behind a load balancer.
- Connection pooling — reuse HTTP connections to downstream services instead of opening new ones per request.
- Async I/O — non-blocking frameworks (like reactive Java with WebFlux, or Node.js) let one instance handle thousands of in-flight composite requests without needing a thread per request.
- Caching — cache frequently requested, slow-changing composite results (e.g., product catalog data) to avoid unnecessary fan-out entirely.
A common mistake is not noticing that your composite endpoint’s latency is entirely dictated by your slowest downstream service. If Loyalty occasionally takes 3 seconds, every single composite request that includes loyalty data will feel slow — even though Order and Delivery respond in 50 ms. Aggressive per-call timeouts and fallbacks are essential.
Batching and Coalescing Requests
A subtler performance technique is request coalescing: if the composer receives many concurrent requests that all need the same downstream data (for example, hundreds of users viewing the same popular product page in the same second), a well-tuned composer can consolidate them into a single downstream fetch and share the result across every waiting request, rather than firing one identical downstream call per incoming user request. Combined with a small in-memory cache, this can turn a thundering herd of duplicate downstream calls into one, which is enormously helpful at scale.
Composition Introduces More Places to Fail
Because composition introduces more moving parts (and more network calls) than a single database query, reliability has to be designed in explicitly.
Circuit Breakers
A circuit breaker is like an electrical fuse: if a provider service starts failing repeatedly, the circuit breaker “trips” and the composer stops calling it for a while (returning a fast fallback instead), giving the struggling service room to recover instead of being bombarded further.
Bulkheads
A bulkhead (borrowed from ship design, where separate watertight compartments stop one flooded section from sinking the whole ship) isolates resources per downstream call — for example, using a separate thread pool for calls to the Loyalty service, so if Loyalty gets slow, it cannot exhaust the threads needed to call Order and Delivery.
Retries with Backoff
Transient failures (a brief network blip) can be retried, but retries must use exponential backoff with jitter — waiting slightly longer between each retry, with some randomness — to avoid a “retry storm” where every client retries at exactly the same moment and overwhelms an already-struggling service.
✓ Reliability Techniques
- Circuit breakers per downstream dependency
- Bulkheaded thread/connection pools
- Sensible per-call and overall timeouts
- Fallback values for non-critical fields
- Health checks and readiness probes
✗ Failure Modes to Guard Against
- Cascading failures (one slow service takes down the whole composer)
- Thread pool exhaustion from one misbehaving dependency
- Retry storms amplifying an outage
- Composer itself becoming a single point of failure
A Composer Sits in a Privileged Position
The composer sits in a privileged position — it talks to many internal services on behalf of the client — so it needs careful security treatment.
- Authentication propagation — the composer typically validates the client’s token (e.g., a JWT) once at the gateway, then forwards a trusted internal token or context to downstream services, rather than making every provider re-validate the raw client credential.
- Least privilege — each provider service should only expose the fields the composer actually needs, not entire internal records.
- Field-level authorization — the composer must be careful not to accidentally merge sensitive fields (like another user’s data) into a response just because a downstream call returned them.
- mTLS between services — mutual TLS ensures the composer and provider services trust each other’s identity on the internal network.
- Rate limiting — since one client request can fan out into several backend calls, a small client-side attack can amplify into a much larger load on internal services; rate limiting at the gateway protects against this.
Because the composer touches multiple services, it is easy to accidentally include more data in the composite response than the client should see — for example, including an internal risk score or another user’s private notes. Always define an explicit, reviewed response contract rather than just merging whatever the providers return.
Amplification and Abuse
Because a composite endpoint quietly turns one incoming request into several backend calls, it also silently amplifies whatever load hits it. That is fine for legitimate traffic, but it is also an attractive target for someone trying to abuse the system: a request that costs an attacker 1 ms of network effort can trigger 5 or 10 backend calls, each with its own database query. Composers therefore usually pair per-user rate limits at the gateway with per-dependency circuit breakers, so a burst of malicious traffic cannot silently become a burst of expensive downstream work.
Which Downstream Call Made This Slow?
Because a single client request now touches several services, observability has to be able to answer: “which downstream call made this request slow or broken?”
Distributed Tracing
Each incoming request is tagged with a unique trace ID that is passed along to every downstream call. Tools like Jaeger, Zipkin, or OpenTelemetry let you visualize the entire fan-out as one connected trace, showing exactly how long each provider call took — making it trivial to spot which particular hop is dragging down the composite latency for a given request.
Per-call latency
Track p50/p95/p99 latency for each downstream dependency separately, not just the overall composite endpoint.
Error rate per dependency
A spike in Loyalty service errors should be visible on its own, not hidden inside an aggregate “5% error rate.”
Circuit breaker state
Dashboards should show which circuits are open, half-open, or closed in real time.
Partial failure rate
Track how often the composite response is returned with missing/degraded fields — a leading indicator of downstream trouble.
Structured Logging
Logs from the composer should include the trace ID, the target service, response time, and status for every downstream call — this makes it possible to reconstruct exactly what happened for any given failed or slow request after the fact.
Alerting on the Right Things
A subtle but important observability lesson for composers: alert on per-dependency SLOs rather than only on the aggregate composite endpoint’s SLO. A composite endpoint may still meet its overall latency target while one specific dependency is quietly degrading enough to cause partial failures on a growing fraction of requests. Separate per-dependency dashboards and alerts catch that class of slow-motion incident well before the aggregate metric would ever look bad enough to page someone.
How Composers Actually Ship
API Composers are typically deployed the same way as any other microservice: as containerized applications (Docker) orchestrated by Kubernetes, often sitting right behind a managed API Gateway.
Containers & Kubernetes
The composer runs as a stateless Deployment with multiple replicas, scaled horizontally based on CPU or request-rate metrics.
Managed API Gateways
Cloud services like AWS API Gateway, Apigee, or Kong can host lightweight composition/aggregation logic directly.
Serverless Composition
AWS Lambda or similar functions can act as lightweight composers for simpler aggregation needs, scaling automatically with traffic.
Service Mesh
Tools like Istio or Linkerd can handle retries, timeouts, and mTLS between the composer and providers transparently.
Rolling Out Changes Safely
Because the composer depends on the contracts of multiple provider services, deployments should use canary releases or blue-green deployments, along with contract testing (e.g., Pact) to catch breaking changes in provider APIs before they reach production.
Multi-Region Considerations
For globally distributed applications, deploying composers close to their users (in multiple regions) dramatically reduces perceived latency — but it also means each regional composer instance needs local, low-latency access to its downstream services (or fast read replicas thereof), because a composer in Singapore calling providers only in Virginia will still be as slow as any other cross-region round trip. Regional composers therefore typically pair with regional deployments (or at least regional caches) of the provider services they call most.
The Composer Never Touches a Database Directly
Composition sits carefully above the data layer: it talks only to service APIs, never to their databases directly. Here is how that plays out end-to-end.
Databases
A defining feature of the microservices world that necessitates composition is the database-per-service pattern — each provider service owns its own database (SQL or NoSQL) and never lets another service query it directly. The composer never touches a database directly; it only talks to service APIs.
Caching
Caching is one of the most effective performance levers for a composer:
- Per-provider caching — cache individual downstream responses (e.g., product details that rarely change) to skip redundant calls.
- Composite response caching — cache the fully merged result for a short TTL (e.g., 30 seconds) when slight staleness is acceptable.
- Cache invalidation — providers can publish change events (via a message broker) that tell the cache layer to invalidate stale composite data.
Load Balancing
Both the client-to-composer hop and the composer-to-provider hops need load balancing:
| Layer | Typical Technique |
|---|---|
| Client → Composer | L7 load balancer / API Gateway distributing across composer replicas |
| Composer → Provider | Client-side load balancing (via service registry) or a service mesh sidecar proxy |
Composition Across REST, GraphQL, gRPC and BFFs
API Composition is deeply tied to how microservices expose their APIs. Let’s look at how it plays with the common API styles.
REST Composition
Composer calls several REST endpoints and manually merges JSON responses into a new composite JSON shape.
GraphQL Federation
Each service exposes a “subgraph”; a gateway (e.g., Apollo Router) stitches them into one federated schema automatically per query.
gRPC Composition
Composer uses gRPC stubs to call multiple services efficiently over HTTP/2, useful for internal high-throughput composition.
BFF (Backend for Frontend)
A specialized composer built for one specific client (e.g., mobile app), shaping data exactly the way that client needs it.
API Composition vs. API Gateway vs. BFF — What’s the Difference?
| Pattern | Main Job |
|---|---|
| API Gateway | Single entry point: routing, auth, rate limiting — may also do composition |
| API Composition | Specifically: calling multiple services and merging their data into one response |
| Backend for Frontend | A dedicated composer tailored per client type, often built using the composition pattern internally |
Shapes That Work — and Shapes That Don’t
API Composition rarely lives in isolation — it lines up next to a handful of closely related patterns, and it has its own set of recognizable anti-patterns.
Related Design Patterns
Scatter-Gather
The classic messaging pattern of sending a request to many recipients and aggregating their replies — the technical foundation of composition.
CQRS + Materialized View
An alternative to real-time composition: pre-build and store a denormalized “read model” combining data from multiple services ahead of time.
Saga Pattern
Handles multi-service writes (as opposed to composition, which handles multi-service reads) using a sequence of local transactions.
Bulkhead & Circuit Breaker
Resilience patterns commonly paired with composition to isolate and protect against downstream failures.
Anti-patterns to Avoid
Letting one composer accumulate so much business logic and so many downstream dependencies that it becomes a new monolith in disguise. Keep composers thin — orchestration and shaping, not business rules.
A composer calling another composer, which calls another composer, creates deep synchronous call chains where a failure or slowdown anywhere cascades all the way up, and latency stacks up badly.
Treating every downstream call as equally critical, so that one minor service being down (like a “recently viewed” recommendation service) takes down an entire page.
Habits That Keep Composers Fast, Safe, and Sane
A distilled checklist of the practices that make composers pleasant to operate — and the mistakes that quietly turn them into pager-driven nightmares.
✓ Best Practices
- Parallelize independent downstream calls whenever possible.
- Set explicit timeouts on every call, plus an overall request deadline.
- Classify each dependency as “critical” or “best-effort” and design fallbacks accordingly.
- Use circuit breakers and bulkheads per dependency.
- Keep composers stateless and horizontally scalable.
- Instrument every downstream call with tracing and per-dependency metrics.
- Cache aggressively where staleness is acceptable.
✗ Common Mistakes
- Calling downstream services sequentially when they could run in parallel.
- No timeout at all — one hung service can hang every composite request.
- Treating every downstream failure as a total failure of the request.
- Letting the composer accumulate business logic that belongs in a provider service.
- Skipping contract tests, so a provider’s API change silently breaks the composer.
- Forgetting to cache, causing unnecessary repeated load on providers.
A Sensible Rollout Path for a New Composite Endpoint
Nail the composite contract first
Agree on the exact response shape with the client team before writing any fan-out code — changing this later is expensive.
Classify each dependency
For every provider call, decide up front whether it is critical or best-effort, and what the fallback value looks like if it is unavailable.
Wire up observability before turning it on
Per-dependency latency, error rates, and circuit breaker state should all be visible from the very first request.
Load-test with realistic fan-out patterns
Because one composite request may become several downstream calls, load-testing the composite endpoint tells you as much about the backend fleet as it does about the composer itself.
Where You Have Already Been Using Composition
You have almost certainly interacted with API Composition already today — you just did not see it. Here are some places where it hides in plain sight.
Netflix
Netflix’s API Gateway layer (historically built on Zuul, and the well-known “API Gateway” pattern write-ups from their engineering blog) composes data from dozens of backend microservices to build a single personalized home-screen response per device type.
Amazon
A single Amazon product page composes data from pricing, inventory, reviews, recommendations, and shipping-estimate services — each independently owned and scaled.
Uber
A single trip-summary screen composes data from the trip service, pricing/fare service, driver service, and ratings service into one rider-facing view.
Search result pages and many Google product surfaces compose “knowledge panels,” ads, and organic results from many independent backend systems in real time.
Across all of these, the underlying motivation is identical to our pizza-delivery example: independently owned, independently scaled services, with a composition layer that gives the end user (or the mobile client) one fast, unified answer — regardless of how many separate teams and databases actually contributed to it behind the scenes.
Questions That Come Up Every Time This Pattern Is Discussed
A quick round of the questions that get asked most often when a team is deciding whether — or how — to introduce API Composition.
Is API Composition the same as an API Gateway?
No, but they are closely related. An API Gateway is the single entry point for all client traffic and can host many responsibilities (auth, rate limiting, routing). API Composition is a specific pattern — often implemented inside the gateway, or as a separate service behind it — for combining data from multiple backend services into one response.
Does API Composition replace a shared database?
Not exactly — it is a workaround for not having one. In a monolith with a single database, you would use SQL joins. In microservices, each service owns its data privately, so composition is how you reconstruct a “joined” view across service boundaries at request time.
Is GraphQL the same thing as API Composition?
GraphQL (especially with schema federation) is one popular implementation approach for composition — it lets you declare a unified schema across services and have a gateway resolve fields from the right subgraph automatically. But you can absolutely do API Composition with plain REST and hand-written merging code too.
What happens if one of the services is down?
A well-designed composer applies fallback logic: critical data may cause the whole request to fail, while non-critical data is simply omitted or marked unavailable so the rest of the response still succeeds — this is called graceful degradation.
When should I NOT use API Composition?
If your application is still a monolith with one shared database, you do not need this pattern — a normal query is simpler and faster. It is specifically valuable once you have split into microservices with separate databases and a client needs data that spans more than one of them.
Can composition happen for writes, not just reads?
Composition is primarily a read-side pattern. Coordinating writes across multiple services is a different, harder problem usually solved with the Saga pattern instead.
How is composition different from caching a joined view up front?
Materialized views (a common alternative under the CQRS umbrella) pre-compute the joined data ahead of time and store it in its own read store, so requests read it directly without any real-time fan-out. That is often faster at read time, but it introduces its own operational cost: keeping the materialized view fresh as source data changes. Composition trades that up-front cost for a small per-request fan-out cost. Many production systems end up using both — a materialized view for the hottest, cheapest-to-precompute reads, and live composition for everything else.
One Pattern to Rebuild the Joins You Gave Up
API Composition is the pattern that lets a microservices architecture feel simple from the outside, even though it is complex on the inside.
It solves the very real problem created by splitting a monolith’s shared database into many independently owned databases: related data ends up scattered, and something has to put it back together for the client. That “something” is the composer — a thin, deliberately narrow layer whose whole job is fan-out and merge.
Key Takeaways
- What it is: a component that fans out requests to multiple provider services and merges their responses into one composite response.
- Why it exists: database-per-service microservices make cross-service SQL joins impossible, so composition rebuilds that “joined” view at request time.
- How it works: parallel fan-out, per-call timeouts, merging, and graceful handling of partial failures.
- Where it lives: inside an API Gateway, a dedicated composer/BFF service, or a GraphQL federation layer.
- What makes it reliable: circuit breakers, bulkheads, retries with backoff, and clear critical vs. best-effort classification of dependencies.
- What makes it fast: parallel calls, caching, and stateless horizontal scaling.
- Where it is used: Netflix, Amazon, Uber, and Google all rely on composition to build single-screen experiences from dozens of independent backend services.
API Composition is how modern microservice architectures give clients one simple response built from many independent services — parallel fan-out, careful merging, and deliberate graceful degradation on partial failures.
Once you see this pattern, you will notice it everywhere — nearly every modern app screen you use is quietly the product of an API Composer working behind the scenes, gathering pieces from independent teams and independent databases into the single, unified answer you actually see.