zoukankan      html  css  js  c++  java
  • 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顶级项目,将发布第一个里程碑版本

  • 相关阅读:
    JavaScript父子页面之间的相互调用
    把一个集合自定转成json字符串
    分页类与前台和后台的调用方法
    移动App测试实战—专项测试
    功能测试需求分析方法
    App弱网测试
    App常见产品问题及预防方法
    21天,搞定软件测试从业者必备的Linux命令
    Docker入门
    Linux核心-慧测课堂笔记
  • 原文地址:https://www.cnblogs.com/strongmore/p/15134920.html
Copyright © 2011-2022 走看看