Use Schema Stitching or Federation In GraphQl

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

  1. Legacy System Integration: Combine multiple legacy GraphQL APIs into one unified schema.
  2. Cross-Team Collaboration: Allow teams to expose their GraphQL APIs independently but stitch them into a single endpoint.
  3. Simplify Consumer Access: Abstract multiple GraphQL APIs into a single schema for frontend developers.

Federation Use Cases

  1. Microservices Architecture: Distribute ownership of GraphQL schemas across teams, enabling microservice principles.
  2. Domain-Driven Design: Each team manages its domain (e.g., users, products, orders), and the federated schema combines them.
  3. Scalability: Handle large, decentralized organizations or projects with multiple independently managed APIs.

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

  1. Define Individual Schemas: Each service exposes its GraphQL schema.

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

  1. Define Subgraphs: Each service defines its schema with federated directives like @key and @external.

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


Schema Stitching and Federation are essential tools for designing scalable and efficient GraphQL APIs. Use schema stitching for simpler, centralized projects where you need to unify legacy APIs. Opt for federation in large-scale, distributed systems to enable domain-driven design and scalability.

By leveraging these approaches, developers can:

  • Enhance scalability and maintainability.
  • Support agile and independent development.
  • Simplify the client experience with unified GraphQL schemas.

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

  • Issue: Schema Stitching combines multiple schemas into a single unified schema at the gateway level, which introduces a central point of management and potential bottleneck.
  • Microservices Concern: Microservices thrive on decentralized, independent service management. Centralizing schemas negates this principle, making updates or schema changes dependent on the stitching process.

2. Performance Overhead

  • Issue: Schema Stitching often requires stitching resolvers, which means queries may trigger multiple network calls and extra data processing at the gateway.
  • Microservices Concern: For large-scale systems, this adds significant overhead, increasing latency and reducing efficiency.

3. Tightly Coupled Development

  • Issue: When you stitch schemas, changes in one service's schema may require updates in the stitched schema, leading to tight coupling.
  • Microservices Concern: This violates the principle of independent deployments for microservices, making it harder to scale or update services individually.

4. Lack of Ownership Clarity

  • Issue: Schema Stitching does not inherently enforce ownership of specific parts of the schema.
  • Microservices Concern: In microservices, each service should own its data and schema independently to prevent cross-team dependencies.
  • When Schema Stitching Can Be Used in Microservices

There are limited scenarios where Schema Stitching can work well in a microservice setup:

  1. Small Microservices System
  2. Legacy Systems Integration
  3. Single Development Team


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

  1. Decentralized Schema Management
  2. Independent Teams and Ownership
  3. Scalability
  4. Optimized Performance
  5. Easier Collaboration

Recommendation

Use Schema Stitching if:

  • You are working with few services or a monolithic team.
  • The system is relatively small-scale.
  • You are integrating legacy GraphQL APIs temporarily.

Use GraphQL Federation if:

  • You have a large-scale system with multiple teams managing independent services.
  • You prioritize performance, scalability, and independence.
  • Your architecture aligns with microservices principles.


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):

  • Each microservice defines its own GraphQL schema (subgraph) independently.
  • Subgraphs are responsible for their domain data, exposing only the parts needed by the unified schema.
  • Federation uses special directives (like @key, @external, and @provides) to enable inter-service data sharing.

Gateway (Backend):

  • The gateway acts as the central entry point for clients.
  • It combines the schemas from all subgraphs dynamically.
  • It delegates incoming queries to the appropriate subgraphs and combines their responses.

Frontend:

  • From the client's perspective, there’s only one unified schema.
  • The frontend queries the gateway directly, without needing to know about the subgraphs.

Key Federation Components

1. Federated Directives

These directives are used in subgraphs to define relationships between services:

  • @key: Specifies the primary key for an entity.
  • @external: Marks a field that exists in another service.
  • @requires: Fetches additional fields from the parent type to resolve the query.
  • @provides: Shares specific fields between services.
  • GraphQL Federation is implemented on both the backend and the frontend, but its primary logic lies in the backend. It involves managing subgraphs (individual schemas for each service) and combining them into a unified API using a gateway.

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):

  • Each microservice defines its own GraphQL schema (subgraph) independently.
  • Subgraphs are responsible for their domain data, exposing only the parts needed by the unified schema.
  • Federation uses special directives (like @key, @external, and @provides) to enable inter-service data sharing.

Gateway (Backend):

  • The gateway acts as the central entry point for clients.
  • It combines the schemas from all subgraphs dynamically.
  • It delegates incoming queries to the appropriate subgraphs and combines their responses.

