Hacker News new | ask | show | jobs
by kuang_eleven 1370 days ago
I can't say I see the point of this, unless you have a criminally unresponsive backend dev team.

Any half-decent backend API will offer parameters to limit the response of an endpoint, from basic pagination to filtering what info is returned per unit. What's the use of extra complexity of a "BFF" if those calls can be crafted on the fly.

And to be clear, I am not suggesting that custom endpoint be crafted for every call that gets made, that just seem like a strawman the article is positing; but rather that calls specify when info they need.

4 comments

The article doesn't do a great job at explaining that this isn't always just filtering, sometimes it's aggregation too.

A mobile client may need data points to display a single page that require calling 20 different APIs. Even if every single backend offered options for filtering as efficiently as possible, you may still need an aggregation service to bundle those 20 calls up into a single (or small set) of service calls to save on round-trip time.

You still have to aggregate somewhere. You can do it on the client or the frontend backend, it still has to get done. In the case of the latter we’re adding one extra hop before the client gets their data.

This pattern is advocating for reduced technical performance to accommodate organizational complexity, which I think the parent finds odd. You either have the client call 20 service/data?client_type=ios or you have the frontend backend call 20 different service/data?client_type=ios (after the client called)

> In the case of [backend for frontend] we’re adding one extra hop before the client gets their data.

> You either have the client call 20 service/data?client_type=ios or you have the frontend backend call 20 different service/data?client_type=ios

The article touches on this point, and it mirrors what I've seen as well. The time from client -> backend can be significant. For reasons completely outside of your control.

By using this pattern, you have 1 slow hop that's outside of your control followed by 20 hops that are in your control. You could decide to implement caching a certain way, batch API calls efficiently, etc.

You could do that on the frontend as well, but I've found it more complex in practice.

Also a note: I'm not really a BFF advocate or anything, just pointing out the network hops aren't equal. I did a spike on a BFF server implemented with GraphQL and it looked really promising.

You won't necessarily have to have ?client_type=xyz params on your endpoints if the BFF can do the filtering, so it saves having to build out all sorts of complexity in each backend service to write custom filtering logic. Of course, you'll pay the price in serialization time and data volume to transmit to the BFF, but that's negligible compared to the RTT of a mobile client.

I'd much rather issue 20 requests across a data center with sub-millisecond latency and pooled connections than try to make 20 requests from a spotty mobile network thats prone to all sorts of transmission loss and delays, even with multiplexing.

> You still have to aggregate somewhere.

Tbh, I'm not entirely sold on this - although I see this (server-side aggregation a cross data sources) as the main idea behind graphql. So seems like it belongs in your graphql proxy (which can proxy graphql, rest, soap endpoints - maybe even databases).

But for the "somewhere" part - consider that your servers might be on a 10gbps interconnect (and on 1 to 10gbps interconnect to external servers) - while your client might be on a 10 Mbps link - over bigger distances (higher latency).

Aggregating on client could be much slower because of the round-trip being much slower.

In addition, you might be able to do some server-side caching of queries that are popular across clients.

I agree with your assessment here, but one additional benefit is the capability to iterate faster on the backend. You have control over _where_ the aggregated data is coming from without waiting months for users to update their mobile app so that it sends requests to a new service, for example.
The article implicitly assumes that you have multiple backend teams and you need to combine the results from different services that belong to different teams. The services having interdependencies would lead to a giant ball of mud, so you need a service in front doing that for you. Now if you have also more frontends with different requirements who is going to take responsibility in the central service?

The architecture really only makes sense if you have a lot of people that would step on each other’s toes if you don’t assign them their areas and would come to a halt if you didn’t gave them enough autonomy.

The article puts forward a slightly different proposition, but IMO BFF in smaller organizations are often managed by backend teams (makes sense seeing that "backend" is still the name...). Same goes for teams with full stack devs, they'll be touching whatever layer they want.

Having multiple layers of backend services can have benefits, and one of these layers would just happen to be dedicated to the public facing frontend. To me the one of the main advantage is to make it easier to manage the security settings and applying different assumptions across the whole API. It is single purpose, so it helps a lot for customization and management (or even choosing a different stack altogether, having different scaling strategies etc.)

It becomes something managed really differently when the "real" backend is opaque and off limits (as describe in the article), but then I'm not sure we should call that "BFF", it's just a regular backend for that team as they have no other backend in charge (i.e if I had a single backend API for a mobile app, that I use to interface with Stripe, I wouldn't call it "BFF", that would make no sense)

I've got some vendors. One of them is still in the Stone Age and has difficulty with anything more than http basic auth. The other can handle a more modern OAuth setup.

Each of these vendors needs a list of accounts in an area. Deep in the stack, the list of accounts and managing individuals tied to those accounts is one service. The vendor doesn't need access to the rest of the service, just the list of accounts.

Each vendor also needs to be able to submit orders and issue refunds. That's a different backend service.

One of the vendors has been known to be... shall we say "inconsiderate" in the rate of requests. It is important that the inconsiderate vendor doesn't impact the other one's performance.

We could add in basic auth into those back end services and add in some more roles for each one of the vendors. This would complicate the security libraries on the back end services needing to accept internal OAuth provider, external OAuth provider, and basic auth - and making sure that someone internal isn't using basic auth because they shouldn't be. And trying to handle per account/application rate limiting on those back end services that really don't need to or want to care about the assorted vendors that are out there.

So, we have a pair of BFFs. They're basically the same, though there's a different profile setting in Spring Boot to switch which set of auth is configured - if its the vendor that uses the external OAuth provider then the external OAuth configuration is used. If the profile is for the other vendor, then the basic auth is used.

Each BFF calls the appropriate internal services and aggregates them - so that the internal services don't need to be concerned with the vendors. There's a BFF that has two instances and has access to these endpoints - that's good enough for the internal services. Likewise, each BFF has its own rate limit so that the inconsiderate vendor won't overload the backend or accidentally rate limit the other vendor out of being able to make requests.

The BFF handles the concern of the vendor. Aggregating the internal requests, rate limiting that vendor, providing isolation between the two vendors, and each handling the authentication and authorization for the vendor. By putting those in the BFF, those aspects of making requests to the vendor are kept isolated.

Additionally, the API contract for the vendor can be held constant while internal teams can change the internal API calls without worrying about if that data is leaking out to the vendor accidentally. Services internal can be updated with new endpoints and the BFF can be changed to point to them as long as the external vendor API contract remains the same - without needing to involve the vendor with a migration from a V1 endpoint to a V2 endpoint.