Mercurius and Apollo InteroperabilityMercuriusandApolloInteroperabilityMercuriusandApolloInteroperability
24 Mar 2023
Share
What is GraphQL
GraphQL is a query language for APIs; it provides a clear and complete description for your data and allows the client to retrieve the data it needs.
The API can be defined using a schema that allows the client to know which operations can be executed by the server — the performance of the operations is determined by the resolvers. Using these concepts makes it easy to define and extend your API.
Once the schema and the resolvers are defined, you can select from any available GraphQL implementations and pick the one that best meets your preferences/requirements.
Initially developed by Facebook, which provided just a reference implementation, GraphQL was mostly implemented in the JS ecosystem in Apollo and Mercurius . Speaking of references, I recommend you view the mercurius-apollo-playground repo created by Andrea Carraro . It provides you with invaluable context because it contains the code and the benchmark commands for the project that inspired this blog post.
Creating a GraphQL server that manages a data structure like the one in the diagram below can be achieved quickly by using a single GraphQL server.
However, real-world applications are more complex than that seen above. Indeed, it’s pretty common for most applications to require hundreds of entities and access to multiple data sources, as the image underneath demonstrates:
We can use a single GraphQL service to build an application as complex as the one above, but the issue with this approach is that a huge codebase is difficult to maintain and split into different teams — some parts can load heavily, others are only called a few times, etc.
A microservices-like approach enables you to split the responsibilities into different services that can be maintained by separate teams and deployed in different ways.
According to the Integrity Principles , having a single entry point with a unified schema is essential. For example, it’s essential in order to be able to access all data from a single query and avoid duplication of graph implementation.
This feature can be created using Apollo federation v1 , one of the most well-known methods of implementing GraphQL in microservices.
Define a sample architecture with <code>supergraph</code> and <code>subgraph</code>
Let’s define a schema for the well-known example of a blog page's basic architecture.
Our system will have two entities:
User: This implements the user data and can retrieve the blog post written by a user
Post: This implements the post data and can retrieve the information about the author (that’s a specific type of user)
Below, you can see an example of a schema that defines the above entities:
type Query { me: User
users:[User] posts:[Post]}type User { id: ID!
name: String!
posts:[Post]}type Post { id: ID!
title: String
content: String
author: User
}
json
Based on the above example, we would have two services — the first one manages the users and the second one looks after the posts .
We can split the schema into 2 subgraphs:
// Subgraph 1 - Usersextend type Query { me: User
users:[User]}type User @key(fields:"id") { id: ID!
name: String!
}// Subgraph 2 - Poststype Query { posts:[Post]}type Post @key(fields:"id") { id: ID!
title: String
content: String
author: User
}type User @key(fields:"id") @extends { id: ID! @external
name: String @external
posts:[Post]}
json
We define the application architecture with three services:
Gateway service with a GraphQL endpoint that acts as supergraph service — this exposes a merged schema that’s created using the subgraphs schemas
User service with a GraphQL endpoint that serves the supergraph 1 schema
Posts service with a GraphQL endpoint that serves the supergraph 2 schema
Implement the application with a gateway
To implement the solution, we can use either Mercurius or Apollo. However, the key thing to note is that we also have the option of picking both libraries and deciding service-by-service which one to use.
First, we define the schema and the resolvers that can be created with an agnostic approach.
// Post subgraph with resolversconst postSubgraph ={schema:` extend type Query {
posts: [Post]
}
type Post @key(fields: "id") {
id: ID!
title: String
content: String
author: User
}
type User @key(fields: "id") @extends {
id: ID! @external
name: String @external
posts: [Post]
}
`,resolvers:{Query:{posts:(parent, args, context)=>{// Get the posts from a servicereturngetPosts()}},Post:{author:post=>{return{__typename:'User',id: post.authorId}}},User:{posts:user=>{returngetPostsByUserId(user.id)}}}}export{ postSubgraph }
js
The User subgraph service with <code>Apollo</code>
The User subgraph service is created using the Apollo framework. It is built by passing a schema created by the buildSubgraphSchema method to the ApolloServer instance. The ApolloServer is then passed to the startStandaloneServer function that exposes the /graphql endpoint.
The Post subgraph service with <code>Mercurius</code>
The Post subgraph service is created using the Mercurius framework. A Fastify application is created with the @mercuriusjs/federation plugin. The plugin exposes a GraphQL federated service on the /graphql endpoint based on the schema and the resolvers defined in the postSubgraph file.
importFastifyfrom"fastify";importmercuriusfrom"@mercuriusjs/federation";import{ postSubgraph }from'./postSubgraph'asyncfunctionrunMercurius(){const service =Fastify({}); service.register(mercurius,{schema: postSubgraph.schema,resolvers: postSubgraph.resolvers,jit:1});await service.listen({port:4002});};runMercurius()
js
The code above shows that the two technologies can be switched quickly if the resolvers are kept agnostic. However, in a real-world example, keeping the agnostic rule valid is more complex and requires serious attention. Authentication, authorization, caching, validation, etc… are created using modules that can be strictly related to either of the platforms.
A good design and a Hexagonal Architecture pattern will help you to achieve the goal of being able to switch the two technologies by keeping the resolvers agnostic.
Create the gateway
We can use the @apollo/gateway or @mercuriusjs/gateway modules to create the gateway. The integration of both technologies is similar:
Firstly, they call the subgraph services to get the exposed schema
Secondly, they merge them in the supergraph schema, automatically creating the resolvers
The services created above expose the GraphQL endpoint and run on ports 4001 and 4002 . The introspection action of the gateway calls the endpoint, merges the subgraph schemas, and provides the client with a unified schema.
Implement the supergraph gateway with <code>Apollo gateway</code>
The Apollo library provides a gateway that can be used as a supergraph server. Setting it up is enough to pass the subgraph endpoints as parameters. You can learn more about this by reading the official documentation for implementing a gateway with Apollo server .
The snippet below creates an Apollo gateway for the services defined above.
import{ApolloServer}from'@apollo/server'import{ startStandaloneServer }from'@apollo/server/standalone'import{ApolloGateway,IntrospectAndCompose}from'@apollo/gateway'functionrunApolloGateway(){const gateway =newApolloGateway({supergraphSdl:newIntrospectAndCompose({subgraphs:[{name:'user',url:'http://localhost:4001/graphql'},{name:'post',url:'http://localhost:4002/graphql'}]})})// Pass the ApolloGateway to the ApolloServer constructorconst server =newApolloServer({ gateway })const{ url }=awaitstartStandaloneServer(server,{port:4000})}runApolloGateway()
js
Implement the supergraph gateway with <code>Mercurius gateway</code>
The Mercurius library provides a module ( @mercuriusjs/gateway ) that creates a supergraph server. As with the Apollo server, setting it up is enough to pass the subgraph endpoints as parameters. You can discover more about this by checking out the official documentation published by Mercurius .
The snippet below creates a Mercurius gateway for the services defined above.
Create a supergraph gateway with <code>Apollo Router</code>
In addition to the library I showed you previously in this blog post, Apollo provides a high-performance router that is built in Rust .
A supergraph definition file is required to run the Apollo router. This file can be generated using rover , a command-line interface for managing graphs with GraphOS: Install rover and follow the instructions to generate the key .
Login to rover.
rover config auth
Create a YAML config file that declares how to retrieve the subgraph schema.
The examples above can be merged differently. For instance, they could employ a full Mercurius or Apollo implementation or use a hybrid implementation with the Apollo router and the subgraph managed by Mercurius. There are a number of options.
A simple case like this is easy to convert and to create some benchmarks for the performance that are based on the configuration. Configurations tested:
Mercurius gateway, Mercurius subgraph services
Apollo gateway, Mercurius subgraph services
Apollo gateway configured with subgraph, Mercurius subgraph services
Apollo router (rust), Mercurius subgraph services
Apollo gateway, Apollo subgraph services
Apollo router (rust), Apollo subgraph services
Test details:
The test is run on a MacBookPro 6-Core Intel Core i7, 32GB
All the data is mocked in memory and a no database call is carried out
The query tested is the sample query defined above
The test is done using autocannon with 10 concurrent connections for 10 seconds
It’s important to be aware that this is not a production test and should not be taken as a reference for the performance provided by the library. An accurate production application adds many layers that affect the system's performance. Request responses in 10 seconds (the higher the better)
As I’ve explained in this article, the creation of a hybrid system with Mercurius and Apollo can be achieved quite easily. Furthermore, it is possible to choose other JS libraries or even different languages. Check the supported feature list in the federation-compatible subgraph implementations document to discover what options are available to you.
The configuration you should choose depends on many factors. If the system is well-designed and tested, it is relatively easy to move from one technology to another and run some benchmarks to understand better which solution fits the product requirements.
Now all that's left is for you to create your own hybrid system with Mercurius and Apollo, when the time is right for you to do so.