API Composition vs Backend-for-Frontend in Microservices

⏱ 20 min read

Microservices have a way of making simple things expensive.

You start with a sensible ambition: split a large system into services that align to business capabilities, let teams move independently, and stop tripping over one another in the monolith. A few quarters later, the organization discovers a new tax. The data needed for one screen now lives in five places. A mobile app wants one shape of response, a web portal wants another, and a partner API wants something else entirely. Suddenly, the elegant service landscape looks less like a city plan and more like a set of suburban cul-de-sacs connected by angry commuters.

This is where two patterns usually enter the room: API Composition and Backend-for-Frontend (BFF).

They are often presented as neighbors. In practice, they solve different problems, carry different failure modes, and reflect different architectural intent. One is primarily about assembling distributed domain data. The other is about shaping experience-specific interfaces for channels such as web, mobile, partner integrations, or even specific product surfaces. Teams blur them together because both sit “between clients and services.” That is technically true and architecturally lazy.

The better question is not “which is more modern?” It is: where should orchestration live, and whose language should it speak?

That question is pure domain-driven design. It forces us to ask whether the thing we are building is a domain capability, an application-layer composition, or merely a presentation adapter wearing server-side clothing. If we get that wrong, we create accidental hubs, brittle coupling, duplicated rules, and distributed performance disasters. If we get it right, we get faster product delivery, clearer bounded contexts, and a system that can evolve without every change becoming a committee meeting.

So let’s be opinionated: API Composition is not a substitute for proper domain modeling, and BFF is not an excuse to rebuild monoliths one frontend at a time. Both patterns are useful. Both are easy to abuse. And in enterprise systems—especially those with Kafka, event-driven integration, and long-lived legacy estates—the distinction matters a great deal.

Context

In a typical enterprise microservices environment, a single customer journey cuts across several bounded contexts: microservices architecture diagrams

  • Customer manages profile and identity
  • Order manages order lifecycle
  • Catalog manages products and pricing references
  • Inventory manages stock positions
  • Payment manages authorization and settlement
  • Shipment manages fulfillment and tracking

No single service should own all of that. That is the point. We separate concerns because business capabilities change at different rates, because teams need autonomy, and because a shared database is not architecture—it is a hostage situation.

But clients do not care about bounded contexts. A customer app needs an “Order Details” view, not a lecture on service decomposition. It wants order items, delivery status, payment status, customer-friendly labels, and perhaps eligibility for return or cancellation. The user sees one page. The architecture sees six services and a conference call.

That mismatch creates pressure to aggregate.

In old SOA estates, this often turned into an enterprise service bus doing too much. In modern microservices, the same instinct reappears in cleaner clothes: API gateways with business logic, orchestration services, GraphQL facades, composition layers, and BFFs. Some are healthy adaptations. Some are old mistakes with JSON.

The challenge is to aggregate without collapsing bounded contexts into mush.

Problem

The problem appears in three forms.

First, distributed read models. A client needs data from multiple services in one call. If the client calls each service directly, it becomes chatty, fragile, and too aware of internal topology. Mobile networks, browser constraints, authentication complexity, and version churn all make that painful.

Second, channel-specific needs. A mobile app may need coarse-grained endpoints with aggressively reduced payloads. A web application may need richer structures and lower latency for incremental rendering. A partner API may need stable canonical semantics, stricter contracts, and a different security posture. One backend API shape rarely fits all three well.

Third, business workflow leakage. Some UI flows involve coordination that looks suspiciously like business process. Consider checkout, return initiation, or claims processing. If we are careless, frontend needs pull orchestration logic into places that should remain thin, or push business policy into experience-specific layers where it will be duplicated and eventually contradicted.

This is the heart of the tension:

  • If we compose too low, we create generic services that know too much about channel concerns.
  • If we compose too high, we force clients to reconstruct business meaning from scattered APIs.
  • If we use BFF for everything, we get frontend-shaped mini-monoliths.
  • If we use API Composition for everything, we invent a central orchestration layer that slowly becomes a god service.

Architecture is the art of deciding where the inevitable mess is allowed to live.

Forces

Several forces pull in different directions.

1. Domain semantics versus presentation semantics

In domain-driven design terms, domain services and application services should speak the language of the business. “Reserve inventory,” “authorize payment,” and “retrieve order aggregate view” are meaningful capabilities. “Get card layout model for mobile screen v3” is not. That belongs closer to the experience layer.

This distinction matters because language becomes coupling. If your composition layer starts exposing “heroBanner,” “tileStack,” or “desktopGrid,” it has crossed from domain composition into frontend convenience. That may be fine in a BFF. It is poison in a shared API composition service.

2. Client diversity

Different clients have different needs:

  • Web apps may tolerate more data and use server-side rendering or edge caching.
  • Mobile apps need fewer round trips, compact payloads, and backward-compatible evolution.
  • Internal operations portals often need broad, cross-domain data access.
  • External partner APIs need durable contracts and stricter governance.

One API rarely satisfies all of them elegantly.

3. Team topology

Conway still wins. If frontend teams are independent and release quickly, a BFF aligned to each experience can be a powerful boundary. If there is a central platform team building shared read APIs, API composition may fit better. If neither ownership model is explicit, both patterns degenerate into turf wars.

4. Consistency and reconciliation

Composed responses often combine data with different consistency guarantees. An order may be confirmed while payment settlement is still pending and inventory adjustments are eventually reflected via Kafka. If the architecture pretends all data is perfectly current, the business will eventually discover otherwise in production. event-driven architecture patterns

Reconciliation is not a side issue. It is part of the design.

5. Operational complexity

Every additional hop increases latency, failure surface, observability needs, and timeout choreography. Composition is not free. It amplifies partial failures and invites retry storms.

6. Migration from legacy

Most enterprises do not start greenfield. They start with a monolith, packaged applications, a data warehouse, and perhaps a message backbone. The path matters. Patterns that work in a mature microservices estate may be disastrous during the first year of decomposition.

Solution

Here is the blunt version.

Use API Composition when you need to assemble data or invoke capabilities across multiple microservices into a domain-meaningful response or application-level use case.

Use BFF when you need to tailor APIs to a specific client experience, channel, or product surface.

They can coexist. In fact, in many enterprises they should.

API Composition is about cross-service assembly. It often sits in an application layer above domain services, gathering data from multiple bounded contexts. It may join synchronous reads, invoke downstream services, or query pre-built materialized views. Its outputs should still make business sense independent of one specific UI.

BFF is about experience optimization. It acts as a thin backend aligned to a frontend’s needs. It may call one or more services—or an API composition layer—and transform results into client-appropriate payloads, workflow steps, pagination models, authorization scopes, and caching strategies.

A simple rule helps:

  • If the endpoint exists because the business use case exists, think API Composition.
  • If the endpoint exists because the client experience exists, think BFF.

That rule is imperfect, but useful.

API Composition as a pattern

An API composer aggregates multiple service calls into a single response. Sometimes it simply orchestrates reads. Sometimes it coordinates commands at the application layer. It is especially common for query scenarios such as customer dashboards, order summaries, account overviews, and operational consoles.

The danger is obvious: if every cross-service request goes through one central composer, it becomes a de facto integration monolith. Shared convenience is how centralization sneaks back in.

So keep the composition aligned to use cases and domain semantics. A composition service should not become “the place where all APIs go to be combined.”

BFF as a pattern

A Backend-for-Frontend creates a backend specifically for one frontend or channel. The mobile team gets a backend optimized for mobile concerns. The web team gets one optimized for browser and rendering concerns. The partner portal gets one with stricter contract stability and partner-specific authorization.

The danger here is duplication. If each BFF reimplements cross-domain business rules, you now have several inconsistent systems pretending to be one. BFFs should be thin in business policy, rich in experience policy.

That distinction is where many teams fail.

Architecture

A healthy architecture often layers the two patterns instead of choosing one as a religion.

Architecture
Architecture

This is not mandatory, but it is often practical. The composition layer handles reusable cross-service assembly with business meaning. The BFF handles channel-specific shaping. That split keeps the domain semantics out of the frontend layer and the frontend semantics out of the shared composition layer.

Domain semantics discussion

This is where domain-driven design earns its keep.

Suppose the business has a concept called Order Fulfillment Status. That concept is not owned wholly by Order, Shipment, or Inventory. It is an application-level view synthesized from multiple bounded contexts. That is a good candidate for API composition or a read model service. It has durable business meaning.

Now compare that to Order Detail Page Layout for Mobile. That is not a domain concept. It is an experience concept. It belongs in a mobile BFF.

A common failure is to let UI labels drive backend design. “We need a dashboard endpoint” sounds harmless. But if that dashboard endpoint bakes in presentation-specific assumptions, it becomes impossible to reuse safely. Better to ask: what domain or application-level information model does this dashboard represent? If no stable answer exists, keep it in the BFF.

Synchronous composition versus precomputed views

Not every composition should happen request-time. If the client needs a near-real-time summary assembled from stable service APIs, synchronous composition may be fine. If the query is expensive, latency-sensitive, or relies on eventually consistent data from multiple domains, precomputed read models are often better.

Kafka matters here. Event-driven systems allow services to publish domain events—OrderPlaced, PaymentAuthorized, ShipmentDispatched, InventoryReserved—from which a materialized view can be built for queries. Then your composition layer reads from that view rather than fanning out to half the estate on every request.

Diagram 2
Synchronous composition versus precomputed views

This approach improves latency and resilience, but it introduces eventual consistency and the need for reconciliation. Again: architecture is choosing your pain.

Migration Strategy

The migration from monolith or coarse-grained legacy APIs to API Composition and BFF should be progressive. Big-bang rewrites are usually management theater with a cloud bill attached.

The right approach is a strangler migration.

Start by identifying high-value client journeys where the current backend shape is causing delivery friction or performance pain. Good candidates are:

  • order tracking
  • customer account overview
  • checkout summary
  • claims and case management dashboards
  • field service work order views

Then build a new path around the old one.

Step 1: Isolate experience-specific entry points

Introduce a BFF for a frontend that is changing rapidly or suffering from over-generic APIs. Keep it thin. Initially, it may call the monolith or existing service endpoints. The immediate goal is not purity; it is control over contract evolution.

Step 2: Extract business-meaningful composition

As microservices emerge, move reusable cross-service assembly into composition services or query APIs. Do not let each BFF build its own version of “order summary” or “customer account status.” That way lies divergence.

Step 3: Introduce event-driven projections

Where synchronous fan-out creates latency or fragility, build Kafka-fed materialized views. This is particularly effective for dashboards, timelines, account overviews, and operations screens.

Step 4: Reconcile and govern semantics

As multiple sources contribute to one view, define explicit reconciliation rules:

  • Which source is authoritative for each field?
  • What happens when one source is stale?
  • How are conflicting timestamps resolved?
  • What statuses are derived versus directly owned?
  • What fallback behavior is allowed during partial outages?

These are not implementation details. They are part of the contract.

Step 5: Retire legacy aggregation gradually

Once a composition endpoint is stable and adoption is broad, cut clients over and remove the old path. Leave enough telemetry in place to detect contract gaps and silent consumer dependencies.

A realistic migration map looks like this:

Step 5: Retire legacy aggregation gradually
Retire legacy aggregation gradually

Notice the ugly truth: for a while, the composition layer may call both the monolith and new services. That is fine. Transitional architecture should look transitional. The mistake is pretending it is permanent.

Enterprise Example

Consider a global retailer modernizing its commerce and service platform.

The legacy estate includes:

  • a central commerce platform
  • an order management suite
  • a CRM system
  • a warehouse management platform
  • mobile and web applications
  • a Kafka backbone introduced during the last transformation wave

The retailer wants a unified “My Orders” experience across web and mobile. Customers should see:

  • current order status
  • payment status
  • item-level shipment tracking
  • return eligibility
  • customer support case linkage
  • personalized messaging

Initially, teams propose a single “Customer Experience API” that all channels will use. It sounds efficient. It is also a trap. Web wants rich item metadata and promotional content. Mobile wants compact responses with progressive disclosure. Customer support wants case IDs and exception details not suitable for consumers. Partners need a subset with a long-term stable contract.

A single generic API would either become bloated or endlessly versioned.

The better design is this:

  1. Order Summary Composition API
  2. - aggregates order, shipment, payment, return eligibility, and support-link status

    - exposes a domain-meaningful contract

    - built partly from synchronous reads, partly from Kafka-fed projections

  1. Web BFF
  2. - shapes order summary into sections needed by the web experience

    - adds CMS-driven messaging and personalization references

    - supports web caching and SSR concerns

  1. Mobile BFF
  2. - reduces payload size

    - bundles multiple calls into coarse-grained endpoints

    - supports app version compatibility and mobile-specific authorization scopes

  1. Support Portal BFF
  2. - extends the summary with internal exception codes, warehouse notes, and case links

    - not exposed externally

