GraphQl 使用 - java
去年在做内容库的时候,涉及到了多系统多数据源互相查询的情景,当时就想要将内容库的所有数据,增加统一的数据接入层,通过graphql的方式提供给上层业务使用,后面受限于人力没有实施。目前从头开始做站群相关的业务,顺带着浅尝了一下graphql,感觉还不错。
GraphQL 是什么
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
背景
参看: https://www.youtube.com/watch?v=Ah6GSCK5Rfs
graphql 和 rest api 是同一层的东西,都是一种基于 http 之上的数据接口协议,两者设计理念完全不同。目前来说 graphql 在很多大小厂也都开始使用,确实会方便很多。 但是 rest api 还是最广泛应用的协议,比如 AMP (T T)。 事实上任何东西都有2面性,对于graphql 来说,优缺点都有
与 REST API 相比的好处
- 优点
- 一次提供,多次使用
- 作为后端,我们只需要关心数据字段的提供,不需要关心前端是怎么用这些字段,页面结构是怎样的
- 真的不需要频繁的对接口了
- 动态可扩展,无冗余查询,所见即所得
- 因为前端只会请求需要的字段,增加新的字段后,不用担心老的业务请求这些冗余数据,很好的支撑了可扩展性
- 所见即所得,能够帮助客户端代码不易出错
- 多个字段,一次请求
- 对于前端来说,面向schema编程,无需知道后端是多少个服务在支撑需要的这些字段,也无需频繁的更改接口去从新服务获取想要的字段,只需要在请求的时候增加一个 field 就好了
- 代码即文档
- 直接通过graphqli 等工具查看schema协议,不需要文档,写注释就好了
- 类型校验
- 在定义好schema的同时,就约束了接口类型,graphql支持强类型校验
- …
- 一次提供,多次使用
- 缺点
- 基于post 请求
- 传统方式无法监控(nginx)
- 不能利用 http 自身的缓存机制
- 没有充分利用http
- 只使用了 post 请求,没有其他http的方法:方法的幂等性
- 缺失了http状态码
- 配套还不完善
- 微服务
- 监控
- 分流
- …
- 基于post 请求
怎么用
几个基本概念
最好参看官方文档:https://graphql.github.io/graphql-spec/June2018/
中文版的可以查看:https://spec.graphql.cn/#sec-Overview-
概念很多,摘了几个出来,在实际业务场景中使用的话,还是有很多需要探的地方,这里只是大概的一个介绍
- schema
- 相当于协议文件,定义了所有对象的类型和查询接口
- 一般定义的操作是2个: query/ mutation , 一个用于读,一个用于写,也存在subscription的操作
- type
- graphql 有自己的一套类型系统,有8种类型
- scalars/标量
- objects/对象
- interfaces/接口
- unions/联合
- Enums/枚举
- Input objects/输入类型
- lists/列表
- Non-null/非空型
- graphql 有自己的基本类型,也可以自定义一些类型,但是需要相应的解释器
- graphql 有自己的一套类型系统,有8种类型
- 内省 -> 验证 -> 执行 -> 响应
- datafetcher
- 实现过程中,定义了字段的获取方法
实操
具体的代码可以参看: https://github.com/xtestw/graphql-demo
引入 graphql 的依赖包
1
2
3
4
5<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>13.0</version>
</dependency>定义 schema 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35schema {
query: Query
mutation: Mutation
}
scalar Date
type Query {
student(id:Int!):Student
students(pagination:Pagination):[Student]
}
type Mutation {
add(newStudent:NewStudent):Student
}
input Pagination{
index: Int!
size: Int!
}
input NewStudent{
name:String!
sex:Sex
}
type Student {
id: Int
name: String
sex: Sex
creation: Date
}
enum Sex{
MALE,FEMALE
}实现 schema 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72package com.xtestw.graphql.demo.schema.wiring;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.xtestw.graphql.demo.schema.model.NewStudent;
import com.xtestw.graphql.demo.schema.model.Pagination;
import com.xtestw.graphql.demo.storage.Student;
import com.xtestw.graphql.demo.storage.Student.Sex;
import com.xtestw.graphql.demo.storage.repository.StudentRepository;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.idl.MapEnumValuesProvider;
import graphql.schema.idl.TypeRuntimeWiring;
import graphql.schema.idl.TypeRuntimeWiring.Builder;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Create by xuwei on 2019/8/4
*/
public class StudentWiring implements Wiring {
StudentRepository studentRepository;
ObjectMapper mapper = new ObjectMapper();
public List<TypeRuntimeWiring> wireTypes() {
return Collections.singletonList(
TypeRuntimeWiring.newTypeWiring("Sex")
.enumValues(new MapEnumValuesProvider(
ImmutableMap.of(
"MALE", Sex.MALE,
"FEMALE", Sex.FEMALE
)))
.build());
}
public void wireQueries(Builder queryBuilder) {
queryBuilder.dataFetcher("student", this::fetchStudentById)
.dataFetcher("students", this::fetchStudents);
}
private List<Student> fetchStudents(DataFetchingEnvironment dataFetchingEnvironment) {
Pagination pagination = mapper
.convertValue(dataFetchingEnvironment.getArgument("pagination"), Pagination.class);
if (pagination == null) {
pagination = Pagination.create(0, 20);
}
return studentRepository.findAll(pagination.toPageable()).getContent();
}
private Student fetchStudentById(DataFetchingEnvironment dataFetchingEnvironment) {
Integer id = dataFetchingEnvironment.getArgument("id");
return studentRepository.findById(id).orElse(null);
}
public void wireMutations(Builder mutationBuilder) {
mutationBuilder.dataFetcher("add", this::addNewStudent);
}
private Student addNewStudent(DataFetchingEnvironment dataFetchingEnvironment) {
NewStudent newStudent = mapper
.convertValue(dataFetchingEnvironment.getArgument("newStudent"), NewStudent.class);
return studentRepository.save(newStudent.toStudent());
}
}构建 GraphqQL 对象实例
1 | package com.xtestw.graphql.demo.config; |
定义接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73package com.xtestw.graphql.demo.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import com.xtestw.graphql.demo.schema.model.Query;
import graphql.GraphQL;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Create by xuwei on 2019/8/3
*/
public class GraphQLController {
GraphQL graphQL;
private Object query( String queryStr)throws IOException {
Query query = getQuery(queryStr);
return graphQL.execute(query.toExecutionInput());
}
private static ObjectMapper mapper = new ObjectMapper();
private static final MapType VARIABLES_TYPE = mapper.getTypeFactory()
.constructMapType(HashMap.class,
String.class, Object.class);
private Query getQuery(String queryText) throws IOException {
String operationName = null;
String fullQueryText = queryText;
Map<String, Object> variables = null;
JsonNode jsonBody = mapper.readTree(queryText);
if (jsonBody != null) {
JsonNode queryNode = jsonBody.get("query");
if (queryNode != null && queryNode.isTextual()) {
queryText = queryNode.asText();
}
JsonNode operationNameNode = jsonBody.get("operationName");
if (operationNameNode != null && operationNameNode.isTextual()) {
operationName = operationNameNode.asText();
}
JsonNode variablesNode = jsonBody.get("variables");
if (variablesNode != null) {
if (variablesNode.isTextual()) {
String variablesJson = variablesNode.asText();
variables = mapper.convertValue(mapper.readTree(variablesJson), VARIABLES_TYPE);
} else if (variablesNode.isObject()) {
variables = mapper.convertValue(variablesNode, VARIABLES_TYPE);
}
}
}
if (variables == null) {
variables = Collections.emptyMap();
}
return new Query(fullQueryText, queryText, operationName, variables);
}
}测试
总结
graphql 相比较 restapi 来说,各有优缺点。个人感觉 graphql的前景还是很大的,目前最大的问题其实还是相关的生态和基础设施还不够完善,也存在很大的迁移成本和学习成本。不过单纯从数据获取的角度来说,非常有优势!此处我们只做了一个非常浅的探索。