Production engineering guide for api composition patterns covering patterns, implementation strategies, and operational best practices.
Api composition patterns are the structural strategies for combining multiple APIs into a cohesive, unified service layer. They matter when a system must orchestrate data and behavior across independently developed services—typically in a microservices or event-driven architecture—where a single client request requires coordination of two or more backend APIs. Composition is essential when a client needs a view or action that cannot be satisfied by any single API alone, and when latency, consistency, and resilience across the composed stack become critical.
Compose APIs in response to a client request, where the result is a single, unified payload from multiple underlying services. This is the most common pattern, used when clients expect a rich, denormalized response without needing to manually stitch together results.
A client request triggers a series of synchronous API calls, where the output of one API becomes the input of the next.
GET /users/123
GET /api/v1/users/123
{
"id": 123,
"name": "Alice",
"email": "[email protected]",
"departmentId": 45
}
GET /api/v1/departments/45
{
"id": 45,
"name": "Engineering",
"managerId": 567,
"budget": 1200000
}
GET /api/v1/users/567
{
"id": 567,
"name": "Bob",
"email": "[email protected]",
"title": "Director"
}
Final response:
{
"user": {
"id": 123,
"name": "Alice",
"email": "[email protected]"
},
"department": {
"name": "Engineering",
"manager": {
"name": "Bob",
"email": "[email protected]",
"title": "Director"
},
"budget": 1200000
}
}
503 Service Unavailable, the composed API may return a partially populated response with null for manager, but the client may not expect this.404 Not Found from the department API must be clearly communicated—either as a 404 at the top level, or by embedding error fields in the payload.readTimeout: 300 and writeTimeout: 200 in its client configuration.maxRetries: 3, initialDelay: 100, multiplier: 2.Compose multiple APIs concurrently, then merge their results. Best when dependencies between services are independent.
GET /orders/456
GET /api/v1/orders/456 → order detailsGET /api/v1/customers/789 → customer infoGET /api/v1/products?ids=101,102,103 → product listGET /api/v1/shipping/456 → shipping status{
"order": {
"id": 456,
"customerId": 789,
"total": 245.99,
"status": "shipped"
},
"customer": {
"name": "Charlie",
"email": "[email protected]"
},
"products": [
{ "id": 101, "name": "Laptop", "price": 1200 },
{ "id": 102, "name": "Mouse", "price": 25 },
{ "id": 103, "name": "Keyboard", "price": 75 }
],
"shipping": {
"trackingNumber": "TRK123456",
"estimatedDelivery": "2025-04-10"
}
}
mergeStrategy: "deep" or mergeStrategy: "flat" in configuration.continueOnFailure: true, returning null for that part. If failOnAny: true, the entire request fails on the first error.composition:
parallel:
- service: "orders"
method: "GET"
path: "/api/v1/orders/{orderId}"
params: { orderId: "id" }
timeout: 1500
retry:
maxRetries: 2
delay: 100
backoff: "exponential"
- service: "customers"
method: "GET"
path: "/api/v1/customers/{customerId}"
params: { customerId: "order.customerId" }
continueOnFailure: true
- service: "products"
method: "GET"
path: "/api/v1/products"
query: { ids: "order.productIds" }
mergeStrategy: "deep"
Compose APIs in response to an event, where one or more services emit events that trigger downstream composition.
A domain event triggers a composition that aggregates data from multiple services into a view model.
Example: UserCreated event
{
"eventId": "evt-123",
"eventType": "UserCreated",
"timestamp": "2025-03-15T10:30:00Z",
"data": {
"userId": 123,
"name": "Diana",
"email": "[email protected]",
"departmentId": 45
}
}
This event is consumed by a composition service that:
users APIdepartments APIteams APIUserProfileView stored in a read model.eventHandlers:
UserCreated:
- action: "fetch-user"
service: "users"
method: "GET"
path: "/api/v1/users/{userId}"
target: "user"
- action: "fetch-department"
service: "departments"
method: "GET"
path: "/api/v1/departments/{departmentId}"
target: "department"
- action: "fetch-team"
service: "teams"
method: "GET"
path: "/api/v1/teams/{departmentId}/members"
target: "team"
- action: "merge-views"
strategy: "merge"
output: "UserProfileView"
schema: "/schemas/user-profile.json"
eventId as a deduplication key.acknowledgeAfter: true to ensure processing.Multiple events trigger a sequence of composition steps, where each step may involve multiple API calls and emit new events.
Example: OrderPlaced → OrderFulfilled
OrderPlaced event published.inventory API to reserve stock.payment API to charge the customer.notification API to send email.OrderFulfilled event.OrderFailed event.Each API call may be configured with:
{
"service": "inventory",
"method": "POST",
"path": "/api/v1/reserve",
"input": {
"orderId": "event.orderId",
"items": "event.items"
},
"timeout": 1000,
"retry": {
"maxAttempts": 3,
"backoff": "exponential",
"delay": 100
},
"errorHandling": {
"strategy": "compensate",
"compensatingAction": "refund-order"
}
}
step: "reserve-stock" with continueOnFailure: true.compositionId and status, and updates it after each step.Compose APIs based on a declarative schema that defines the input, output, and transformation logic.
{
"name": "UserWithDepartment",
"input": {
"type": "object",
"properties": {
"userId": { "type": "integer" }
},
"required": ["userId"]
},
"output": {
"type": "object",
"properties": {
"user": { "$ref": "#/definitions/User" },
"department": { "$ref": "#/definitions/Department" },
"manager": { "$ref": "#/definitions/User" }
}
},
"steps": [
{
"name": "fetch-user",
"service": "users",
"method": "GET",
"path": "/api/v1/users/{userId}",
"output": "user"
},
{
"name": "fetch-department",
"service": "departments",
"method": "GET",
"path": "/api/v1/departments/{user.departmentId}",
"output": "department"
},
{
"name": "fetch-manager",
"service": "users",
"method": "GET",
"path": "/api/v1/users/{department.managerId}",
"output": "manager"
}
],
"transform": {
"merge": {
"target": "output",
"source": [ "user", "department", "manager" ]
}
}
}
POST /api/v1/compose/UserWithDepartment
{
"userId": 123
}
Response:
{
"user": { "id": 123, "name": "Alice", "email": "[email protected]" },
"department": { "id": 45, "name": "Engineering" },
"manager": { "id": 567, "name": "Bob" }
}
https://schemas.example.com/compositions/UserWithDepartment.json.GET /api/v1/compositions and responds with a list of available compositions.compositionId and input.When the schema changes, the composition system must handle versioning:
compositionId: "UserWithDepartment", version: "2.1"compositionId: "UserWithDepartment", version: "2.0"compositionId: "UserWithDepartment", version: "1.0"Each version may have different steps, transformation rules, or error handling.
null fields and warnings arrays.Cache-Control: max-age=300 and ETag on the composed response.compositionId, stepName, service, duration, status, and error.| Strategy | Best For | Latency | Fault Tolerance | Idempotency |
|---|---|---|---|---|
| Sequential Chaining | Dependent, linear workflows | High | Moderate | High |
| Parallel Composition | Independent, high-throughput APIs | Low | High | Medium |
| Event-Driven | Real-time, asynchronous, reactive systems | Medium | High | High |
| Schema-Driven | Reusable, declarative, versioned APIs | Medium | High | High |
Each pattern solves a distinct problem in API composition. Choose based on whether the request is driven by a client, an event, or a schema—and how resilient and performant the composition must be.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy