Implementing GraphQL Subgraphs with Ballerina Swan Lake for Federated APIs
- Mohamed Sabthar
- Job Title - WSO2
This article is based on Ballerina Swan Lake Update 7.2.
I want to share insights into implementing GraphQL subgraphs in Ballerina. But before we dive into coding, let's take a moment to understand GraphQL federation and the problems it solves.
Ballerina Swan Lake WSO2 is an open source and cloud native programming language optimized for integration
GraphQL Federation is a way to build a unified GraphQL API by combining multiple GraphQL services. This can be useful for organizations that have their data siloed in different services, or that need to provide a unified API to their customers or partners.
Ballerina is a good language for implementing GraphQL Federation for a number of reasons:
- It is a statically typed language, which can help to prevent errors and ensure the reliability of your GraphQL API.
- It is a concurrent language, which means that it can handle multiple requests simultaneously, which is important for scalability.
- It has built-in support for GraphQL Federation, which makes it easy to implement and manage your federated GraphQL API.
What is GraphQL Federation?
GraphQL Federation is a method for building a unified GraphQL API by combining multiple GraphQL services, each responsible for a specific domain or set of functionalities. It provides a standard process to compose services into a cohesive schema, simplifying consumption and querying in a single place.
The Three Main Elements of Federation
GraphQL Federation comprises three main elements:
- The Gateway | Router: This serves as the primary entry point for clients to access the unified GraphQL API. It routes requests to the appropriate service, aggregates results, and returns a unified response.
- Subgraphs: These are individual GraphQL services responsible for specific domains or functionalities. Each service defines its schema, exposes its types and fields, and can be developed and deployed independently in different programming languages, and on different servers.
- Entities: An entity refers to an object type that can be resolved across multiple subgraphs. Each subgraph contributes different fields to the entity and is responsible for resolving only its contributed fields. This approach offers scalability and efficiency in building and managing distributed GraphQL systems.
Advantages of GraphQL Federation
GraphQL Federation addresses several challenges:
- Data Silos: It solves the problem of data stored in separate services, enabling easy access and retrieval in a single request.
- Schema Complexity and Scalability: It simplifies the schema structure by breaking it into smaller, 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 its data and schema while providing a unified API for consumers.
Now that you understand the basic concepts, let's dive into the details of the federated system we're going to build.
An Example Scenario
To illustrate GraphQL Federation, 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 via a mutation request.
To implement a federated system, we'll create two separate services (subgraphs): a product service and a review service. Separating reviews and products into their own services offers several benefits, including:
- Scalability: Each service can be scaled independently to meet specific needs, such as handling an influx of reviews.
- Maintainability: Separation facilitates easier management, development, testing, and deployment of each service, reducing the risk of system-wide errors or downtime.
- Loose Coupling: Separation promotes loose coupling between different parts of the system, enabling changes or updates to each service without affecting the rest of the system and fostering a flexible, modular architecture.
Sharing Entities Between Subgraphs
Each subgraph/service has its responsibilities:
- The product service: Provides product details such as title, description, price, and category.
- The review service: Allows adding reviews to a product and retrieves reviews for a particular product.
Both subgraphs contribute fields to the product type, making the product type an entity.
Writing the Product Subgraph with Ballerina Prerequisite
Refer to the GraphQL service section in Ballerina By Examples.
Create a new Ballerina package named "product" using the following command:
Defining the Type to Characterize the Schema Types of the Product Service
Code 1: Ballerina Record Type
In the above code, I've employed a Ballerina record type to represent the product type and utilized the @subgraph: entity annotation to designate this type as an entity type. The key field on the annotation specifies the primary key (i.e., id) for uniquely identifying the product entity.
Next, let's delve into the implementation of the product service:
Code 2: Implementation of the Product Service
Here, the @subgraph: annotation is utilized to expose the service as a subgraph. For the sake of simplicity, an in-memory products table serves as the data source.
Code 3: The Product Data Source
With the first subgraph completed, let's now implement the next subgraph.
To do this, create a new Ballerina package named "reviews" using the following command:
Code 4: The Review Subgraph Implementation
In the reviews subgraph, I've defined the necessary types to represent the schema types and designated product as an entity with id as the key field. It's worth noting the addition of 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 contains the logic to resolve the product value for a given id (primary key). The subgraph: Representation record allows us to access the primary key's value in the "resolveProduct" function.
Now, let's move on to the implementation of the review service.
Code 5: The Review Data Source
With both subgraphs implemented, we can combine them into a single, cohesive service using a Gateway. This combined service is referred to as a supergraph, providing a unified API that allows clients to interact with both subgraphs as if they were a single entity.
To expose the supergraph via a Gateway, follow these steps:
- Create a new folder for an npm project.
- Add the following package.json file.
Code 6: Exposing the Supergraph via a Gateway
- Add the following index.js file to link your implemented subgraphs.
Code 7: Following index.js File to Link Implemented Subgraphs
Finally, execute the following missing commands from your workspace. This will run both the subgraph and the gateway.
The gateway exposes the supergraph at the 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
Code 8: Query
Code 9: Response
- Add a review to a product
Code 10: Mutation
- Query the same product using the 1st command to see the newly added comment
Everything in our federated supergraph is functioning as intended! I trust this article has provided you with a comprehensive grasp of federation and the process of implementing subgraphs using the Ballerina GraphQL package. I encourage you to explore federation with Ballerina to discover the advantages it can offer your organization.
Link to the full source code: https://github.com/MohamedSabthar/ballerina-GraphQL-federation