Use Schema Stitching or Federation In GraphQl
Where Can It Be Used, and What Are Its Primary Use Cases?
What Problems Are Addressed by Schema Stitching and Federation?
How Can It Be Implemented Using HotChocolate?
Which approach should be used in a microservice architecture?
Can GraphQL Serve as a Composition Layer in Microservice Architectures?
Schema Stitching and Federation are two powerful techniques in GraphQL used to combine multiple GraphQL APIs into a single, unified schema. Here's an in-depth explanation of both concepts, their use cases, the problems they solve, and how to implement them, with a focus on practical examples and scenarios.
Where We Can Use It
Schema Stitching Use Cases
Federation Use Cases
Issues Solved by Schema Stitching and Federation
1. Unified Access
Problem: Multiple GraphQL APIs can confuse consumers and complicate development. Solution: Stitching or federation unifies these APIs under a single schema, providing a seamless developer experience.
2. Decoupled Development
Problem: Centralized schema management creates bottlenecks when multiple teams need to collaborate. Solution: Federation enables teams to manage their subgraphs independently, fostering agile development.
3. Consistent Data Access
Problem: Data might be scattered across different services or domains. Solution: Unified schemas provide consistent access patterns, reducing client-side complexity.
4. Scalability
Problem: Monolithic GraphQL APIs become hard to scale. Solution: Federation distributes responsibility and ownership, allowing individual subgraphs to scale independently.
How to Implement Schema Stitching
1. Schema Stitching Overview
Schema stitching combines multiple schemas into one unified API by merging their types and resolvers. It can be implemented at runtime or during build time.
Steps to Implement Schema Stitching
public class UserSchema
{
public User GetUser(int id) => new User { Id = id, Name = "John Doe" };
}
public class ProductSchema
{
public Product GetProduct(int id) => new Product { Id = id, Name = "Laptop" };
}
2. Merge Schemas: Use tools like HotChocolate
var userSchema = SchemaBuilder.New().AddQueryType<UserSchema>().Create();
var productSchema = SchemaBuilder.New().AddQueryType<ProductSchema>().Create();
var stitchedSchema = SchemaBuilder.New()
.AddSchema(userSchema)
.AddSchema(productSchema)
.Create();
3. Stitch Resolvers: Combine resolvers to resolve fields across schemas.
builder.AddStitching()
.AddSchemaFromFile("userSchema.graphql")
.AddSchemaFromFile("productSchema.graphql");
How to Implement Federation
1. Federation Overview
Federation distributes the schema into multiple subgraphs, allowing each service to own its portion of the schema. Apollo Federation is the most popular implementation.
Steps to Implement Federation
User Service:
type User @key(fields: "id") {
id: ID!
name: String!
}
Product Service:
type Product @key(fields: "id") {
id: ID!
name: String!
owner: User @provides(fields: "name")
}
Enable Federation: Use HotChocolate's federation
builder.AddType<User>().AddFederation();
When to Choose Schema Stitching or Federation
By leveraging these approaches, developers can:
Your Choice: If you need centralized control, use schema stitching. If you want decentralized ownership and scalability, federation is the way to go!
chema Stitching is not the best choice for microservices in most cases. While it can work for combining schemas from multiple services, it has certain limitations that make it less ideal for microservice-based architectures compared to GraphQL Federation. Here's a detailed explanation:
Why Schema Stitching is Not Ideal for Microservices
1. Centralized Schema Management
2. Performance Overhead
3. Tightly Coupled Development
4. Lack of Ownership Clarity
There are limited scenarios where Schema Stitching can work well in a microservice setup:
GraphQL Federation: The Better Choice for Microservices
GraphQL Federation is designed specifically to address the needs of microservices and is generally a better choice.
Key Benefits of Federation for Microservices
Use Schema Stitching if:
Use GraphQL Federation if:
shema Stitching can work for small-scale systems or transitional scenarios, but it is not ideal for microservices due to its centralized nature, performance overhead, and tight coupling. For modern microservice architectures, GraphQL Federation is a far better approach, offering the flexibility, scalability, and independence needed for large, distributed systems.
Federation Architecture
Subgraphs (Backend Services):
Gateway (Backend):
Frontend:
Key Federation Components
1. Federated Directives
These directives are used in subgraphs to define relationships between services:
Here’s a breakdown of how federation works, focusing on backend implementation and how the subgraphs from multiple microservices are combined:
Federation Architecture
Subgraphs (Backend Services):
Gateway (Backend):
Frontend:
Key Federation Components
1. Federated Directives
These directives are used in subgraphs to define relationships between services:
How to Combine Subgraphs from More Than One Microservice
To combine subgraphs, you use a federated gateway like Apollo Gateway or implement federation using tools like HotChocolate.
Steps to Combine Subgraphs
1. Define Subgraphs
Each service defines its schema independently and uses federation directives.
// User Service Schema:
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
// Product Service Schema:
type Product @key(fields: "id") {
id: ID!
name: String!
owner: User @provides(fields: "name")
}
Each microservice has its own GraphQL server. For example, using HotChocolate
User Service Implementation
builder.AddGraphQLServer()
.AddQueryType<UserQuery>()
.AddTypeExtension<UserTypeExtension>()
.AddFederation();
Product Service Implementation
builder.AddGraphQLServer()
.AddQueryType<ProductQuery>()
.AddTypeExtension<ProductTypeExtension>()
.AddFederation();
Set Up the Federated Gateway
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddGraphQLServer()
.AddRemoteSchema("users", c => c.WithHttpClient(client =>
client.BaseAddress = new Uri("https://localhost:4001/graphql")))
.AddRemoteSchema("products", c => c.WithHttpClient(client =>
client.BaseAddress = new Uri("https://localhost:4002/graphql")));
var app = builder.Build();
app.MapGraphQL();
app.Run();
Query the Unified Schema
领英推荐
query {
product(id: "1") {
name
owner {
name
}
}
}
To use federated directives in HotChocolate, you must enable federation support in each subgraph.
builder.AddGraphQLServer()
.AddQueryType<Query>()
.AddTypeExtension<FederatedTypeExtensions>()
.AddFederation();
Key Federation Components
@key: Defining the Primary Key
The @key directive is used to indicate the unique identifier for an entity that can be referenced across subgraphs.
Example:
[GraphQLKey("id")]
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
In the GraphQL schema:
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
@external: Referencing Fields from Other Subgraphs
The @external directive marks fields that are not defined in the current subgraph but exist in another subgraph.
Example:
Product Service Schema:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
[GraphQLExternal]
public int OwnerId { get; set; }
}
In the GraphQL schema:
type Product @key(fields: "id") {
id: ID!
name: String!
ownerId: Int! @external
}
@requires: Fetching Additional Fields
The @requires directive fetches additional fields from the parent type to resolve a query.
Example:
Resolver Implementation:
public class ProductResolvers
{
[GraphQLRequires("ownerId")]
public User GetOwner([Service] UserService userService, int ownerId)
{
return userService.GetUserById(ownerId);
}
}
In the GraphQL schema:
type Product {
id: ID!
name: String!
owner: User @requires(fields: "ownerId")
}
@provides: Sharing Fields with Other Subgraphs
The @provides directive shares specific fields with another service, enabling dependent subgraphs to resolve their queries.
Example:
User Service Schema:
[GraphQLKey("id")]
public class User
{
public int Id { get; set; }
public string Name { get; set; }
[GraphQLProvides("name")]
public string GetName() => Name;
}
In the GraphQL schema:
type User @key(fields: "id") {
id: ID!
name: String! @provides(fields: "name")
}
Complete Example
// User Service
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
public class User
{
[GraphQLKey]
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class Query
{
public User GetUserById(int id) => new User
{
Id = id,
Name = "John Doe",
Email = "[email protected]"
};
}
// Product Service
type Product @key(fields: "id") {
id: ID!
name: String!
owner: User @requires(fields: "ownerId")
}
public class Product
{
[GraphQLKey]
public int Id { get; set; }
public string Name { get; set; }
[GraphQLExternal]
public int OwnerId { get; set; }
}
public class ProductResolvers
{
public User GetOwner([Service] UserService userService, int ownerId)
{
return userService.GetUserById(ownerId);
}
}
// Gateway Setup Using HotChocolate
builder.Services.AddGraphQLServer()
.AddRemoteSchema("userService", c => c.WithHttpClient(client =>
client.BaseAddress = new Uri("https://localhost:4001/graphql")))
.AddRemoteSchema("productService", c => c.WithHttpClient(client =>
client.BaseAddress = new Uri("https://localhost:4002/graphql")));
// Querying the Unified Schema
query {
product(id: 1) {
name
owner {
name
email
}
}
}
The gateway:
Using HotChocolate with Federation:
This approach enables scalable, decentralized, and maintainable GraphQL APIs in microservice architectures.
Using complex Senarion loadin profile from doctor MS
1. Doctor Microservice Subgraph to compose the required datat to Appointment or Patient Microservices
The Doctor microservice exposes a User type with a Profile field. Use the @key directive to make it a federated entity.
[GraphQLKey("id")]
public class User
{
public long Id { get; set; }
public string Profile { get; set; }
}
public class Query
{
public List<User> GetUsersDetails(List<long> userIdRefs)
{
// Fetch user details from the Doctor database or service
return userIdRefs.Select(id => new User
{
Id = id,
Profile = $"https://profile.images/{id}.png" // Example profile URL
}).ToList();
}
}
// graph Schema Output:
type User @key(fields: "id") {
id: ID!
profile: String!
}
type Query {
usersDetails(userIdRefs: [ID!]!): [User!]!
}
Consumer Microservice (e.g., Appointment or Patient)
The consumer microservice references the User entity from the Doctor subgraph and resolves the profile field using federation.
[GraphQLKey("id")]
public class User
{
public long Id { get; set; }
[GraphQLExternal]
public string Profile { get; set; }
}
public class Appointment
{
public long Id { get; set; }
public long DoctorId { get; set; }
[GraphQLProvides("profile")]
public User GetDoctorProfile([Service] IDoctorService doctorService, long doctorId)
{
return doctorService.GetUserById(doctorId);
}
}
// Schema Output:
type User @key(fields: "id") {
id: ID!
profile: String! @external
}
type Appointment {
id: ID!
doctorId: ID!
doctor: User!
}
// Gateway Configuration
//Set up the federated gateway to combine the Doctor and Appointment subgraphs.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGraphQLServer()
.AddRemoteSchema("doctor", c => c.WithHttpClient(client =>
client.BaseAddress = new Uri("https://localhost:4001/graphql")))
.AddRemoteSchema("appointment", c => c.WithHttpClient(client =>
client.BaseAddress = new Uri("https://localhost:4002/graphql")));
var app = builder.Build();
app.MapGraphQL();
app.Run();
// Gateway Query
query {
appointment(id: 1) {
doctor {
id
profile
}
}
}
Gateway Flow
This approach simplifies the architecture, improves performance, and aligns with microservices best practices.
How GraphQL Acts as a Composition Layer
GraphQL’s flexibility and query language make it an excellent tool for combining data from various microservices without tightly coupling them. It allows clients to query exactly the data they need, even if that data is distributed across multiple backend systems.
Unified API for Consumers:
Decoupled Frontend and Backend:
Dynamic Query Resolution:
Optimized Data Fetching:
Easier Data Aggregation:
Interoperability:
Use Case: GraphQL for Read-Only Composition
A typical example would involve a client application needing a dashboard that displays aggregated data about a doctor's profile and appointments. Each of these data sources resides in separate microservices.
Best Practices for Using GraphQL as a Composition Layer
Federation for Scalability:
Caching and Performance:
Error Handling:
Gradual Adoption:
Limitations of Using GraphQL as a Composition Layer
Write Operations:
Gateway Bottleneck:
Gateway Bottleneck:
How Federation Solves Problems
Decentralized Schema Management:
Cross-Service Relationships:
Scalability:
Unified API for Clients:
Optimized Query Resolution:
Conclusion
Schema Stitching can work for small-scale systems or transitional scenarios, but it is not ideal for microservices due to its centralized nature, performance overhead, and tight coupling. For modern microservice architectures, GraphQL Federation is a far better approach, offering the flexibility, scalability, and independence needed for large, distributed systems.
By implementing GraphQL Federation, you can eliminate the need for manual data loading mechanisms like ProfilePictureBatchDataLoader and instead rely on:
This approach simplifies the architecture, improves performance, and aligns with microservices best practices.
Using GraphQL as a composition layer is an excellent way to aggregate data from multiple microservices, especially for read-heavy applications. By leveraging federation, resolvers, and a well-structured schema, you can:
GraphQL Federation is particularly well-suited for this purpose, ensuring decentralized ownership while providing centralized access. Let me know if you'd like help implementing this in your specific project!
Using GraphQL as a composition layer is a growing trend in the software industry due to its ability to:
While it may not fit every use case, GraphQL is particularly effective in read-heavy, microservices-based architectures where unified access and flexibility are critical. It is commonly used by major tech companies and continues to gain traction as a tool for modern API development.