GraphQL-rajapinnat Spring Bootilla · Oppitunti

Federatiivisten aligraafien rakentaminen

Kehittäkää yksittäisiä Spring Boot -palveluja federatiivisina aligraafeina ja määritelkää niiden skeemat ja entiteettisuhteet.

Oppitunti 2/411 vaihetta

Federatiivisten aligraafien rakentaminen on ilmainen GraphQL-rajapinnat Spring Bootilla-oppitunti CoddyKitissä. Tämä on oppitunti 2/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu GraphQL-rajapinnat Spring Bootilla-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. GraphQL-rajapinnat Spring Bootilla-kurssilla on yhteensä 4 oppituntia.

Johdatus federoituihin alikaavioihin

GraphQL Federationissa supergraafi koostuu useista itsenäisistä GraphQL-palveluista, joita kutsutaan alikaavioiksi.

Kukin alikaavio on itsenäinen GraphQL-rajapinta, joka omistaa tietyn osan toimialueen mallista. Ajattele sitä datastasi vastaavana mikropalveluna.

Apollo Gateway yhdistää nämä alikaaviot yhdeksi yhtenäiseksi rajapinnaksi, joten asiakkaiden on helppo tehdä kyselyitä eri palveluiden tietoihin.

Federoitujen entiteettien ymmärtäminen

Entiteetit ovat alikaavioiden yhdistämisen keskeinen käsite. Entiteetti edustaa tyyppiä, johon voidaan viitata ja jota voidaan laajentaa eri palveluissa.

Esimerkiksi User-entiteetti voidaan määritellä "Auth"-alikaaviossa, mutta "Product"-alikaavio voi laajentaa sitä lisäämällä käyttäjäkohtaisia arvosteluja.

Entiteetit merkitään skeemaan @key-direktiivillä, joka määrittää, miten kyseisen tyypin ilmentymä yksilöidään.

Alikaavion määrittäminen

Federoidun alikaavion rakentaminen Spring Bootilla alkaa tavallisesta Spring Boot GraphQL -projektista.

Keskeinen riippuvuus on spring-boot-starter-graphql. Varmista, että mukana on myös HTTP-päätepisteisiin tarvittava spring-boot-starter-web.

Lisäksi sinun on määritettävä build.gradle- tai pom.xml-tiedosto niin, että se sisältää GraphQL-skeematiedostot (.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'
}

Skeema ja @key-direktiivi

@key-direktiivi on ratkaisevan tärkeä. Se kertoo Apollo Gatewaylle, mitkä kentät yksilöivät entiteetin alikaaviossasi.

Voit määrittää useita avaimia tai yhdistelmäavaimia (esimerkiksi @key(fields: "id type")). Gateway käyttää näitä kenttiä noutaessaan osittaisia tietoja muista alikaavioista.

Määritellään yksinkertainen Product-entiteetti ja merkitään sen id ensisijaiseksi avaimeksi.

# 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!
}

Product-entiteetin rakentaminen

Toteutetaan nyt Product-entiteetti Spring Boot -sovelluksessamme. Tämä edellyttää dataluokan ja resolverin luomista.

Resolver käsittelee tuotteita koskevat kyselyt kuten mikä tahansa GraphQL-päätepiste. Käytämme yksinkertaisuuden vuoksi muistissa säilytettävää dataa.

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; }
    }
}

_entities-resolverin toteuttaminen

Apollo Gateway tarvitsee tavan pyytää alikaavioltasi entiteettiä sen avaimen perusteella. Tämä hoidetaan erityisellä _entities-kyselyllä.

Spring Bootissa toteutat tämän tarjoamalla RuntimeWiringConfigurer-beanin. Tämä bean yhdistää _entities-kyselyn datan noutajaan.

Datan noutaja vastaanottaa "representations"-esityksiä (karttoja, jotka sisältävät __typename- ja avainkentät) ja palauttaa vastaavat entiteettioliot.

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; }
    }
}

Omistamasi entiteetin laajentaminen

