Replacing the traditional REST APIs, GraphQl claims to reduce the number of trips to the server helping developers building applications that run faster on different devices and consume fewer data.
On the developer side, GraphQL introduces with a static query language for the data layer, server query validation, and an exceptional set of tooling that makes development smoother, reducing bottlenecks related to the backend and front-end integration.
However, when adopting a new technology knowing what it implies for our systems is crucial.How will the system look like? What are the strong points that we can leverage? What are the boundaries and limitations? How will the CI/CD pipeline look like? What will be really easy? Where will the challenges lay?
Since GraphQL was open sourced only four years ago, experience in production is limited mostly to grey literature (blog posts, conferences). And so, the question arises: is there any way in which the new paradigm can be understood and the questions above answered?
Although GraphQL influences our architecture uniquely, it builds on well-known design patterns. The patterns have been already used and analysed extensively in production environments, and the experience can be used when designing GraphQL systems. As Newton once said:
If I have seen further it is by standing on the shoulders of Giants. — Isaac Newton
In the next section, we take a look at the most significant patterns GraphQL introduces:
- The API Gateway Pattern
- The Integration Database Pattern
- The Data Federation Pattern
The advantages and limitations that GraphQL inevitably inherits from these patterns are discussed and an architecture that scales is proposed.
The architectural choice that allows GraphQL to reduce the number of trips to the server and to unify different data sources under one unique interface is the API Gateway Pattern. An evolution of the Facade Pattern from the Object-Oriented Design world, this paradigm enables hiding implementation details under a uniform interface offered to consumers.
Building on this pattern, GraphQL uses request routing(at resolver level) and unifies edge functions(caching, authentication) speeding up development in a world of microservices.
Aside from the benefits presented above, this pattern also comes with certain limitations. The first and most important is that it introduces a single point of failure in the architecture, increasing system risk.
To ensure availability and reliability, we must prepare our GraphQL servers for scale: using container technologies, setting up autoscaling strategies and distributed logging are just a few of the many tasks that become important when setting up an API Gateway.
An API Gateway containing the client interface also introduces an asymmetry in the development cycle. Developing several clients in parallel around a unique GraphQL service requires an extra effort in release planning.
Schema releases must be syncronized and negotiated with more than one client team and with the schema owners. Without special attention to this aspect, the development of the API Gateway can quickly become a development bottleneck.
One of the biggest promises of GraphQL is that it allows the development of a single backend endpoint while being able to serve data to different clients.
The Integration Database Pattern
Although mostly applied to database architectures, this idea is also nothing new. Also known as theIntegration Database Pattern or the Shared Database Pattern in the world ofEnterprise Integration Patterns, it promises faster synchronisation for applications that share the same data (ex. different clients).
However satisfying requirements from different clients, with different users and even different development teams under the same interface at the same time, is something not easy to achieve.
Modern web application development advocates for pushing as much as possible business logic to the backend, being the front end only responsible for the presentation(view layer).
This often includes formatting in the case of numbers, abbreviations for longer text, locale information and so on, so forth. Attempts to conciliate and accommodate all these different requirements in the same schema and sometimes under the same data structures end up in bloated GraphQL types like the one below, and of course, code duplication at resolver level:
A bloated type example
Of course, in this case, we can delegate the amount fetching to a completely new type(e.g. ItemAmount) and build a more generic Item type.
Although, generic types help satisfying divergent requirements they decrease transparency(lack of context) and increase development time.
Having many client applications relying on one single type converges into a schema of“monolithic” types that satisfy all the client requirements but few can understand and even more important resolve. As Fowler mentions:
“The resulting links between applications and databases end up being brittle and thus difficult to change.”
— Martin Fowler, Database Styles 2004
Although the Integration Database Pattern enables relatively fast development in the short and middle term, in the long run, the deep coupling of the different clients will slow backend development as the requirements on the same data structure will diverge.
Last but not least, one of the most acclaimed features of GraphQL is that it allows engineering teams to“map”the business domain to a single schema, a central dictionary for the enterprise.Increasing transparency and decreasing communication overload are just one of the few advantages that this pattern brings to the table.
A simplified Federated Schema Pattern implementation
The idea of unifying knowledge in an organisation under a single (distributed) dictionary and present it to client developers as a uniform interface was already introduced by theData Federation Pattern.
Although subsets of the Federated Schema(Export Schemas) are developed separately and individually, they are merged into a uniform and unique schema also used as“live documentation”for client applications.
The pattern, used by IBM back in the 2000s as core to some of their stellar products(InfoSphere Federation Server) tried to bring uniformity to the enterprise internals. Federated Database servers often come with out of the box schema merging tools and query delegation and aggregation systems.
As GraphQL adoption grows in the enterprise, Apollo, the main vendor of GraphQL tooling and solutions, released theApollo Federation.
Shortly said, Apollo provides us this time with the tooling need in order to maintain an orchestrate a distributed graph. Utilities for merging different schemas anddirectives for cross schema referenceare just some of the elements included in the recently open-source project.
Apollo Federation Distributed Graph(Federated Schema)
Although the work of the Apollo team is brilliant, the concepts introduced are not trivial at all. The wonderful tooling does not eliminate the need for the so-called “schema board” where changes in the schema are being negotiated between clients and the schema owners. As Phill Calcado mentioned earlier this year:
“.. it is hard for me to believe that you can combine the needs of many different applications, owned by different teams, with different users and use cases, in a single schema”
—Phil Calcado, Some thoughts on GraphQL vs. BFF 2019
The effort of synchronising and unifying requirements coming from different contexts and owned by different teams grows faster than the benefits of a unique dictionary after certain scale.
Although Federated Schema Models are a natural step for certain organisations there are alternatives where instead of managing the complexity of a distributed unique schema the situation is avoided altogether.
So how can we use GraphQL at scale and survive? One of the most promising solutions is the Backend For Front End Pattern. The pattern, introduced by the team at Sound Cloud back in 2012 when they were trying to scale the product to emerging mobile devices, implies building an intermediate backend service explicitly designed for a frontend domain.
Backend For Front End setup with GraphQL
The GraphQL service is owned by the client team and developed against a common backend interface. In this way, the individual requirements of the client remain constrained to the BFFE instances and the “indirect” coupling between clients is highly decreased.
The unique schema is divided by client needs, and although this will generate certain code duplication(types, resolvers, edge functions)the overall cost of change of the system is much, much lower than a single distributed schema.
Using this pattern in combination with GraphQL allows architects to take advantage of the strong point of GraphQL and push the boundaries of scale and development speed. At the same time, resolver and schema development are faster as types are less generic and the less ambiguous.
In conclusion, the tradeoff of code repetition and duplication of edge functions is much easier to manage successfully than the constrains of a single schema served by a single API Gateway with deep client coupling.
Building and using new technologies is one of the most exciting parts of working in engineering, yet we should not forget that new paradigms are usually the result of using “old ideas” applied differently or in a new context.
This article is the written version of the talk I gave at the Berlin GraphQL Meetup organised by Prisma. You can find the full version on Youtube: