SpringBoot整合GraphQL入門


前言

GraphQL 是一種 API 查詢語言, 簡單來說就是一種描述客戶端如何向服務器端請求數據的 API 語法,和 RESTful 規范類似。

GraphQL 規范由 Facebook 在2015年開源,設計初衷是想要用類似圖的方式表示數據,即不像在 RESTful 中,數據被各個 API endpoint 所分割,而是有關聯和層次結構的被組織在一起,更多相關知識可以去 GraphQL 官網 了解。

GraphQL-Java

介紹

GraphQL 只是一種規范,還需要有具體的語言庫來實現這種規范,就像 FastJson 實現了 JSON 規范一樣,GraphQL 在java中的一種實現是 graphql-java,更多語言對 GraphQL 的支持可以 看這里

簡單使用

引入maven依賴

<dependency>
   <groupId>com.graphql-java</groupId>
   <artifactId>graphql-java</artifactId>
   <version>11.0</version>
</dependency>

客戶端使用

import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.StaticDataFetcher;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;

public class Client {

  public static void main(String[] args) {
    //定義schema,可以類比xml定義
    String schema = "type Query{hello: String}";
    //解析schema
    SchemaParser schemaParser = new SchemaParser();
    TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema);

    //加載服務端數據
    RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
        .type("Query", builder -> builder.dataFetcher("hello", new StaticDataFetcher("world")))
        .build();

    SchemaGenerator schemaGenerator = new SchemaGenerator();
    GraphQLSchema graphQLSchema = schemaGenerator
        .makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
    //構建GraphQL實例並執行查詢腳本
    GraphQL build = GraphQL.newGraphQL(graphQLSchema).build();
    ExecutionResult executionResult = build.execute("{hello}");
    System.out.println(executionResult.getData().toString());//{hello=world}
  }

}

基本處理流程圖如下

想了解更多關於定義schema的介紹,可以查看 官方文檔-Queries and Mutations

SpringBoot整合GraphQL

引入maven依賴

<dependency>
   <groupId>com.graphql-java</groupId>
   <artifactId>graphql-java-spring-boot-starter-webmvc</artifactId>
   <version>1.0</version>
</dependency>
<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>27.1-jre</version>
</dependency>

Guava不是必需的,這是使用Guava來簡化我們的代碼,graphql-java-spring-boot-starter-webmvc 自動裝配GraphQLController

這是一個統一處理的Controller,源碼如下

import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.ExecutionResult;
import graphql.Internal;
import graphql.spring.web.servlet.ExecutionResultHandler;
import graphql.spring.web.servlet.GraphQLInvocation;
import graphql.spring.web.servlet.GraphQLInvocationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;

import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

@RestController
@Internal
public class GraphQLController {

    @Autowired
    GraphQLInvocation graphQLInvocation;

    @Autowired
    ExecutionResultHandler executionResultHandler;

    @Autowired
    ObjectMapper objectMapper;

