Response Aggregation in API Gateway — Combine Multiple Backend Responses
In this tutorial, you will learn about Response Aggregation in API Gateway. We cover key concepts, practical examples, and best practices to help you master this topic.
Response aggregation is the process of combining data from multiple backend services into a single response at the gateway level, so clients make one request instead of many.
What You'll Learn
- How aggregation reduces client complexity and network round trips
- Fan-out requests to multiple backends concurrently
- Handling partial failures during aggregation
Why It Matters
A dashboard page may need user profile, recent orders, notifications, and product recommendations. Without aggregation, the client makes four sequential requests. With gateway aggregation, the client makes one request, and the gateway fans out to all four services concurrently, returning a single response.
Real-World Use
The Durga Antivirus dashboard shows user info, active subscriptions, recent scan history, and threat alerts. The gateway endpoint /dashboard fans out to four internal services concurrently and combines their responses into one JSON payload for the frontend.
flowchart LR
Client["Client"] --> GW["Gateway\n/aggregated-endpoint"]
GW --> S1["Service A"]
GW --> S2["Service B"]
GW --> S3["Service C"]
S1 --> GW
S2 --> GW
S3 --> GW
GW --> Client
style GW fill:#dbeafe,stroke:#2563eb
Concurrent Fan-Out with asyncio
import asyncio
import aiohttp
from flask import Flask, jsonify
app = Flask(__name__)
SERVICES = {
"user": "http://user-service:8080/profile",
"orders": "http://order-service:8080/recent",
"alerts": "http://alert-service:8080/unread",
}
async def fetch(session, name, url):
try:
async with session.get(url, timeout=5) as resp:
data = await resp.json()
return name, data, None
except Exception as e:
return name, None, str(e)
@app.route("/dashboard")
def dashboard():
async def aggregate():
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, name, url) for name, url in SERVICES.items()]
results = await asyncio.gather(*tasks)
payload = {}
errors = {}
for name, data, error in results:
if data:
payload[name] = data
if error:
errors[name] = error
payload["_errors"] = errors
return payload
return jsonify(asyncio.run(aggregate()))
Expected response:
{
"user": {"id": 42, "name": "Alice"},
"orders": [{"id": 1, "item": "Laptop"}],
"alerts": [{"type": "threat", "count": 3}],
"_errors": {}
}
Sequential Fallback Aggregation
Some aggregations require ordered steps. For example, get the user ID first, then fetch that user's orders:
@app.route("/user-orders")
def user_orders():
user_resp = requests.get("http://user-service:8080/me")
user = user_resp.json()
orders_resp = requests.get(f"http://order-service:8080/users/{user['id']}/orders")
orders = orders_resp.json()
return jsonify({"user": user, "orders": orders})
Aggregation with Graphql
Some gateways support GraphQL aggregation where the client specifies exactly which data it needs:
@app.route("/graphql", methods=["POST"])
def graphql_aggregation():
query = request.json.get("query")
if "user" in query:
user = requests.get("http://user-service:8080/profile").json()
if "orders" in query:
orders = requests.get("http://order-service:8080/recent").json()
return jsonify({"data": {"user": user, "orders": orders}})
Common Mistakes
1. Sequential Requests When Concurrent Works
Fetching unrelated data sequentially doubles response time. Use asyncio or threading for parallel fan-out.
2. Not Setting Per-Service Timeouts
One slow service blocks the entire aggregation. Set individual timeouts for each backend call.
3. Returning Everything or Nothing
If one service fails, should the whole request fail? Design partial responses with error indicators so clients can display partial data.
4. Ignoring Response Size
Aggregating large datasets creates huge payloads. Allow clients to select which fields they need with query parameters.
5. Tight Coupling to Backend Response Shapes
If a backend changes its response format, aggregation breaks. Use transformation layers to convert backend responses to consistent shapes.
Practice Questions
- What problem does response aggregation solve for mobile clients?
- When should you use concurrent vs. sequential aggregation?
- How should the gateway handle a backend service that times out during aggregation?
- Why might aggregation produce very large responses?
- How can you decouple aggregation logic from backend response formats?
Answers:
- Mobile networks have high latency. One aggregated call replaces multiple sequential calls, reducing total latency from N round trips to 1.
- Use concurrent for independent data (profile, orders, alerts). Use sequential when one response depends on another (get user ID first, then orders).
- Return partial data with an error field for the failed service. Set per-service timeouts to avoid blocking the entire aggregation.
- If backends return large collections, the combined response grows quickly. Implement pagination or field selection to limit size.
- Add a transformation layer between backends and the client that normalizes backend responses into consistent client-facing shapes.
Challenge: Design a gateway aggregation endpoint for an e-commerce product page that needs product details, reviews, inventory status, and shipping estimate. Determine which calls run concurrently and which run sequentially.
FAQ
Mini Project
Build a gateway aggregation endpoint /profile that fetches user data, recent activity, notification count, and subscription status from four separate mock services. Use concurrent requests, individual 3-second timeouts, and return partial data if any service fails.
What's Next
Continue with Circuit Breaker Pattern in Gateway to prevent cascading failures, or explore Caching in API Gateway for performance optimization.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro