Limited Offer

30% OFF Lifetime Access ($139) with code SYSTEM30

TOPIC #143Intermediate 9 min read

API Composition / Aggregator Pattern

πŸ’‘
Core Architecture Summary

Combine data across microservices: In-memory parallel fan-out aggregation, scatter-gather, timeout handling, and partial degradation.

Key Glossary Concepts in this TopicAll Glossary Terms

Scatter-Gather API Composition & Partial Degradation πŸ“Š

Aggregator queries 4 microservices concurrently in parallel, gracefully degrading on non-critical timeouts before returning a unified composite payload.

Scatter-Gather API Composition & Partial Degradation πŸ“Š
100%
Rendering visual architecture flowchart...

01.1. Why API Composition is Mandatory in Microservices

In a traditional monolithic architecture with a unified relational database, rendering a complex user interface (such as an Order Details Page) is straightforward: a single SQL query executes an internal JOIN across orders, users, payments, and shipments tables in < 5ms.

In a microservices architecture, the Database-per-Service pattern strictly prohibits cross-service database queries. The Order Service cannot query the Payment Service's database tables.

If a mobile client were forced to query each service individually, the client would suffer from:

  • Chatty Network Overhead: Sending 5 sequential mobile HTTP requests over high-latency cellular 4G/5G connections (100-300ms RTT per hop).
  • Battery & Data Drain: Multiple TLS handshakes and duplicate header parsing.
  • Client Complexity: Complex client-side join logic that breaks whenever backend schemas change.

The API Composition (or Aggregator) pattern solves this by creating a dedicated server-side aggregation layer (often implemented as a Backend for Frontend / BFF) that fetches data from downstream services and joins the results in memory.

02.2. The Scatter-Gather Parallelism Engine

To minimize user-perceived latency, an API Aggregator must never execute downstream RPC calls sequentially. Instead, it must utilize the Scatter-Gather pattern:

Latency Comparison:

  • Sequential Execution: T_{total} = T_{Order} + T_{User} + T_{Payment} + T_{Shipping} = 15ms + 22ms + 38ms + 25ms = 100ms.
  • Parallel Scatter-Gather: T_{total} = \max(T_{Order}, T_{User}, T_{Payment}, T_{Shipping}) + T_{merge} = \max(15, 22, 38, 25) + 3ms = 41ms (> 2.4Γ— faster!).

TypeScript Implementation Example with Promise.allSettled:

typescript
interface CompositeOrderResponse {
  order: OrderDto;
  customer: UserProfileDto;
  payment: PaymentReceiptDto;
  recommendations: RecommendationDto[];
}

export async function getCompositeOrderDetails(orderId: string): Promise<CompositeOrderResponse> {
  // Step 1: Fetch core order to extract customerId and paymentId
  const order = await orderService.getOrder(orderId);

  // Step 2: Scatter-Gather concurrent requests for dependent data
  const [userResult, paymentResult, recsResult] = await Promise.allSettled([
    userService.getUserProfile(order.customerId, { timeoutMs: 300 }),
    paymentService.getPaymentReceipt(order.paymentId, { timeoutMs: 300 }),
    recommendationService.getRecommendations(order.items, { timeoutMs: 150 }) // fast timeout
  ]);

  // Step 3: Handle partial degradation
  const customer = userResult.status === 'fulfilled' 
    ? userResult.value 
    : { name: 'Customer', email: order.fallbackEmail }; // Fallback

  const payment = paymentResult.status === 'fulfilled' 
    ? paymentResult.value 
    : { status: 'UNKNOWN', maskedCard: '****' };

  const recommendations = recsResult.status === 'fulfilled' 
    ? recsResult.value 
    : []; // Non-critical fallback: empty recommendations array

  // Step 4: In-Memory Composition
  return { order, customer, payment, recommendations };
}

03.3. Failure Handling & Partial Degradation Strategies

When an API Aggregator depends on 4–8 microservices, downstream failures are a mathematical certainty at scale. The aggregator must classify dependencies into two categories:

1. Critical Dependencies (Fail-Closed)

  • If the Order Service is unreachable, the aggregator cannot fulfill the request and must return an explicit 404 Not Found or 503 Service Unavailable error.

2. Non-Critical Dependencies (Fail-Open / Graceful Fallback)

  • If the Recommendation Service or Loyalty Points Service experiences a timeout or 500 error, the aggregator silently catches the exception, logs a warning metric, and populates the response with a static default or empty array ([]).
  • The end-user successfully views their order and delivery status without noticing that recommendations failed to load.

04.4. API Composition vs CQRS: When Composition Breaks Down

While API Composition is ideal for single-entity composite views (e.g., viewing Order #123), it encounters severe architectural limits when querying large datasets:

  • The Pagination & Sorting Nightmare: If you need to query "Show all orders placed in New York with a total >100$ sorted by customer loyalty score", the aggregator would have to fetch millions of records from the Order, User, and Payment services and perform an in-memory join and sortβ€”crashing the aggregator with Out-Of-Memory (OOM) errors.
  • When to Switch to CQRS (Command Query Responsibility Segregation):
    • For complex filtering, cross-domain searching, or paginated aggregate queries, do not use API Composition.
    • Instead, use CQRS with Change Data Capture (CDC) to asynchronously project denormalized data into a dedicated search database (e.g., Elasticsearch, OpenSearch, or Postgres Read Replica) pre-joined for instantaneous queries.

βš–οΈArchitectural Trade-offs & Production Realities

Architectural Advantages

  • Allows creating rich, tailor-made composite UI payloads without violating Database-per-Service encapsulation
  • Minimizes mobile client network roundtrips and cellular battery consumption through single-endpoint aggregation
  • Enables graceful partial degradation: non-critical service timeouts do not break core business flows

Trade-offs & Constraints

  • In-memory joins consume substantial aggregator CPU and RAM when processing large data volumes
  • Overall request latency is bound to the slowest downstream microservice dependency ($\\max(T_1, T_2, ...)$)
  • Unsuitable for complex multi-entity sorting, filtering, and cross-domain pagination (requires CQRS instead)
Production Implementation in Big Tech
Netflixβ€’ Video Metadata Composite Aggregator (Zuul & Falcor/GraphQL)

When a user opens the Netflix TV application, the home screen aggregator concurrently fans out requests to the Video Catalog Service, Personalized Artwork Service, Bookmark Service, and Subtitle Service. If the Personalized Artwork service is slow, the aggregator instantly falls back to default global movie posters within a strict 50ms SLA.

🎯 Staff+ Engineering Takeaways

  • API Composition joins data across isolated microservice databases in memory at the gateway or BFF layer.
  • Scatter-Gather concurrency reduces total latency to the duration of the single slowest dependency.
  • Implement strict timeouts and graceful fallback defaults for all non-critical downstream dependencies.
  • Use API Composition for single-entity views; use CQRS / Elasticsearch for cross-service search and pagination.

Topic Knowledge Assessment 🧠

Step through 3 scenario questions to test your staff-level grasp.

Question 1 of 30 answered
#1

If an API Aggregator needs data from Service A (15ms), Service B (40ms), and Service C (20ms), what is the total latency if calls are executed using parallel Scatter-Gather vs sequential execution?

Rate This Architecture Chapter4.9 / 5.0 (38 ratings)

How clear and staff-actionable was this system breakdown?