Architecting GraphQL Distributed Systems

Maria Lucena
CloudX at Fidelity
Published in
8 min readFeb 3, 2023

By Maria Lucena

GraphQL as a technology has grown in adoption in recent years for many reasons. For the end customer, the ability to request data from multiple sources to a single endpoint improves the user experience and can make the connection more performant. For developers responsible for API implementation, the ability to define the capabilities of the system as “types” brings many benefits to the development cycle. These benefits also translate to business gains like faster prototyping and reduced system redundancies. For the developer community, the GraphQL Foundation has provided neutral stewardship for the GraphQL specification and reference implementation.

But what about scale? Most companies start with a single graph or “monolith” to learn. However, monoliths come with limitations and the need to distribute the graph quickly becomes apparent. You can implement distributed graphs a few different ways; one solution is to use the approach of “federation” which was first introduced to the GraphQL community in 2019 by Apollo. While this intended to solve the need for serving distributed GraphQL APIs, there are still a lot of unanswered questions when it comes to Fidelity Investments’ infrastructure. For that, we need to rely on our expertise at Fidelity when tackling enterprise-level complexities. As teams prepare to implement a graph of enterprise grade, there are considerations outside of simply serving a GraphQL schema. A unified graph can potentially serve the capabilities of the entire organization. To create that, we need to take a step back and correctly plan, design, and implement such a complex graph.

In this article, I will discuss observability tools to leverage at each stage of transformation from monolithic graph to a federated system. I presented these learnings at the 2022 GraphQL Summit in San Diego.

Planning Considerations

Let’s take a step back from our technical frame of mind to think about the business value that technology can deliver. In any business, there are multiple work streams, competing priorities, and a desire to maintain competitive advantage. At Fidelity Investments, we use the Business Architecture framework to help us align our work to business goals. Business Architecture gives business decision makers and technologists a complete view of the organization and a way of communicating effectively to translate business strategy into actionable initiatives. The result is a clear direction with stakeholders working together, speaking the same language, and helping each other succeed.

In addition to the Business Architecture, it is important during planning to maintain a business capabilities mindset. A capability is something a business does or can do. In the context of the Business Architecture, capabilities are the actions performed on business objects (the nouns that make up the business language) and these are ubiquitous across an organization. Therefore, a business capability represents a unique action in a company regardless of business unit. Business capabilities are the building blocks that make up the enterprise. If this mindset is adopted when building things, everyone involved is no longer hyper-focused on a single business deliverable; but rather, everyone is focused on creating an ecosystem that is more easily extensible (designed to accommodate change) because it was thoughtfully designed.

Financial Planning Application

Now that we have laid out some good planning concepts, let’s observe the Financial Planning Application proof-of-concept.

Back-end For Front-end (BFF) monolithic graph

This Back-end for Front-end (BFF) monolithic graph can be transformed into a GraphQL federated system without sacrificing functionality and achieving a flatter architecture. Here, multiple domains within the financial planning profile bounded context are used to serve the app. By the time we are done moving to federation, the customer should not experience any disruption; but on the contrary, should benefit from improved architecture transparency.

Financial Planning Domain Services

We should now contemplate the design aspect of the Financial Planning API. The functionality is separated by concern. This is thanks to the fact that, at Fidelity, some of our teams rely on the time-proven principles and patterns of Domain Driven Design (DDD). This structure lends itself to a distributed architecture. Looking at the codebase, you can see that root folders are separated by Applications and Libraries, a convention in NX tooling. Applications hold bare minimum functionality to serve the BFF graph. The libraries define, among other things, domain services. Thanks to the principle of “separation of concerns,” those services only define functionality pertinent to their particular domain. In general, the definitions there include DAOs, GraphQL resolvers, and parts of the schema that only relate that specific domain. This manner of organizing code allows for an organic growth into federation. Each of those domain services can be turned into subgraphs and deployed separately. Thanks to thoughtful engineering planning practices when building the platform, all the things that make up a subgraph are part of each library module.

Distributing the Graph

