API Composition Pattern for Microservice Read Models
The use case
A booking summary page may need:
Booking
Payment status
Traveller
Hotel statusThe data belongs to different services.
For a low/medium-volume interactive view, I can compose APIs rather than create a new database immediately.
BFF/query service
Web
|
Booking Summary BFF
|-- Booking Service
|-- Payment Service
|-- Customer Service
`-- Hotel ServiceNestJS:
@Get('/booking-summary/:id')
async summary(
@Param('id') id: string,
) {
const deadline = Date.now() + 1500;
const [
booking,
payment,
traveller,
] = await Promise.all([
this.booking.get(id, deadline),
this.payment.getForBooking(
id,
deadline,
),
this.customer.getTraveller(
id,
deadline,
),
]);
return {
id,
status: booking.status,
paymentStatus: payment.status,
travellerName:
traveller.displayName,
};
}Parallel calls reduce serial latency.
Per-dependency budget
I do not let every call use the full page SLA.
overall page budget 1500 ms
booking 700 ms
payment 700 ms
customer 500 msCalls happen in parallel, leaving budget for gateway/rendering.
Partial failure
For a dashboard, payment status might degrade to UNKNOWN.
const payment =
await safeCall(
() =>
this.payment.getForBooking(id),
{ status: 'UNKNOWN' },
);
return {
booking,
payment,
degraded:
payment.status === 'UNKNOWN',
};For a financial confirmation page, the same fallback may be unacceptable.
Fallback is a business decision.
When composition becomes wrong
If the page calls:
20 servicesfor every request, the probability of at least one dependency being slow becomes high.
At that point I consider:
- CQRS read projection;
- cached aggregate view;
- search index;
- materialized reporting model.
N+1 calls
List of 100 bookings:
1 call get bookings
100 calls get customer
100 calls get paymentThis is a classic composition failure.
Batch endpoints or a prepared read model are better.
Production checklist
- Keep fan-out small.
- Call independent dependencies in parallel.
- Define per-call timeouts.
- Define partial-result semantics.
- Trace fan-out.
- Avoid N+1.
- Cache stable reference data.
- Move to projection when traffic/fan-out grows.
- Do not query another service DB directly.
FAQ
API Composition vs CQRS read model?
Composition reads live services at request time. CQRS maintains a prepared read model.
Where should composition live?
Often in a BFF or dedicated query service.
Can an API Gateway compose?
Technically yes, but I avoid putting complex business/read logic in the generic edge gateway.
Related architecture guides
- [Microservices Design Patterns I Have Used in Real Enterprise Projects](/articles/microservices-design-patterns-real-enterprise-projects)
- [CQRS](/articles/cqrs-command-query-responsibility-segregation-real-systems)
-
Architect's final take
API composition is useful while the fan-out remains understandable. Once the query graph becomes a distributed join engine, I build a read model.
Your feedback helps prioritize deeper technical content.






