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