GraphQL APIs with Spring Boot · درس

بناء Subgraphs متّحدة

طوّر خدمات Spring Boot فردية بصفتها subgraphs متّحدة، مع تحديد مخططاتها وعلاقات كياناتها.

الدرس 2 من 411 خطوة

بناء Subgraphs متّحدة درس مجاني في GraphQL APIs with Spring Boot على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في GraphQL APIs with Spring Boot، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة GraphQL APIs with Spring Boot 4 دروس في المجموع.

بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.

Intro to Federated Subgraphs

In GraphQL Federation, a supergraph is composed of multiple independent GraphQL services, called subgraphs.

Each subgraph is a self-contained GraphQL API that owns a specific part of your domain model. Think of it as a microservice for your data.

The Apollo Gateway then combines these subgraphs into one unified API, making it easy for clients to query data across different services.

Understanding Federated Entities

Entities are the core concept for connecting subgraphs. An entity represents a type that can be referenced and extended across different services.

For example, a User entity might be defined in an "Auth" subgraph but extended by a "Product" subgraph to add user-specific reviews.

Entities are marked in the schema using the @key directive, which specifies how to uniquely identify an instance of that type.

Setting Up Your Subgraph

To build a federated subgraph with Spring Boot, you'll start with a standard Spring Boot GraphQL project.

The key dependency is spring-boot-starter-graphql. Make sure you also include spring-boot-starter-web for HTTP endpoints.

You'll also need to configure your build.gradle or pom.xml to include GraphQL schema files (.graphqls).

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.5'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.coddykit'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-graphql'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework:spring-graphql-test'
}

Schema with @key Directive

The @key directive is crucial. It tells the Apollo Gateway which fields uniquely identify an entity within your subgraph.

You can define multiple keys, or composite keys (e.g., @key(fields: "id type")). The Gateway uses these fields to fetch partial data from other subgraphs.

Let's define a simple Product entity and mark its id as the primary key.

# src/main/resources/graphql/schema.graphqls
type Query {
  products: [Product]
  productById(id: ID!): Product
}

type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Float!
}

Building the Product Entity

Now, let's implement the Product entity in our Spring Boot application. This involves creating a data class and a resolver.

The resolver will handle queries for products, just like any other GraphQL endpoint. For simplicity, we'll use in-memory data.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
import java.util.List;
import java.util.ArrayList;

// Assume schema.graphqls is defined as in Scene 4

@SpringBootApplication
public class ProductSubgraphApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProductSubgraphApplication.class, args);
    }

    @Controller
    public static class ProductResolver {
        private static final List<Product> products = new ArrayList<>(List.of(
            new Product("1", "Laptop", 1200.00),
            new Product("2", "Mouse", 25.00)
        ));

        @QueryMapping
        public List<Product> products() {
            return products;
        }

        @QueryMapping
        public Product productById(@Argument String id) {
            return products.stream()
                           .filter(p -> p.getId().equals(id))
                           .findFirst()
                           .orElse(null);
        }
    }

    public static class Product {
        private String id;
        private String name;
        private Double price;

        public Product(String id, String name, Double price) {
            this.id = id;
            this.name = name;
            this.price = price;
        }
        public String getId() { return id; }
        public String getName() { return name; }
        public Double getPrice() { return price; }
    }
}

Implementing _entities Resolver

The Apollo Gateway needs a way to ask your subgraph for an entity by its key. This is handled by a special _entities query.

In Spring Boot, you implement this by providing a RuntimeWiringConfigurer bean. This bean maps the _entities query to a data fetcher.

The data fetcher receives "representations" (maps of __typename and key fields) and should return the corresponding entity objects.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import graphql.schema.idl.RuntimeWiring;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.stereotype.Controller;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.ArrayList;

// Assume schema.graphqls and Product/ProductResolver from Scene 5

@SpringBootApplication
public class ProductSubgraphApplication { // Same as previous scene

    public static void main(String[] args) {
        SpringApplication.run(ProductSubgraphApplication.class, args);
    }