Jos alikaaviosi omistaa entiteetin eli määritteli @key-direktiivin, voit lisätä siihen kenttiä päivittämällä sen skeemaa ja resolveria.

Nämä uudet kentät ovat suoraan saatavilla alikaaviostasi. Gateway tietää, että näitä kenttiä koskevat kyselyt on reititettävä palveluusi.

Lisätään description-kenttä Product-entiteettiimme.

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());
                })
            );
        }
}

Ulkoisten entiteettien laajentaminen

Mitä jos toinen alikaavio määrittelee Product-entiteetin, mutta alikaaviosi, esimerkiksi "Review"-palvelu, haluaa lisätä siihen arvosteluja?

Käytät tyyppimäärityksessä @extends-direktiiviä. Se kertoo Gatewaylle, että tämä tyyppi on muualla määritellyn entiteetin laajennus.

Käytät myös @external-direktiiviä kentissä, jotka kuuluvat alkuperäiseen entiteettimääritykseen mutta joita alikaaviosi tarvitsee uusien kenttien ratkaisemiseen.

# 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
}

Laajennettujen kenttien ratkaiseminen

Kun alikaaviosi laajentaa entiteettiä, sinun on toteutettava resolver lisäämillesi uusille kentille, kuten Product-entiteetin reviews-kentälle.

Spring GraphQL käyttää tähän @SchemaMapping-annotaatiota. Metodi vastaanottaa pääolio-olion (Product-instanssin), joka sisältää @external-kentät.

Gateway välittää id-kentän (meidän @external-kenttämme) resolverille, joten voit noutaa asiaankuuluvat arvostelut.

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
}

Direktiivien testi

Federation perustuu vahvasti tiettyihin direktiiveihin, jotka määrittävät, miten alikaaviot toimivat yhdessä. Testaa, miten hyvin hallitset nämä keskeiset direktiivit.

Alikaavioiden rakentamisen kertaus

Olet oppinut rakentamaan federoituja alikaavioita Spring Bootilla!

  • Entiteettien määrittäminen @key-direktiivillä.
  • _entities-resolverien toteuttaminen, jotta Gateway voi noutaa entiteettejä.
  • Kenttien lisääminen alikaaviosi omistamiin entiteetteihin.
  • Muiden alikaavioiden entiteettien laajentaminen direktiiveillä @extends ja @external.
  • Resolverien toteuttaminen näille laajennetuille kentille @SchemaMapping-annotaation avulla.

Näiden rakennuspalikoiden avulla voit luoda modulaarisia ja skaalautuvia GraphQL-rajapintoja Federationilla.

Aloita maksutta

Opi GraphQL-rajapinnat Spring Bootilla tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
12
Oppitunnit
48

Usein kysytyt kysymykset

Onko oppitunti ”Federatiivisten aligraafien rakentaminen” ilmainen?

Kyllä – oppitunnin ”Federatiivisten aligraafien rakentaminen” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko GraphQL-rajapinnat Spring Bootilla-kurssin, päivitä CoddyKit PROhon. GraphQL-rajapinnat Spring Bootilla-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Federatiivisten aligraafien rakentaminen”?

Kehittäkää yksittäisiä Spring Boot -palveluja federatiivisina aligraafeina ja määritelkää niiden skeemat ja entiteettisuhteet. Harjoittelet GraphQL-rajapinnat Spring Bootilla-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni GraphQL-rajapinnat Spring Bootilla-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin GraphQL-rajapinnat Spring Bootilla-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 2/4.

Kuinka kauan ”Federatiivisten aligraafien rakentaminen”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä GraphQL-rajapinnat Spring Bootilla-oppitunnilla?

Kyllä. Jokainen GraphQL-rajapinnat Spring Bootilla-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Apollo Federationin esittely
  2. Federatiivisten aligraafien rakentaminen
  3. Gatewayn määrittäminen ja hallinta
  4. Entiteettiviitteet ja @key-direktiivi
← Takaisin: GraphQL-rajapinnat Spring Bootilla