This split solved a political problem as much as a technical one. The domain-level composition was owned by a platform-aligned team with strong business process knowledge. The BFFs were owned by channel teams. Each team had autonomy without inventing its own truth.

The Kafka projections were crucial. Shipment events from warehouse systems arrived asynchronously. Payment settlement from the payment provider lagged order confirmation. Return eligibility was recalculated nightly with policy overlays. A purely synchronous composition would have been both slow and fragile. Instead, the composition API returned:

  • authoritative real-time order state from Order Service
  • latest known shipment timeline from a projection
  • settlement status with freshness metadata
  • derived return eligibility with policy version references

That last point matters. Mature enterprises do not just return values; they return enough metadata to explain them when the call center phones.

Operational Considerations

Composition and BFF layers sit in the blast radius of many failures. They deserve production-grade engineering, not “it’s just an adapter” neglect.

Latency budgets

Every composed call needs an explicit latency budget. Without one, teams stack synchronous dependencies until p95 becomes a hostage note. Set budgets per endpoint and per downstream call. Decide where to degrade gracefully.

For example:

  • order header may be mandatory
  • shipment ETA may be optional
  • personalization may be soft-fail
  • support case summary may time out independently

Not all data deserves equal blocking rights.

Partial failure handling

Composition rarely fails cleanly. One service times out, one returns stale data, one gives partial results, and another responds with a 200 carrying nonsense. Design for partial response patterns where the business allows it. Include completeness indicators and freshness timestamps.

Observability

Distributed tracing is table stakes. More importantly, trace by business request, not just technical spans. When a customer says “my order page was wrong,” you need to reconstruct which service contributed which values and from what event or snapshot.

Caching

BFFs often benefit from aggressive response shaping and caching. Composition layers can cache too, but be careful: if they aggregate multiple sources with different update frequencies, cache invalidation becomes a board game nobody wins. Prefer caching around stable read models or projections instead of raw fan-out results.

Security and authorization

BFFs often terminate channel-specific auth and convert claims into downstream scopes. Composition layers should not become backdoor privilege escalators. Keep authorization semantics explicit. Internal support views and consumer views should not share loose contracts with hidden fields.

Versioning

BFFs are a natural place to absorb frontend evolution. Shared composition APIs should evolve more cautiously, because more than one channel may depend on them. If every frontend change requires changing the shared composition contract, the boundary is wrong.

Reconciliation and data quality

This deserves emphasis. In event-driven systems, composed views drift. Offsets lag. events arrive out of order. source systems backfill. idempotency breaks under rare retries. A robust architecture includes reconciliation jobs, dead-letter handling, replay strategy, and business-visible freshness indicators.

If your dashboard can silently lie, it eventually will.

Tradeoffs

Let’s make the tradeoffs plain.

API Composition advantages

  • Reduces client chattiness
  • Encapsulates cross-service assembly
  • Preserves domain-level semantics
  • Reusable across channels when designed well
  • Good home for application-layer orchestration

API Composition disadvantages

  • Can become central bottleneck
  • Adds synchronous dependency chains
  • Tempts teams to blur bounded contexts
  • Hard to maintain if every use case is routed through one layer
  • Vulnerable to cascading latency and partial failures

BFF advantages

  • Optimizes for specific client needs
  • Shields frontends from internal topology
  • Enables frontend team autonomy
  • Useful for mobile optimization, channel-specific auth, and presentation shaping
  • Good place for experience-level adaptation

BFF disadvantages

  • Duplicates logic if used carelessly
  • Can reintroduce siloed APIs per channel
  • Risks embedding business rules in experience layers
  • Hardens client-specific contracts that become expensive to retire
  • Can create several mini-monoliths behind the UI

Combined approach advantages

  • Separates business composition from presentation shaping
  • Balances reuse and autonomy
  • Supports progressive migration
  • Works well with event-driven read models

Combined approach disadvantages

  • More moving parts
  • More teams must coordinate on contracts and ownership
  • Easy to over-engineer if scale or client diversity is modest

There is no free lunch here. There is only paying the bill in the place you choose.

Failure Modes

These patterns fail in recognizable ways.

1. The god composer

A central composition service becomes the universal integration point for every team. Soon it knows all domains, owns no domain, and changes require cross-team governance. Congratulations, you have rebuilt the monolith with worse latency. EA governance checklist

2. Business logic leaks into BFFs

Teams start implementing pricing rules, order cancellation policy, or eligibility calculations in the BFF because “the frontend needed it quickly.” Six months later, web and mobile disagree on what a customer is allowed to do.

3. Excessive synchronous fan-out

An innocent “summary” endpoint makes twelve downstream calls, five in series. Under load, p99 collapses and retries make everything worse. This is the most common operational faceplant.

4. Stale projections without reconciliation

Kafka-backed materialized views are introduced for performance, but replay, backfill, and poison-message strategies are weak. The system serves stale or inconsistent summaries with no freshness metadata and no operational alarms.

5. Shared contracts polluted by UI concerns

A composition API starts carrying channel-specific fields because “it’s easier than creating another endpoint.” Over time, the API loses coherence and can no longer evolve without breaking unrelated clients.

6. Ownership ambiguity

No one knows whether the BFF team, platform team, or domain team owns a cross-service field. Work stalls, bugs bounce, and architecture turns into organizational archaeology.

When Not To Use

Do not use API Composition if:

  • a single service already owns the data and behavior needed
  • composition would merely paper over poor domain boundaries
  • request-time fan-out would violate latency or reliability requirements
  • a projection or read model would serve better
  • you are creating a generic aggregation layer with no clear bounded purpose

Do not use BFF if:

  • you have only one stable client with modest needs
  • the “frontend-specific” backend is actually implementing shared business policy
  • the organization cannot sustain ownership per channel
  • you are using it to hide an incoherent service model rather than improve client delivery

Do not use both if:

  • your system is still small and the complexity would be ceremonial
  • your team lacks operational maturity for distributed tracing, timeout design, and event-driven reconciliation
  • your biggest problem is unclear domain modeling, not API shape

Patterns should solve pressure, not advertise sophistication.

Several related patterns commonly show up nearby.

API Gateway

An API gateway handles concerns like routing, auth, rate limiting, and protocol mediation. It should not become your business composition engine. Some gateways can compose, but capability is not a mandate.

GraphQL

GraphQL can be an excellent client-facing query layer, especially with varied data needs. But GraphQL does not remove domain modeling. A badly designed GraphQL federation can spread coupling with impressive efficiency.

CQRS

Command Query Responsibility Segregation pairs well with composition. Queries often benefit from precomputed views, while commands remain routed to owning services.

Saga / Process Manager

For long-running business workflows, sagas coordinate state transitions across services. This is different from read composition. Do not confuse a workflow orchestrator with a UI aggregator.

Materialized View / Projection

Often the right answer for complex read scenarios in event-driven systems. Particularly useful when Kafka events can be consumed into query-optimized stores.

Strangler Fig

The migration pattern of wrapping and gradually replacing legacy functionality. Essential for enterprises modernizing from monoliths or packaged platforms.

Summary

API Composition and Backend-for-Frontend are not rivals in a cage match. They are different tools for different tensions in microservices architecture.

API Composition is about assembling multiple service capabilities into a coherent, business-meaningful response. It belongs in the application layer, close to domain semantics, and often benefits from event-driven projections and reconciliation logic.

BFF is about shaping backend interactions for a specific client experience. It belongs close to the channel, should optimize payloads and workflows for that client, and should remain thin in core business logic.

Use API Composition when the use case is shared and domain-relevant. Use BFF when the need is channel-specific and experience-driven. Use both when you need shared domain composition plus client-specific adaptation. And resist the old enterprise temptation to turn either one into the new center of the universe.

Because that is the real lesson here: in distributed systems, aggregation is unavoidable. The question is whether you do it deliberately, with clear language and ownership, or accidentally, by letting convenience become architecture.

Convenience is how monoliths are reborn.

Frequently Asked Questions

What is a service mesh?

A service mesh is an infrastructure layer managing service-to-service communication. It provides mutual TLS, load balancing, circuit breaking, retries, and observability without each service implementing these capabilities. Istio and Linkerd are common implementations.

How do you document microservices architecture for governance?

Use ArchiMate Application Cooperation diagrams for the service landscape, UML Component diagrams for internal structure, UML Sequence diagrams for key flows, and UML Deployment diagrams for Kubernetes topology. All views can coexist in Sparx EA with full traceability.

What is the difference between choreography and orchestration in microservices?

Choreography has services react to events independently — no central coordinator. Orchestration uses a central workflow engine that calls services in sequence. Choreography scales better but is harder to debug; orchestration is easier to reason about but creates a central coupling point.