    @Bean
    public RuntimeWiringConfigurer runtimeWiringConfigurer() {
        return builder -> builder.type("Query", typeWiring ->
            typeWiring.dataFetcher("_entities", env -> {
                List<Map<String, Object>> representations = env.getArgument("representations");
                return representations.stream()
                        .map(representation -> {
                            if ("Product".equals(representation.get("__typename"))) {
                                String id = (String) representation.get("id");
                                // In a real app, fetch from DB by ID
                                return new Product(id, "Product " + id + " (Federated)", 0.0);
                            }
                            return null;
                        })
                        .collect(Collectors.toList());
            })
        );
    }

    // ProductResolver and Product class from Scene 5 would be here
    @Controller
    public static class ProductResolver {
        private static final List<Product> products = new ArrayList<>(List.of(
            new Product("1", "Laptop", 1200.00),
            new Product("2", "Mouse", 25.00)
        ));
        @QueryMapping public List<Product> products() { return products; }
        @QueryMapping public Product productById(String id) {
            return products.stream().filter(p -> p.getId().equals(id)).findFirst().orElse(null);
        }
    }
    public static class Product {
        private String id; private String name; private Double price;
        public Product(String id, String name, Double price) { this.id=id; this.name=name; this.price=price; }
        public String getId() { return id; } public String getName() { return name; }
        public Double getPrice() { return price; }
    }
}

Expanding Your Owned Entity

If your subgraph owns an entity (meaning it defined the @key), you can add more fields to it just by updating its schema and resolver.

These new fields will be available directly from your subgraph. The Gateway will know to route queries for these fields to your service.

Let's add a description field to our Product entity.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
import org.springframework.context.annotation.Bean;
import graphql.schema.idl.RuntimeWiring;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.ArrayList;

// src/main/resources/graphql/schema.graphqls (updated)
// type Product @key(fields: "id") {
//   id: ID!
//   name: String!
//   price: Float!
//   description: String # NEW FIELD
// }
// ... (rest of schema and _entities resolver as before)

@SpringBootApplication
public class ProductSubgraphApplication { // Same as previous scene

    public static void main(String[] args) {
        SpringApplication.run(ProductSubgraphApplication.class, args);
    }

    @Controller
    public static class ProductResolver {
        private static final List<Product> products = new ArrayList<>(List.of(
            new Product("1", "Laptop", 1200.00, "Powerful computing device."),
            new Product("2", "Mouse", 25.00, "Ergonomic wireless mouse.")
        ));

        @QueryMapping public List<Product> products() { return products; }
        @QueryMapping public Product productById(String id) {
            return products.stream().filter(p -> p.getId().equals(id)).findFirst().orElse(null);
        }
    }

    public static class Product {
        private String id; private String name; private Double price; private String description; // NEW FIELD
        public Product(String id, String name, Double price, String description) {
            this.id = id; this.name = name; this.price = price; this.description = description;
        }
        public String getId() { return id; } public String getName() { return name; }
        public Double getPrice() { return price; } public String getDescription() { return description; }
    }

    // runtimeWiringConfigurer bean would be here as in Scene 6
    @Bean
    public RuntimeWiringConfigurer runtimeWiringConfigurer() {
        return builder -> builder.type("Query", typeWiring ->
            typeWiring.dataFetcher("_entities", env -> {
                List<Map<String, Object>> representations = env.getArgument("representations");
                return representations.stream()
                            .map(representation -> {
                                if ("Product".equals(representation.get("__typename"))) {
                                    String id = (String) representation.get("id");
                                    // Fetch full product data based on ID
                                    return new Product(id, "Product " + id, 0.0, "Placeholder desc.");
                                }
                                return null;
                            })
                            .collect(Collectors.toList());
                })
            );
        }
}

Extending External Entities

What if another subgraph defines Product, but your subgraph (e.g., a "Review" service) wants to add reviews to it?

You use the @extends directive on the type definition. This tells the Gateway that this type is an extension of an entity defined elsewhere.

You also use @external on fields that are part of the original entity definition, but are needed by your subgraph to resolve its new fields.

# src/main/resources/graphql/schema.graphqls (Review Subgraph)
type Query {
  reviews: [Review]
}

