The ultimate guide to GraphQL BFF on AWS AppSync
November 27, 2023 7 min read
Building a user experience (UX) and adding it to an existing application presents a few challenges when it comes to efficiently working across teams at scale. We need front end developers to have the capacity to give the best interaction on desktop and mobile browsers and across mobile or wearable devices. However, to make those interactions work they need to be tied into the APIs that drive actions across the company. If multiple features are under different stages of development and support, iterating APIs directly is distracting. Moreover, it requires a context switch. Depending on the API it may be a big one. The team may need to switch languages, build testing harnesses, switch pipelines, swap debug strategies and coordinate the changes with all others doing feature development. The API will certainly require versioning, but can quickly lead to different UXs calling different versions, complicating support.
What is a backend for frontend?
To combat these issues, the Backend for Frontend (BFF) pattern was developed. While there are several variations of it, as the simplest form, the BFF is a UX specific API pattern built to provide an anti corruption layer between the system APIs and what is exposed to the front end developer. They select the calls and actions they need to take to support the UX, and the BFF developers support them by wiring the appropriate calls to other microservices together across your ecosystem. BFFs are a powerful tool in creating logical components and minimizeing context switches as you develop and maintain complex user experiences.
Applying the BFF
Exploring some of the tradeoffs in a BFF is better done with an example, so let’s consider Capital One Dining. Since a BFF is always tied to a UX, let’s start with the context of what we want to unlock.
When Capital One introduced Capital One Dining, a reservation booking platform for rewards cardholders to unlock unforgettable culinary experiences and exclusive access to sought-after reservations at a collection of top-rated restaurants, we exclusively teamed up with acclaimed chefs like José Andrés and Dominique Crenn and culinary industry titans including the Michelin Guide and the James Beard Foundation.
With the help of our partners, we give our cardholders the ability to make hard-to-get reservations at a carefully curated collection of more than 450 top-rated restaurants across 16 cities nationwide like Chef José Andrés’ minibar in Washington, D.C., Atelier Crenn by Chef Dominique Crenn in San Francisco, Itamae in Miami and Cadence in New York City.
Let’s pull just a few high-level requirements from this UX. We need to (1) integrate with external partners and their APIs, we need (2) information about the Capital One Customer and we need (3) specific data about their rewards status to confirm they can use the service - and that’s just the BFF data needs! We still need to create all of the user interactions, provide security and defense in depth, and most importantly, we need to add it seamlessly into existing user experiences.
Simple is better than complex, and complex is better than complicated. Just issuing a simple JWT and sequencing the calls to Capital One and our partners would result in something unnecessarily complicated. Even if we set aside maintenance, we’re taking time away from building that UX to try and coordinate API calls across multiple backends. Managing this way results in a UX that looks, in the best case, a little like this.
Focusing on UX integrations
I want our developers to focus on the UX, at this point they have all of the high context switch problems because we don’t have a BFF pattern in place. By applying the BFF, we immediately decouple the engineers focused on the user interface from those who can now focus on the integrations.
The BFF accomplishes its goal at this point, but the inner workings of this service are now where the complexity lies. The BFF and front end groups still need to coordinate what the API interfaces should look like, how it should be versioned and what the endpoint structure should be. Let’s assume this is architected as a RESTful API service for the moment.
RESTful interfaces are the prevalent standard at this point. The easy to compress JSON data and expressive verbs make the format exceptionally human readable, which is a great advantage. However, they have a fundamental problem for our Dining teams. When designing a full UX, API calls are expensive. Often, developers have to background and defer those calls and show loading screens while they complete. They can send significant amounts of data, and regardless of pagination or use case based endpoints, RESTful endpoints remain at their core producer-centric.
If the /rewards endpoint returns 45 fields pertaining to rewards statuses, that’s what the API returns. If only three of these are needed by the UX, we’re sending 42 extra fields. I’ve mentioned that BFFs are use-case specific, so why not just design it to send those three? That’s perfectly reasonable, but recall that we have to version this API and we want to share it across our Web, Mobile and Wearables users. The user interaction for the web may want or need more fields than the wearable interface. The mobile interface may only want those fields if the user clicks on a more detailed screen. The tendency, whenever we build producer centric APIs is to include what should be possible using the endpoint to avoid breaking API changes. This problem is known as over-fetching.
Problem: Creating an API design for decoupling
It actually gets worse though. Remember we’re producer-centric here, so organizing the API into categories such as /customer, /rewards and /vendor would make a lot of sense here. In fact, arguably that’s good domain driven design in the API. However, it leads to a second problem. As the front end developer, I now need to make multiple API calls. Even to show a simple interface to welcome the user by name and show the available restaurants in a particular geography, I need to call /customer and /vendor. To provide a cohesive interface using the BFF, I need to grow the overhead of the application; this requirement is called under-fetching.
What we need to solve the under-fetching and over-fetching problems is a way to invert the paradigm for the producer-centric RESTful API. We need a way to allow the front end developers to specify exactly what they need and provide only those fields, and have a single endpoint with which to interact. This is where technologies like GraphQL come into the picture. Open sourced by Facebook in 2015, GraphQL does exactly that. By creating a graph of the data available in a BFF, the client can call for specifically the fields they need and only when they need them. The standard leverages JSON to keep the benefits that exist in the RESTful interfaces, and as a bonus offers a web socket based subscription service to allow for event driven updates to flow through the endpoint.
Solution: Implementing BFF architecture to solve under and over-fetching
By implementing a BFF using GraphQL, all API calls from our front end developers know to always hit a simple /graphql endpoint with what they need, By implementing a BFF using GraphQL, all API calls from our front end developers know to always hit a simple /graphql endpoint with what they need. We have successfully inverted the paradigm. It gives us the decoupling we started with seeking, and doesn’t force an over-fetching or under-fetching problem.
We have a strong pattern at this point, and a standards-based solution! Since GraphQL frameworks exist in numerous prevalent languages today, the team can even choose the one with which they are most comfortable to build. Let’s not stop here though. We know we want a GraphQL solution and we want to focus on delivering the UX that adds value to our customers, unlocking that unforgettable dining experience! So we need to ask another hard question, is the setup, hosting and build of the GraphQL the best way to deliver this UX?
In other words, is that really a cost of doing business using this pattern? Provisioning compute that supports running GraphQL, delivering a fault-tolerant design and the associated security patches; how much time are we spending on the UX to better integrate customer, rewards and vendor data to service the UX versus keeping the service running and operational? Fortunately, in the Cloud, we have an alternative.
Benefits of using serverless GraphQL services with AppSync
We can use a serverless GraphQL service, like AWS AppSync, which provides the backbone for all of the GraphQL support, and lets us focus our resources on the code or integrations (e.g., DynamoDB table) that supports each element of the graph. We have out of the box resilience, and serverless scaling and since we can write our resolvers in any language we want using AWS Lambda, we don’t trade off any flexibility for our BFF developers. AWS AppSync even provides full support for the web socket subscriptions capability within GraphQL all without our teams ever provisioning a compute instance.
Key takeaways to navigate frontend challenges with GraphQL BFF on AWS AppSync
Embrace the transformative potential of GraphQL Backend for Frontend (BFF) on AWS AppSync to revolutionize frontend development.
By implementing this powerful pattern, developers can efficiently integrate external APIs, streamline coordination and focus on delivering exceptional user experiences.
These real-world examples emphasize the advantages of using a serverless GraphQL service, such as AWS AppSync, offering resilience, scalability and optimal resource utilization.
Elevate your UI/UX game and simplify the development journey with GraphQL BFF, ensuring a seamless and user-centric application. Join us at AWS ReInvent FWM313 to learn more about how Capital One and AWS are using Federated GraphQL APIs to reinvent backend for frontend architecture!