Skip to content
Ocean-Atlasv0.1.0Canonical Knowledge

Synchronous API Composition

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).

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?

  • 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.

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.

flowchart LR
Client --> AnswerApi
AnswerApi --> AnswerService
AnswerService -- aggregate --> DayApi --> DayService
AnswerService -- aggregate --> TimeDateApi --> TimeDateService
AnswerService -- aggregate --> DayTipApi --> DayTipService
@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.getDay

This is a minimal illustrative fragment, not a complete duplicated example — see Working Examples for the full, runnable model.

  • 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.
  • 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.
  • If the composed calls don’t need to run as a fan-out — one source is enough — use a plain connect instead of aggregate.
  • If the composed operations don’t need to answer synchronously, prefer Asynchronous Request-Response instead.