type Review {
  id: ID!
  text: String!
  productId: ID!
}

# This subgraph extends the Product type from another service
extend type Product @key(fields: "id") {
  id: ID! @external
  reviews: [Review] # New field added by THIS subgraph
}

Resolving Extended Fields

When your subgraph extends an entity, you need to implement a resolver for the new fields you've added (e.g., reviews on Product).

Spring GraphQL uses the @SchemaMapping annotation for this. The method receives the parent object (the Product instance) which will contain the @external fields.

The Gateway will pass the id (our @external field) to your resolver, allowing you to fetch the relevant reviews.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping; // Important for extensions
import org.springframework.stereotype.Controller;
import org.springframework.context.annotation.Bean;
import graphql.schema.idl.RuntimeWiring;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.ArrayList;

// Assume schema.graphqls is defined as in Scene 8

@SpringBootApplication
public class ReviewSubgraphApplication {

    public static void main(String[] args) {
        SpringApplication.run(ReviewSubgraphApplication.class, args);
    }

    @Controller
    public static class ReviewResolver {
        private static final List<Review> reviews = new ArrayList<>(List.of(
            new Review("101", "Great laptop!", "1"),
            new Review("102", "Mouse works well.", "2"),
            new Review("103", "Solid performance.", "1")
        ));

        @QueryMapping
        public List<Review> reviews() {
            return reviews;
        }

        // Resolver for the 'reviews' field on the extended Product type
        @SchemaMapping(typeName = "Product")
        public List<Review> reviews(Product product) { // Product object will have its 'id'
            return reviews.stream()
                          .filter(r -> r.getProductId().equals(product.getId()))
                          .collect(Collectors.toList());
        }
    }

    public static class Review {
        private String id; private String text; private String productId;
        public Review(String id, String text, String productId) {
            this.id = id; this.text = text; this.productId = productId;
        }
        public String getId() { return id; } public String getText() { return text; }
        public String getProductId() { return productId; }
    }

    // Product class for extensions (only needs @external fields)
    public static class Product {
        private String id; // This field is received from Gateway
        public Product(String id) { this.id = id; }
        public String getId() { return id; }
    }

    // _entities resolver for Review type (if Review is also an entity) would be here if needed
}

Directives Check

Federation relies heavily on specific directives to define how subgraphs interact. Test your understanding of these key directives.

Subgraph Building Recap

You've learned how to build federated subgraphs in Spring Boot!

  • Defining entities with the @key directive.
  • Implementing _entities resolvers to allow the Gateway to fetch entities.
  • Adding fields to entities owned by your subgraph.
  • Extending entities from other subgraphs using @extends and @external.
  • Implementing resolvers for these extended fields using @SchemaMapping.

These building blocks enable you to create modular and scalable GraphQL APIs with Federation.

البدء مجانًا

تعلم GraphQL APIs with Spring Boot مع معلم ذكاء اصطناعي — مجانًا

اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.

الدورات
12
الدروس
48

الأسئلة الشائعة

هل درس «بناء Subgraphs متّحدة» مجاني؟

نعم — نص درس «بناء Subgraphs متّحدة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة GraphQL APIs with Spring Boot، انتقل إلى CoddyKit PRO. تتضمن دورة GraphQL APIs with Spring Boot 4 دروس في المجموع.

ماذا ستتعلم في «بناء Subgraphs متّحدة»؟

طوّر خدمات Spring Boot فردية بصفتها subgraphs متّحدة، مع تحديد مخططاتها وعلاقات كياناتها. تتمرن على GraphQL APIs with Spring Boot مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ GraphQL APIs with Spring Boot؟

لا تُشترط خبرة سابقة. GraphQL APIs with Spring Boot على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «بناء Subgraphs متّحدة»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس GraphQL APIs with Spring Boot هذا؟

نعم. كل درس في GraphQL APIs with Spring Boot يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. مقدمة إلى Apollo Federation
  2. بناء Subgraphs متّحدة
  3. إعداد Gateway وإدارته
  4. مراجع الكيانات والتوجيه @key
← العودة إلى GraphQL APIs with Spring Boot