An Intro to GraphQL Federation with Ballerina GraphQL
This article was written using Ballerina 2201.5.0 (Swan Lake Update 5)
Hello! It’s been a while since I last wrote a blog post but I am back at it. Today I wanted to share with you some insights into implementing GraphQL subgraphs in Ballerina. But before we dive into the coding part, let’s take a moment to understand what GraphQL federation is and what problems it solves.
What is GraphQL Federation ?
GraphQL Federation is a way to build a unified GraphQL API by combining multiple GraphQL services that are each responsible for a specific domain or set of functionalities. It provides a standard way to compose these services into a cohesive schema, making it easy to consume and query them in a single place.
The Three Main Elements of Federation
Graphql Federation consists of the following three main elements.
- The Gateway | Router — This is the main entry point for clients to access the unified GraphQL API. It’s responsible for routing requests to the appropriate service, aggregating the results, and returning a unified response.
- Subgraphs — These are the individual GraphQL services that are responsible for a specific domain or set of functionalities. Each service defines its own schema and exposes its own set of types and fields. The services can be developed and deployed independently from each other, and they can be written in different programming languages and run on different servers.
- Entities — An entity refers to an object type that can be resolved across multiple subgraphs. Each subgraph in the federation can contribute different fields to the entity, and is responsible for resolving only the fields that it contributes. This allows for a more scalable and efficient way of building and managing distributed GraphQL systems.
Some Advantages of GraphQL Federation
- Data silos — GraphQL Federation solves the issue of data being stored in separate services, making it difficult to access and retrieve in a single request.
- Schema complexity and scalability — It simplifies the overall schema structure by breaking it down into smaller, more manageable schemas for each service, making it easier to maintain and evolve over time.
- Service ownership and separation of concerns — It allows each service to maintain ownership over their respective data and schemas while still providing a unified API for consumers.
Since you are now familiar with the basic concepts, let’s dive into the details of the federated system we are going to build.
An example scenario
To illustrate GraphQL Federation, let’s consider a simple product review scenario where a product can have multiple reviews. Users can query the product and view its reviews along with other details, such as the product’s name, price, and description. Users can also add reviews for a product by sending a mutation request to the server.
To implement a federated system, Let’s write 2 separate services (subgraphs): a product service and a review service. Separating the reviews and product into their own services offers several benefits, including:
- Scalability — If the system experiences high traffic or growth, it’s easier to scale each service independently, depending on their specific needs. For example, the review service may require more processing power or storage space to handle an influx of reviews.
- Maintainability — Separating the services into their own domains makes it easier to manage and maintain each service separately. Each service can be developed, tested, and deployed independently of each other, which can reduce the risk of errors or downtime across the entire system. Further, each service can have its own team responsible for its development and maintenance. This can help promote a sense of ownership and accountability for each service.
- Loose coupling — Separating the services promotes loose coupling between different parts of the system. This can make it easier to make changes or updates to each service without affecting the rest of the system. It will also lead to a more flexible and modular architecture.
Sharing Entity Between Subgraphs
Following are the responsibilities of each subgraph/service:
- The product service — returns the product details such as title, description, price, and category
- The review service — allows adding a review to a product and returns the reviews for a particular product
Overall the product subgraph contributes title, description, price and category field to a product and the review service contributes review field to a product. Since both subgraphs contribute fields to the Product type, the Product type is considered as an entity.
Writing the Product Subgraph with Ballerina
Prerequisite: Writing GraphQL service with ballerina.
Not familiar! refer GraphQL service section in Ballerina By Examples
Create a new Ballerina package called product using the following command
bal new -t service product
Defining the type to represent the product service schema types
In the above code, I have simply used a ballerina record type to represent the Product type. and used @subgraph:Entity annotation to make this type as an entity type. The key field on the annotation specifies the primary key (.ie id) to uniquely identify the Product entity.
The Product service implementation
Here @subgraph:Subgraph annotation is used to expose the service as subgraph.
The datasource for products
For the sake of simplicity, I have used an in memory products table as a datasource here.
Since we are now done with the first subgraph, let’s move to the implementation of the next subgraph.
Writing Review Subgraph with Ballerina
Create a new Ballerina package called reviews
bal new -t service reviews
Defining the type to represent the review service schema types
I’ve created the necessary types to represent the schema types, and I’ve made the Product an entity with id as the key field. Notice that there’s a new field called resolveReference in the @subgraph:Entity annotation. This field points to a function that resolves an entity of a specific type using its primary key. When the gateway/router requires a particular entity to be resolved from a subgraph, it invokes the corresponding entity’s reference resolver and returns the value for that entity. In our code, the resolveReference field of the Product entity points to a function called resolveProduct, which defines the logic to resolve the product value for a given id (primary key). The subgraph:Representation record allows us to access the value of the primary key in the resolveProduct function.
The Review service implementation
The datasource for reviews
Now that we have implemented both subgraphs, we can combine them into a single, cohesive service using a Gateway. This combined service is called a supergraph, and it provides a unified API that allows clients to interact with both subgraphs as if they were a single entity.
Exposing Supergraph via Gateway
Following are the steps to expose the service via a gateway. Here I’m using the Apollo gateway library.
- create a new folder for a npm project
- Add the following package.json file
- Add the following index.js file to link your implemented subgraphs
- Finally, run the following commands from your works space. This will execute both subgraph and the gateway
bal run product
bal run review
cd gateway && npm install && npm start
The gateway exposes the supergraph at default url http://localhost:4000. Now you can query the supergraph with any client. Let’s try some queries.
- Query a product to view reviews and title
Query:
curl 'http://localhost:4000' \
-H 'Content-Type: application/json' \
--data-binary '{"query":"query {\n product(id: \"1\") {\n title\n reviews {\n comment\n }\n }\n}"}'
Response:
{
"data": {
"product": {
"title": "Knife",
"reviews": [
{
"comment": "Good product"
},
{
"comment": "Average product"
},
{
"comment": "Not bad"
}
]
}
}
}
2. Add review to a product
Mutation:
curl 'http://localhost:4000' \
-H 'Content-Type: application/json' \
--data-binary '{"query":"mutation Mutation($reviewInput: ReviewInput!) {\n addReview(reviewInput: $reviewInput) {\n comment\n }\n}","variables":{"reviewInput":{"productId":"1","rating":4.6,"comment":"A new review","author":"sabthar"}}}'
Response:
{
"data": {
"addReview": {
"comment": "A new review"
}
}
}
3. Query the same product using the 1st command to see the newly added comment
Response:
{
"data": {
"product": {
"title": "Knife",
"reviews": [
{
"comment": "Good product"
},
{
"comment": "Average product"
},
{
"comment": "A new review"
}
]
}
}
}
That’s all our federated supergraph working as expected 🎉.
I hope this post has given you a clear understanding of federation and how to implement subgraphs using the Ballerina graphql module. Consider trying out federation with Ballerina to see the benefits it can bring to your organization. You might be pleasantly surprised.
Link to the full source code: https://github.com/MohamedSabthar/ballerina-graphql-federation
References: