Skip to content

API Composition

Your services each own a private database, so the order detail page is now a problem. It needs the order itself from the Order service, the buyer’s name and tier from the Customer service, and the delivery status from the Shipping service. In a monolith this was a three-table join. Now those tables live in three databases that no single query can reach.

The page still has to render. You need to gather data from several services and present it as one combined result, but you have no shared database to join across, and you would rather not invent a whole new storage layer just to answer one query.

How do you implement a query whose data is scattered across services, without breaking the Database per Service boundary, and without over-engineering?

Introduce an API Composer — a small component (often inside an API gateway or a backend-for-frontend) whose only job is to answer this query. It calls each service that owns part of the answer, collects the responses, and joins them together in memory before returning the combined result.

The composer holds no data of its own. It is pure coordination: fan out the requests (ideally in parallel, since they are independent), then merge. Because the three calls don’t depend on each other, issuing them concurrently keeps latency close to that of the slowest single call rather than the sum of all three.

flowchart LR
  Client[Client] --> Composer[API Composer]
  Composer -->|getOrder| OrderSvc[Order Service]
  Composer -->|getCustomer| CustomerSvc[Customer Service]
  Composer -->|getShipment| ShippingSvc[Shipping Service]
  OrderSvc --> ODB[(Orders DB)]
  CustomerSvc --> CDB[(Customers DB)]
  ShippingSvc --> SDB[(Shipping DB)]
  Composer -->|merged result| Client
The composer fans out to each owning service and joins the responses in memory

The composer below fetches the order, customer, and shipment in parallel, then stitches them into a single view object. The merge is an in-memory join keyed by the IDs the order already carries.

async function getOrderDetail(orderId: string) {
const order = await orderSvc.getOrder(orderId);
// The order tells us which customer and shipment to fetch.
const [customer, shipment] = await Promise.all([
customerSvc.getCustomer(order.customerId),
shippingSvc.getShipment(order.id),
]);
// In-memory join.
return {
orderId: order.id,
total: order.total,
customerName: customer.name,
customerTier: customer.tier,
deliveryStatus: shipment.status,
eta: shipment.eta,
};
}

What you gain:

  • Simplicity. No new storage, no event pipeline. If you can call the services, you can compose them. It is the obvious first choice for cross-service queries.
  • Always current. Because it reads live from each owner at request time, the result reflects the latest state — no replication lag.

What it costs you:

  • In-memory joins are inefficient. Joining large result sets in application memory is far slower than a database doing it with indexes. A query that filters or sorts across services (for example, “the 20 highest-value orders for gold-tier customers”) may force the composer to pull huge amounts of data from each service and discard most of it.
  • Availability is the product of its parts. The composer needs every called service to respond. If one is down or slow, the whole query suffers — unless you add timeouts and partial-result handling.
  • Reduced consistency. The pieces are read at slightly different moments, so the combined view can mix states that never existed together at one instant.

When the in-memory join becomes the bottleneck — large data, complex filtering, or strict latency targets — graduate to a maintained read model with CQRS, which pre-joins the data ahead of time.

  • Database per Service — the boundary that turns a join into a composition.
  • CQRS — the heavier alternative for queries too expensive to compose at request time.
What does an API Composer do?
Why should the composer issue its calls in parallel when they are independent?
What is the main drawback of API Composition for large or heavily filtered queries?