Most enterprise frontends don't have a data problem — they have a shape problem. The data exists, but it lives in SAP, Salesforce, a legacy point-of-sale system, and three internal services, each with its own protocol, latency and failure mode. Asking a web app to talk to all of them directly is how you end up with a brittle client and a very unhappy on-call rotation.
On a shipment portal I worked on, the answer was Adobe App Builder used as an API orchestration hub: one place that federates those backends, adds the custom logic they can't, and hands the frontend a single, predictable contract.
What App Builder actually is
App Builder is Adobe's framework for building cloud-native apps that extend the Experience Cloud. Three parts matter for orchestration:
- Adobe I/O Runtime — serverless functions (actions) for custom backend logic. No servers to manage; each action is a small, independently deployable unit.
- API Mesh — federates multiple sources (REST, GraphQL, SOAP) into a single GraphQL endpoint. The frontend queries one graph; the mesh fans out.
- Adobe I/O Events — an event backbone for reacting to things asynchronously instead of blocking a request on slow downstream work.
The orchestration pattern
The portal never talks to SAP or Salesforce directly. It talks to the mesh, and the mesh — plus a thin layer of Runtime actions — does the fan-out:
one GraphQL query in · fan-out to sources · async events out
Two things make this work in practice.
The mesh owns the read path. Downstream responses are large and modelled for integration, not for a screen. API Mesh lets each query ask for exactly the fields a page renders, and resolves them across sources — so filtering happens at the edge, not in the browser. A mesh source config is just declarative:
{
"sources": [
{ "name": "orders", "handler": { "graphql": { "endpoint": "https://.../orders" } } },
{ "name": "customer", "handler": { "openapi": { "source": "https://.../sap.json" } } }
]
}
Runtime actions own the write path and the glue. Anything the mesh can't do — auth exchange, validation, add-to-cart, kicking off a data migration — is a small serverless action. Because actions are independent, you can mock and develop them in parallel; headless mocking of the mesh let our tracks move without waiting on each other.
Why events, not just requests
Some work must be reliable but must not block the user: emailing a shipping label, publishing a payment-lifecycle event, notifying a downstream system. Those go through I/O Events and out to Kafka, where independent consumers handle them with retries and replay. The portal stays responsive; the paperwork always arrives. Modelling those payloads against an Avro schema before publishing kept the event contract honest as more consumers came online.
What I'd tell my past self
- Treat the mesh schema as a product. It's the contract every frontend depends on; version it and review changes like an API, because it is one.
- Keep actions small and boring. The temptation is to grow one action into a monolith. Resist it — small actions are what make parallel development and clean rollbacks possible.
- Decide sync vs async early. The line between "the user waits for this" and "this happens eventually" is an architecture decision, not an implementation detail. Draw it before you write the action.
Used this way, App Builder isn't really an Adobe-specific trick — it's the orchestration-hub pattern with the undifferentiated plumbing removed. The frontend gets one contract, the backends stay untouched, and the interesting engineering moves to where it belongs: the seams between systems.