In the “Leveraging microservices for your business” series, principal architect, Matt Bishop has been addressing areas of focus to help organizations and developers determine if microservices are the right business choice. Part one described microservices as architectural qualities, while part two went through the benefits and challenges of building a microservice ecosystem.
In this third and final installment, Bishop will address one of the most critical challenges microservice implementations face, the backend for frontend pattern.
Adrian Cockcroft states microservices is a "loosely coupled service-oriented architecture with bounded contexts." Loosely coupled architectures must have very little interaction with each other to fulfil their core business purposes. They share business identifiers as the logical thread through their combined capabilities. These capabilities are scoped by bounded contexts that represent independent, irreducible states of a business concern.
This architecture can deliver correctly-modeled business systems that are resilient, scalable and highly adaptable to business needs. Unfortunately, they are very difficult to consume as a client. The independent services usually have different API semantics to access their data and change their state. They are hard to discover and orchestrate to produce what the client wants, which is a cohesive consumer experience. The microservices solution is to create a new microservice—the BFF.
BFFs Are Not Your Friends
The Backend for Frontend (BFF) pattern pulls the necessary microservices together into a single experience for a specific consumer, like an iOS app or a progressive web app (PWA). Sam Newman, a well-known author and thought leader in the microservices architecture world, explains that a BFF microservice should be tuned to serve specific clients rather than be a general-purpose service for all clients. Each client needs different data, different actions and has different protocols, so they should not try to be shared, but rather copied and modified for each new client.
The BFF pattern has problems in practice, however. Often the responsibility to build a BFF falls to the client development team that will consume it. A client team is composed of software developers who are experts in front-end technologies. They are rarely competent to build server-side services that support their clients. Server-side code has completely different frameworks, semantics and concerns compared to client-side code. Many developers who have to write BFFs do not have the experience to create a BFF that will succeed in production, under load, and over the long term.
Another problem is that this team needs to fully understand the business rules and workflows in order to create the right BFF. In commerce, this is not a trivial undertaking. The good news is that this logic, which often lives in the client code, now lives on the server where it can be better managed and maintained. The bad news is that every new client team needs to know these rules and workflows as well. Their efforts are largely duplicated and often out-of-sync with each other.
These problems lead me to conclude that BFFs are costly, risky and an agility anti-pattern. BFFs throttle and constrain the microservices architecture at their gateway to the consumer by struggling to keep up with the microservices trying to deliver business value to the humans who need it.
Hypermedia to the Rescue
The general premise of the BFF microservice is wrong. Business logic and workflow orchestration must work the same way on all clients because the business itself provides the products and services to the customer. It would not be good if the Android app had different business rules than the iOS app.
Agility demands that new features come to market as fast as possible, yet features must follow the valid business rules and workflows provided. A single shared API can provide this correctness and agility, but it must not bear the negative attributes pointed about in the BFF justifications. At Elastic Path, we have found that a Hypermedia API offers the best sharable experience that can be shaped by the clients to produce just the right amount of data and affordances for any given client experience.
The big difference between a Hypermedia API and other forms of REST are the links in the representations. Links point to related data, and they also point to relevant affordances. Links provide next-best actions at runtime, so the client does not have to know what that action should be. All the rules and logic that drive the links live in one place on the server.
A Hypermedia API client is reactive; it must know that linked data and actions are possible, and it must react to the links (or lack thereof) accordingly. A good example is the commerce checkout flow. When viewing a Cart resource, links will point to actions like adding or removing items from the cart, but will also offer links to provide payment. The client will find the known "checkout" workflow link to be missing and will not offer a Checkout button until the link appears. Instead, it will see the "payments" link and present "Add Payment" controls tied to the linked resource on the cart. Once payment method is established, the server will send a "checkout" link with the cart representation and the client can show the Checkout button.
Hypermedia's control of link formation and presentation dramatically improve the time-to-market for a client app. An article by Shaun Maharaj shares the Hypermedia API client experience in great detail. Clients that are fast to build and deploy deliver the promised microservice agility all the way to the end consumer without risk of failure.
What about GraphQL?
If you are familiar with GraphQL, you will likely notice the similarities between links and GraphQL "connections." This is a good comparison as they are the way data is linked together in a GraphQL query.
Hypermedia formats often provide a similar querying and embedding pattern. HAL uses "_embedded" links and JSON-API offers an "includes" query to fetch aggregated data responses in a single request.
One important distinction between Hypermedia and GraphQL is when an action can occur. GraphQL Mutations are a flat, almost out-of-band concept for a client. They can learn what actions can be taken by looking at the Mutations list, but they do not know when they can be taken, and worse, they must rely on documentation and naming coincidence to actuate a Mutation. The client must operate defensively and make assumptions as to when a "Checkout" Mutation can be used. The client code must know that a "Provide Payment" Mutation must be first sent before checkout. And thus, the business logic makes its way back into the client where it does not belong.
GraphQL is a query language, meant for building views for a variety of client experiences. It is not a workflow orchestration API and is only a partial solution compared to Hypermedia.