With the introduction of federated architecture, the model for building a new unified and single Graph API (by combining multiple graph schemas) has become even easier. The architecture provides a gateway layer that brings together different federated services into one unified API endpoint.
According to Principled GraphQL, a single Graph API should be split or decentralized yet interoperable among teams working in different locations. The major benefit is that as our schemas are split across multiple services, and as they grow or scale over time, federation via the “gateway” layer handles the hard part of combining or composing these schemas without much complex logic or intervention on our end.
With Federation, there is a separation of concerns, as different teams can independently work on specified portions of the graph simultaneously.
As a result of the problems associated with monoliths, the architecture introduced the idea of splitting our data graph implementation (separation of concerns) across multiple graph services or APIs that can be assembled later on (coordinated) via the “gateway.”
For context, using the same GraphQL syntax with federation, large-scale systems or applications decoupled as microservices are:
- Quick to understand and implement by new engineers, who can work independently yet synchronously on different portions of the graph schema
- Easier to modify with new features or bug fixes
- Easier to scale and deploy
- More manageable, thanks to less dependency and coupling between the different services
- Incrementally adoptable
- Implementation-independent in terms of programming language support
Here, we are going to critically examine and highlight the components of federation, and at the end of this article, we should have answered the questions outlined below:
- Is federated architecture a novelty?
- What problems does it solve that would be harder (or impossible) to implement with other architectural models?
In the process of answering these questions, we will get to learn about:
- The problems associated with monolithic setups/architecture
- The benefits of the federated architecture. Here, we will understand why it is different from others
- Features and use cases, components, design, and, finally, a rundown of a sample implementation
To easily follow along, It is advisable to have a basic understanding of GraphQL as a query language. Also, it would be useful to understand the basics of the graph data structure and how to build APIs or services with GraphQL. In order to start implementing federated architecture, we should also have a basic understanding of schema stitching, although this is not absolutely required.
The origins of federation
Before we proceed further, let’s begin by understanding the reasons or thinking behind the federated architecture. According to Principled GraphQL, “Though there is only one graph, the implementation of that graph should be federated across multiple teams.”
In simplest terms, the federation architecture advocates for a single data graph exposed via an API for querying all the data composed from different microservices. In essence, we don’t necessarily have to know which service the data is coming from.
This way, we can write our code in a declarative manner, without worrying about how to combine the different graph services into one unified interface for querying. Why is this important? So that we can have a solid representation of our entire data graph, accessible in an efficient and optimized way via the gateway layer — which connects and composes these data sources from multiple different services.
Now another question arises: Would this same implementation be possible via other means, and if yes, what makes federation more performant or any different? We are going to be answering these questions in the coming sections.
Problems with monoliths
Monolithic graph servers are hard to manage and maintain. This is because the system becomes increasingly tightly coupled as functionality or feature set grows. As a result, little changes become a huge challenge, whereby single failure points can bring the entire system down as they are hugely dependent on each other.
Again, according to GraphQL Principles, there should be an abstraction layer between services and consumers of those services. For monoliths, this is usually not the case and eventually leads to issues with scalability and adaptability, even when deployed on a solid infrastructure. Due to the nature of monoliths, schemas are tightly coupled or bound to service implementations, which:
- Reduces reusability of schema fields as the codebase evolves
- Leads to difficulty in refactoring the schema
- Produces an ever-growing challenge in maintaining, deploying, and scaling these applications due to the (usually) large size of the codebase
Also, because monoliths can lead to a slow feedback loop among dev teams when implementing a change, there might be a sort of friction. This would subsequently reduce the velocity of distributed teams. Additionally, as monoliths tend to involve a very large codebase, complex logic increases the startup and response time of the application.
For monoliths, it becomes obvious that anytime there is an upgrade or change to the system, there is bound to be downtime for some period, no matter how little the change may be…”
We should also note that migrating monoliths to a different kind of technology, stack, or architecture would initially pose a very difficult challenge. This is because the whole application would be required to be thoroughly tested and re-deployed after the upgrade. This makes monoliths even less flexible and fault-tolerant, not to mention difficult to maintain.
Features of federation
For a microservices-based architecture, we can implement federation with numerous advantages. Also, schemas are organized by features and teams, so there is more or less a strict separation of concern. When we discuss the components of federation in the next section, we will get to understand how, despite this separation of concern, services get to extend types defined in other services.
Furthermore, another sweet spot where federation shines is in the area of incremental adoption. You can introduce it gradually in a codebase, making it easy to move away from on architectural model to something else. For example, to key into the federation hype, we can gradually migrate our codebase from, say, a monolithic graph server by splitting each module into a bunch of microservices.
Note: Other federation features can be derived or inferred from Principled GraphQL. Additionally, federation offers other advanced features, which makes it easy to integrate with legacy APIs and to construct complex schemas using primary and compound keys, etc. More details can be found in this section of the documentation.
Components of federation
For schema stitching, the process of communicating or representing the relationship between individual services are configured manually on the gateway layer. However, connecting data between multiple services is where federation truly shines.
The two essential techniques for connecting data are type references and type extensions, which are also referred to as directives. In this case, each individual GraphQL server generates an independent schema that can be run as a standalone component.
Federated architecture requires adding directives and resolvers to our schema fields.
Basically, the GraphQL gateway layer only needs to be aware of how the different APIs or graph services can be reached, and it is able to automatically create a single graph API endpoint. How does this work? Basically, each service can expose their APIs by using entities and keys. This is how services connect, as entities enable a type to be referenced by another service.
Entities and keys are the foundation of a federated graph. Here, the keys basically act as directives, which aid in uniquely identifying instances of a field type. Details about this can be found in this section of the documentation.
Additionally, because an organization’s schema should be a distributed responsibility, the architecture introduced type references. In this scenario, services can extend types from other services, basically enabling schema composition.
For more detailed information on the other core concepts and components of federation, please refer to this section of the Apollo docs.
Design and implementation
Implementing a federated graph architecture involves:
- The different graph servers or schemas
- A gateway that unifies these different graph schemas and composes them into one graph API endpoint
Wondering how this works? Let’s proceed.
The gateway has a query planner. The query plan panel is visible on the GraphQL Playground, where we can see see how the gateway is executing all requests. When clients send their requests via the gateway, it splits these queries into smaller chunks or sub-queries that walk through each of these graph schemas. The gateway layer already understands the interrelationships between these services, as we have previously discussed.
Basically, the query planner helps in planning and executing the query over all the graph schemas, then composes them into one API endpoint for the client to query. Additionally, there is a graph manager, which helps with tracking a schema’s full history and usage.
For highly distributed teams that require some sort of special tooling and support, federation provides a managed service, which helps solve some specific problems at scale — metrics collection, traces, schema change validation, etc.
Note: An implementing service must conform to the federation specification, which exposes all the magic that happens on the gateway layer.
Moving further, a detailed setup for converting an existing schema into a federated service can be found in the documentation. Also, there is a reference API documentation for all things federation, which can also be found here.
Now let’s quickly do a rundown of federation in order to see how all the pieces fit together, using the demo application on their GitHub.
As usual, we can start a new Node project with the
npm init command, then go ahead to install all the required dependencies for the project setup. We can begin by installing @apollo/gateway, apollo-server, and, of course, the latest version of GraphQL. To do so, we can run:
npm install @apollo/gateway apollo-server graphql --save
Note: More details about the setup can be found in the
package.jsonfile. Also note that to run the different services, we can install concurrently by running
npm install concurrently --save.
Creating the gateway
In setting up the gateway, we can see (as defined in the
gateway.js file) that we are importing the Apollo server and gateway. Then, we are instantiating a new Apollo gateway in order to compose all the services into one. After that, we are destructing the schema and executor using the
gateway.load() function, which we will use later on to start up our server.
Type references and extensions
If we take a look at the services folder, we can see a couple of services, which will later be composed to form a single graph API endpoint.
accounts service folder, we need to set up the API. To do so, we can go ahead and install all the needed dependencies to run the service:
npm install @apollo/federation apollo-server graphql --save
Then, in the
index.js file, we can have a look at the entire setup. We will notice here that we are extending the user type in this service. The
user service is now an entity based on the
@key directive, which can be extended and resolved in other services.
If we navigate to the reviews service, for example, we will see how we are using the user account entity we had specified earlier in the account service. We can also notice that from the array of dummy data specified in the user service, there are
ids, which are unique and can be used to reference these fields in other services as the need arises.
Also, we are destructuring the Apollo server and the type definitions. To build a federated schema, we can see that we are importing the
buildFederatedSchema, useful for wrapping the resolvers, and then the type defs we had imported earlier from the Apollo server.
Federated architecture is indeed a novelty in the sense that it simplifies, in a highly optimized manner, the problem of bringing together multiple schemas from different microservices and merging them into one unified graph API.
While this is also somewhat possible in monolith-based architectures, there are numerous drawbacks; thus, it is quickly becoming less attractive as a go-to for building modern, highly distributed systems.
In this article, we have learned how federated architecture eases the lives of developers and majorly distributed teams working independently yet synchronously. With these improvements and advancements, it is indeed safe to say the ecosystem will continue to see even more improved features.
Additionally, ideas — or, rather, implementations — thought to be previously difficult to achieve are becoming simpler and more accessible, albeit layered above some levels of abstractions with syntactic sugar.
Finally, we can then make our decision about federation taking into consideration the idea that microservices are not the only solution — as a result, architectural decisions or choices always come with some level of tradeoff.
Please share your views by engaging with me on my Twitter. Also, go ahead and ask me any questions in the comment section below — I would be happy to answer them. Thanks for reading 🙂
Monitor failed and slow GraphQL requests in productionWhile GraphQL has some features for debugging requests and responses, making sure GraphQL reliably serves resources to your production app is where things get tougher. If you’re interested in ensuring network requests to the backend or third party services are successful, try LogRocket.https://logrocket.com/signup/
LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.