Six product teams ship a web app and an iOS app against 23 domain services. A typical screen needs data from four to nine of them. Mobile releases take roughly three weeks to reach 90% of installs. Round-trip time on mobile networks is 60-150 ms. The platform team must pick one aggregation approach for the next two years. Which fits?
Show the full answer Hide the answer
The deciding property
Not payload size and not round trips — the three-week tail of mobile installs. An iOS build released today is still being used in a month, so the server must keep returning a shape that nobody writes any more, for as long as the client team decides. That obligation is a client-team obligation, and the only structure that places it with the team that can discharge it is an aggregation layer the client team owns and deploys.
The round-trip arithmetic sets the floor: six dependent calls at 100 ms is 600 ms of pure network before any rendering, so some aggregation is required. The ownership question decides which kind.
Why this one
A BFF per experience lets the iOS team keep a deprecated field alive for five weeks and delete it the day adoption passes their threshold, with no cross-team ticket. It also keeps the web BFF free to change daily, because web has no install tail at all. The cost is real: four aggregation layers to run, four sets of timeouts and deadlines, and a standing temptation to put domain logic in them. The boundary test is one sentence: if a rule would be wrong for a different client, it belongs in the BFF; otherwise it belongs downstream.
Why the other options fail
- A single shared aggregation service is the thing that produced the BFF pattern. It is owned by a team whose roadmap is not the product's, so screen changes queue behind someone else's sprint, and its owner has no reason to carry a dead field for the iOS tail. It is the right answer when there is one client, or when the clients genuinely want the same shapes.
- A federated graph fixes over-fetching and the waterfall well, and fixes neither ownership nor the version tail: deprecating a field still requires knowing which client versions select it, and now 23 teams share one schema that needs governance, depth limits and persisted queries. It becomes the right call when many clients want many shapes of the same graph and you can staff the graph as a product.
- No aggregation layer is right when a screen needs one or two services, and here it is six round trips plus six token validations plus six failure modes on the client — and the retry policy ends up in the app store.
What would flip the decision
| If this changes | Choose | Because |
|---|---|---|
| Only a web client remains | shared service or direct calls | no install tail to absorb |
| Screens need 1-2 services | direct calls | the waterfall is 100-200 ms |
| One team owns all clients | shared service | ownership and cadence already align |
| 50+ clients including partners | federated graph | per-client backends stop scaling organisationally |
When this is the wrong answer
Four engineers building one product with one client should not build a BFF. Shape the responses in the service that already exists and revisit when a second client appears with a different screen.