GraphQL as a technology does not solve for everything; but what it solves, it does so well which is why it is part of Fidelity’s technology stack. Our organization is quite sizable with many domains and countless customer journeys, and moving to federation proved beneficial on many levels. Separating the schema by domain and then bringing the system together in a unified graph gives us the power needed in our ecosystems.

As mentioned above, there are multiple ways of distributing graphs. Federating a graph is not a silver bullet. It comes with the challenges of a distributed microservice architecture like design complexities, observability in a distributed system, and operational overhead. So, if you are contemplating distributing your graph, much like anything else, weigh your options.

The patterns described below are inspired by leading federation implementations and discussions in the GraphQL ecosystem.

Federation Patterns

As part of our experimentation, we had to ensure that our graphs were an accurate expression of our capabilities and domains as we moved the existing functionality to a federated system. To do that, domain expertise was required to correctly identify entities and establish relationships between them. After doing that, the following patterns emerged.

Core Entities

Core entities

Core entities are the easiest things to tackle especially if your functionality has already been separated by domain. Another way to find them is by looking at your business objects, using DDD, or any design tool that can help with identifying entities. Entities are at the core of federation. To define an entity, all you must do is add an @key directive and create resolver reference.

Contributing Fields to Core Entities

We have the need to create relationships between those entities. We do this by contributing fields to core entities.

Contributing fields to core entities

This pattern represents the simplest of relationships between the entities. While the code to accomplish this is straight-forward, it does require domain knowledge, good naming practices, and a sensible governance body to help teams decide the best place for adding their functionality.

Contributing Computed Fields

This pattern builds on entities’ relationships. In this use case, we are adding a field to an entity that holds data needed for calculation in an external subgraph to the one requiring it. Federation specification provides the @requires and @external directives to achieve this.

Contributing computed fields

The @requires directive tells the router or gateway that as it resolves the entities in the query plan, more data is needed by the requiring field from the external subgraph. This solves the challenge of bringing data (that used to be defined in the same runtime when the graph was served as a monolith) back together. Field computation and complex data aggregation is handled with this solution.

Computed Fields from Multiple Subgraphs

In this pattern, we have computed fields from multiple subgraphs. A system may have an entity that defines a computed field which requires data from external subgraphs. What is different about this pattern is that the entities involved in the computed field do not have to be related. To demonstrate, imagine a scenario with three subgraphs: Customer, Account and Experience.

Computed fields from multiple subgraphs

Experience defines the Content entity with the field customerAdvice, which requires data from Accounts and Customer to compute. The @requires specifies the list of fields needed by from the external subgraphs. There can be nesting applied, but the types involved need to be resolvable by other subgraphs. This instructs the router to fetch the extra values from Customer and Account and pass it down to Experience so it can resolve customerAdvice without having to explicitly query for values in those subgraphs. This is an extremely valuable pattern to support. It gives federation systems the flexibility to extend entities and to use their data in calculated fields without having to disrupt their definitions or to tightly couple entities that should not be aware of each other.

Financial Planning Federation System

Finally, the Financial Planning Federation System is a flatter architecture, less complex configuration, less network hops, and more clearly defined components. While this capitalizes on the benefits of GraphQL, it also gives more clarity and better support to our capabilities and domains.

Financial Planning federation system

Key Takeaways

In summary, the process outlined here is one architectural example of implementing a distributed graph. If your company is focused on driving customer value and aligning business strategy to its technical execution, consider using the Business Architecture framework with a business capabilities mindset. Domain Driven Design can help you connect domain experts with developers and create platforms that serve the business capabilities better and accelerate the delivery of those. Federation can help your growing graph. It is best suited for distributed systems, and it is designed to help with the management and delivery of that. Finally, whatever solution you choose for your federation journey, use it intelligently. The tools mentioned here can be complex which requires a good grasp of their concepts to conquer them. Understand how they work, train your teams to use them, and make the most of out of it. Happy adoption journey!

--

--

Maria Lucena
CloudX at Fidelity

Software Architect at Fidelity Investments. Spirited technologist with a focus on backend solutions.