Synchronous API Composition
1. Context
Section titled “1. Context”A system exposes one coordinating operation whose answer is assembled from several other synchronous API calls, each potentially served by a different service (for example, a “day tip” answer built from a day service, a time/date service, and a tip service).
2. Problem
Section titled “2. Problem”How do you combine multiple synchronous API results into one response, without pushing orchestration logic into the client and without collapsing every capability into one monolithic service?
3. Forces
Section titled “3. Forces”- Coupling — the coordinating service must know each source API’s contract, coupling it to their shape.
- Latency — composed calls should run as a bounded fan-out, not an unbounded chain, or total latency accumulates linearly.
- Consistency — the assembled answer should reflect a coherent set of underlying calls even if one branch is slow.
- Failure handling — any composed call can hang or fail without a bound; the aggregate must not inherit that risk unbounded.
- Reuse — the services being composed should stay independently usable and independently deployable, not fused into the coordinator.
4. Solution
Section titled “4. Solution”Expose a coordinating @api operation and implement it in a coordinating
@service. Use connect for simple one-to-one passthroughs to a single
source API, and aggregate for fan-out compositions that combine several
source calls — optionally transforming each branch with an expression and
bounding the whole composition with a timeout.
5. Structure
Section titled “5. Structure”flowchart LR Client --> AnswerApi AnswerApi --> AnswerService AnswerService -- aggregate --> DayApi --> DayService AnswerService -- aggregate --> TimeDateApi --> TimeDateService AnswerService -- aggregate --> DayTipApi --> DayTipService6. DSL Sketch
Section titled “6. DSL Sketch”@service
AnswerService use config CommonServiceConfig as svcCfg impl api AnswerApi as api on svcCfg.apiConfig.port use api DayApi as day use api TimeDateApi as td use api DayTipApi as tip use expression PrettyDate use expression PrettyTime use expression PrettyTip
aggregate api.getTimeAnswer -> day.getDay | td.getDate using PrettyDate | td.getTime using PrettyTime | tip.getDayTip using PrettyTip - timeout 2s
connect api.getDay -> day.getDayThis is a minimal illustrative fragment, not a complete duplicated example — see Working Examples for the full, runnable model.
7. Consequences
Section titled “7. Consequences”Benefits
Section titled “Benefits”- One client-facing contract instead of several client-side calls.
- Per-branch transforms via expressions keep response shaping out of the source services.
- An explicit timeout bounds the composition’s worst-case latency.
Trade-offs
Section titled “Trade-offs”- The coordinating service takes a runtime dependency on every composed API being reachable.
- Partial-failure semantics (what happens if one branch times out) must be defined deliberately through the aggregation’s timeout behavior.
- More moving parts than a single flat service for the same capability.
8. Alternatives
Section titled “8. Alternatives”- If the composed calls don’t need to run as a fan-out — one source is
enough — use a plain
connectinstead ofaggregate. - If the composed operations don’t need to answer synchronously, prefer Asynchronous Request-Response instead.
9. Related DSL
Section titled “9. Related DSL”- Service DSL Reference —
aggregateandconnectsyntax. - API DSL Reference — the composed and coordinating API contracts.
- Expression DSL Reference — per-branch transforms.
10. Working Examples
Section titled “10. Working Examples”- Synchronous Answer Aggregation — a coordinating service that composes day, time/date, and tip APIs into one answer.
11. Related Patterns
Section titled “11. Related Patterns”- Asynchronous Request-Response — the broker-based alternative when calls don’t need to be direct API calls.
- Database-Backed CRUD — another shape of service-level connection, for persistence rather than composition.