    @RequestMapping(value = "${graphql.url:graphql}",
            method = RequestMethod.POST,
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public Object graphqlPOST(@RequestBody GraphQLRequestBody body,
                              WebRequest webRequest) {
        String query = body.getQuery();
        if (query == null) {
            query = "";
        }
        CompletableFuture<ExecutionResult> executionResult = graphQLInvocation.invoke(new GraphQLInvocationData(query, body.getOperationName(), body.getVariables()), webRequest);
        return executionResultHandler.handleExecutionResult(executionResult);
    }

    @RequestMapping(value = "${graphql.url:graphql}",
            method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public Object graphqlGET(
            @RequestParam("query") String query,
            @RequestParam(value = "operationName", required = false) String operationName,
            @RequestParam(value = "variables", required = false) String variablesJson,
            WebRequest webRequest) {
        CompletableFuture<ExecutionResult> executionResult = graphQLInvocation.invoke(new GraphQLInvocationData(query, operationName, convertVariablesJson(variablesJson)), webRequest);
        return executionResultHandler.handleExecutionResult(executionResult);
    }

    private Map<String, Object> convertVariablesJson(String jsonMap) {
        if (jsonMap == null) return Collections.emptyMap();
        try {
            return objectMapper.readValue(jsonMap, Map.class);
        } catch (IOException e) {
            throw new RuntimeException("Could not convert variables GET parameter: expected a JSON map", e);
        }

    }
}

在這個基礎上我們只需要定義 schema 和實例化 GraphQL 對象就可以了,具體流程可以參考 Getting started with GraphQL Java and Spring Boot

定義schema文件

文件 schema.graphqls

type Query {
    bookById(id: ID): Book
}

type Book {
    id: ID
    name: String
    pageCount: Int
    author: Author
}

type Author {
    id: ID
    firstName: String
    lastName: String
}

定義DataFetcher

import com.google.common.collect.ImmutableMap;
import graphql.schema.DataFetcher;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;

@Component
public class GraphQLDataFetchers {

  private static List<Map<String, String>> books = Arrays.asList(
      ImmutableMap.of("id", "book-1",
          "name", "Harry Potter and the Philosopher's Stone",
          "pageCount", "223",
          "authorId", "author-1"),
      ImmutableMap.of("id", "book-2",
          "name", "Moby Dick",
          "pageCount", "635",
          "authorId", "author-2"),
      ImmutableMap.of("id", "book-3",
          "name", "Interview with the vampire",
          "pageCount", "371",
          "authorId", "author-3")
  );

  private static List<Map<String, String>> authors = Arrays.asList(
      ImmutableMap.of("id", "author-1",
          "firstName", "Joanne",
          "lastName", "Rowling"),
      ImmutableMap.of("id", "author-2",
          "firstName", "Herman",
          "lastName", "Melville"),
      ImmutableMap.of("id", "author-3",
          "firstName", "Anne",
          "lastName", "Rice")
  );

  public DataFetcher getBookByIdDataFetcher() {
    return dataFetchingEnvironment -> {
      String bookId = dataFetchingEnvironment.getArgument("id");
      return books
          .stream()
          .filter(book -> book.get("id").equals(bookId))
          .findFirst()
          .orElse(null);
    };
  }

  public DataFetcher getAuthorDataFetcher() {
    return dataFetchingEnvironment -> {
      Map<String, String> book = dataFetchingEnvironment.getSource();
      String authorId = book.get("authorId");
      return authors
          .stream()
          .filter(author -> author.get("id").equals(authorId))
          .findFirst()
          .orElse(null);
    };
  }
}

DataFetcher表示當執行查詢腳本時,獲取某一個屬性的數據所對應的業務處理,復雜的業務邏輯就在這塊,這里我們使用靜態數據來模擬,真實環境應該查詢數據庫來獲取數據。

解析schema並創建GraphQL

import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import graphql.schema.idl.TypeRuntimeWiring;
import java.nio.charset.StandardCharsets;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

@Component
public class GraphQLProvider implements InitializingBean {

  @Autowired
  private GraphQLDataFetchers graphQLDataFetchers;

  private GraphQLSchema graphQLSchema;

  private GraphQLSchema buildSchema(String sdl) {
    TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
    RuntimeWiring runtimeWiring = buildWiring();
    SchemaGenerator schemaGenerator = new SchemaGenerator();
    return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
  }

  private RuntimeWiring buildWiring() {
    return RuntimeWiring.newRuntimeWiring()
        .type(TypeRuntimeWiring.newTypeWiring("Query")
            .dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
        .type(TypeRuntimeWiring.newTypeWiring("Book")
            .dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
        .build();
  }

  @Bean
  public GraphQL graphQL() {
    return GraphQL.newGraphQL(graphQLSchema).build();
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    Resource resource = new DefaultResourceLoader().getResource("schema.graphqls");
    String sdl = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
    graphQLSchema = buildSchema(sdl);
  }
}

使用Postman客戶端請求數據

最終請求的 content-type 還是 application/json,postman只是將數據做了一下轉換,

{
  bookById(id: "book-1"){
    id
    name
    pageCount
  }
}

到了服務器,數據就變成了

leetcode答題網站中就大量使用到了 GraphQL 這種請求方式,

參考

GraphQL文檔官網
Getting started with GraphQL Java and Spring Boot
GraphQL java工程化實踐
graphQl + SpringBoot 入門
Spring GraphQL成為Spring頂級項目,將發布第一個里程碑版本


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM