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顶级项目,将发布第一个里程碑版本

posted @ 2021-08-12 21:15  strongmore  阅读(2255)  评论(0编辑  收藏  举报