Frontend:

  • From the client's perspective, there’s only one unified schema.
  • The frontend queries the gateway directly, without needing to know about the subgraphs.


Key Federation Components

1. Federated Directives

These directives are used in subgraphs to define relationships between services:

  • @key: Specifies the primary key for an entity.
  • @external: Marks a field that exists in another service.
  • @requires: Fetches additional fields from the parent type to resolve the query.
  • @provides: Shares specific fields 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:

  • The User entity is uniquely identified by its id.

[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:

  • The Product entity refers to the owner field (a User) defined in the User Service.

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:

  • The Product entity needs the ownerId from the parent Product to resolve the owner field.

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:

  • The User service provides the name field to the Product service.

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:

  1. Fetches Product data from the Product Service.
  2. Resolves owner (a User) from the User Service.

Using HotChocolate with Federation:

  1. Define subgraphs with @key, @external, @requires, and @provides directives to enable inter-service relationships.
  2. Set up a gateway to combine subgraphs dynamically.
  3. Query seamlessly through the gateway, as if all data is part of one unified schema.

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

  1. Fetch Appointment: The gateway fetches the Appointment data (including DoctorId) from the Appointment subgraph.
  2. Resolve Profile: The gateway fetches the Profile for the Doctor using the @key directive from the Doctor subgraph.

By implementing

  1. Subgraphs: Each service owning and exposing its part of the schema.
  2. Federated Gateway: Combining and resolving data dynamically across subgraphs.

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:

  • GraphQL provides a single endpoint that aggregates data from multiple microservices, abstracting the complexity of the backend architecture from clients.

Decoupled Frontend and Backend:

  • Clients (e.g., web, mobile apps) interact with the GraphQL API without needing direct knowledge of the underlying microservices.

Dynamic Query Resolution:

  • GraphQL resolves data dynamically by calling the appropriate microservices, often through resolvers or a federated gateway.

Optimized Data Fetching:

  • Clients can request exactly the data they need (no overfetching or underfetching), improving performance and reducing bandwidth usage.

Easier Data Aggregation:

  • GraphQL simplifies the process of combining data from multiple sources, such as merging user data from a User service with order data from an Order service.

Interoperability:

  • Works well with both REST and GraphQL microservices, enabling gradual adoption of GraphQL in existing architectures.

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:

  • Use GraphQL Federation to avoid creating a monolithic GraphQL schema.
  • Let each service own its part of the schema and manage its own resolvers.

Caching and Performance:

  • Cache frequently accessed data to reduce latency and avoid redundant calls to microservices.
  • Use DataLoaders for batching and deduplication of data-fetching operations.

Error Handling:

  • Ensure robust error handling in resolvers to manage issues like unavailable microservices or invalid responses.

Gradual Adoption:

  • If existing microservices use REST APIs, introduce GraphQL gradually by wrapping REST APIs in GraphQL resolvers.

Limitations of Using GraphQL as a Composition Layer

Write Operations:

  • GraphQL works best for read-heavy operations. Coordinating writes across multiple services can be challenging.

Gateway Bottleneck:

  • The GraphQL gateway can become a bottleneck if not scaled properly, especially for high-throughput systems.

Gateway Bottleneck:

  • The GraphQL gateway can become a bottleneck if not scaled properly, especially for high-throughput systems.

How Federation Solves Problems

Decentralized Schema Management:

  • Each service owns its schema independently.
  • Teams can work autonomously without coordination bottlenecks.

Cross-Service Relationships:

  • Federation enables services to reference and resolve fields from other subgraphs using directives like @key.

Scalability:

  • Subgraphs can scale independently.
  • You can add new services to the federation without impacting the existing system.

Unified API for Clients:

  • Clients query a single endpoint with a consistent schema, hiding the complexity of distributed services.

Optimized Query Resolution:

  • Federation resolves queries by fetching only the necessary data from subgraphs, reducing overfetching.

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:

  1. Subgraphs: Each service owning and exposing its part of the schema.
  2. Federated Gateway: Combining and resolving data dynamically across subgraphs.

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:

  • Simplify client interactions with a unified API.
  • Decouple frontend and backend development.
  • Enhance scalability and performance in a microservice architecture.

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:

  • Simplify data aggregation from distributed systems.
  • Enhance developer productivity.
  • Provide a flexible and efficient API for clients.

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.


要查看或添加评论,请登录

Salem Naser的更多文章

社区洞察

其他会员也浏览了