前言
本文并非对GraphQL的详细介绍,也不会过多描述GraphQL与restful架构的优缺点对比。尽量以最简单直白的表达,说明GraphQL是什么及在java中的基本使用。
官方文档:https://graphql.org/
GraphQL是什么
由来
在 2012 年由Facebook创立的一门开源的API查询语言,用于服务器端执行按已定义类型系统的查询,无关特定的数据库或存储引擎,由自己的代码支持。
目的
可以作为restful架构的替代方案。
对比
关于“由来”和“目的”上的几行简单描述,看完之后依然让人迷惑GraphQL是什么,为什么可以替代restful。下面作一个简单直白的对比说明:
作为web开发,restful风格/规范这个词,应该是比较熟悉的。REST描述了客户端与服务端的一种交互形式,restful规范约定了我们平常暴露资源的API的一种格式,比如:
- url描述唯一的资源
- url各部分应该是描述资源的名词,原则上尽量不使用动词
- url表示的资源变更的结果而不应该是个过程
- Http Method表示不同的动作
- 协议和数据格式一般采用的是http+json
- ...
当然了,我们日常作java开发的时候,大多是使用的spring boot(spring mvc),暴露api的Url也不一定完全遵守restful规范(除非公司强制要求),Url里就可能使用动词,HTTP的method只用GET或POST就表达了所有含义等等。
restful不是本文重点,主要是想说其中的一些问题点,比如我开发了一个查询项目信息的接口(item/all),可以检索所有的项目信息,前端会用一个table来展示。如果在另一个页面,有个下拉框展示项目列表,而我作为后端开发,为了省事,我可能会复用item/all这个接口给前端,不会再提供一个新的API。但是这个接口的每条数据都包含了项目的所有字段(比如,id,项目编码,项目名称,创建时间,描述等等)。但对于前端的这个下拉框来说,只需要id和项目名称可能就足够了,这也就是过度获取了。
如果前端获取项目信息时还需要获取该项目相关的人员信息,可能还能需要向后端再发一个请求来获取人员信息,就会出现这种频繁多次请求(当然,实际情况我们开发的时候,有可能会提供这样一个接口,返回一个复合数据结构包括项目和相关的人员信息等)。
还有一个问题,restful中一个url标识一个资源,新增项目是一个url,查询是一个url,修改也是一个url,新增用户信息又是一个url,等等。
GraphQL就可以解决上面描述的这些问题。
GraphQL并不限定必须在WEB中使用,但如果在web中使用,一般暴露一个API就够了,前端可以按需获取哪些字段的数据。可以这样认为,使用GraphQL的后端就像是一个数据源,而前端只需要指定哪些字段(像我们写sql去查询数据库一样),就可以获取哪些数据,由前端来驱动数据。
GraphQL Java
GraphQL提供多种语言的服务器端库。因为本文是以java来说明,所以我会以一下最基本的示例来描述一下GraphQL的使用。
GraphQL在java中的实现的github地址:https://github.com/graphql-java/graphql-java
下面我会以一下最基本的示例简单说明(非web应用):
代码示例
示例代码中,我不会详细说明,每一步为什么这么写,因为GraphQL的内容虽然不多,但也不是一两句全部说得清的,如果感兴趣,建议查看官方文档,系统的了解下。
示例代码仅作示例参考,并非固定的写法范式,所以实际项目中不一定是按照这样的实现流程编码的。
下面示例的业务场景是:获取项目信息
引入依赖
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>11.0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
关于lombok,并非graphql-java需要,而是示例代码中使用了,如果实际开发的时候,不使用lombok并不需要引入这个依赖。
定义GraphQL的schema
在类路径下创建一个item.graphqls
schema {
# 查询
query: Query
# 更新
# mutation: Mutation
}
# 定义一个查询类型
type Query {
queryItemList: ItemList # 定义查询项目列表,为了示例,这里就不传入参数了
getMale: Personnel
item: Item
}
# 定义人员信息类型
type Personnel {
id: ID! #人员id,指定是一个ID类型,且不为空 ID表示一个唯一字段,可以是string或integer
name: String! #人员姓名
age: Int # 人员年龄,可以为空
isMale: Boolean! #是否为男姓
}
# 定义项目字段
type Item {
id: ID!
code: String!
name: String!
people: [Personnel!]! #人员信息,是一个数组/列表
}
type ItemList {
itemList: [Item!]! #获取项目列表
total: Int! # 获取项目总数
}
创建相关bean实例
@Data
public class Personnel {
private long id;
private String name;
private int age;
private boolean isMale;
}
@Data
public class Item {
private long id;
private String name;
private String code;
private List<Personnel> people;
}
@Data
public class ItemList {
private List<Item> itemList;
private int total;
}
定义一个Resolver
public class QueryResolver implements GraphQLQueryResolver {
// 对应item.graphqls里的queryItemList
public ItemList queryItemList() {
ItemList itemList = new ItemList();
itemList.setItemList(new ArrayList<Item>(){{
add(item());
}});
itemList.setTotal(1);
return itemList;
}
public Item item() {
Item item = new Item();
item.setId(1);
item.setCode("graphql");
item.setName("示例");
item.setPeople(people());
return item;
}
public List<Personnel> people() {
return new ArrayList<Personnel>(){{
add(getFemale());
add(getMale());
}};
}
// 返回一个female Personnel
public Personnel getFemale() {
Personnel personnel = new Personnel();
personnel.setId(1);
personnel.setName("xiao hong");
personnel.setAge(18);
personnel.setMale(false);
return personnel;
}
// 返回一个male Personnel
public Personnel getMale() {
Personnel personnel = new Personnel();
personnel.setId(2);
personnel.setName("xiao ming");
personnel.setAge(20);
personnel.setMale(true);
return personnel;
}
}
创建并执行
public class GraphQLExample {
public static void main(String[] args) {
GraphQLSchema graphQLSchema = SchemaParser.newParser()
.file("item.graphqls")
.resolvers(new QueryResolver())
// .file("book.graphqls")
// .resolvers(new BookResolver()) //其它定义继续增加
.build().makeExecutableSchema();
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
// 查询项目列表,获取项目的编码和姓名字段,各字段不一定非要逗号分隔,空格,换行都行
String query = "{queryItemList{itemList{code,name}}}";
System.out.println("查询项目列表,获取项目的编码和姓名字段,查询语句:" + query);
ExecutionResult executionResult = graphQL.execute(query);
System.out.println("查询结果:" + executionResult.getData().toString());
System.out.println();
query = "{queryItemList{itemList{id, people{name}}}}";
System.out.println("查询项目列表,获取项目的id和相关人员的姓名字段,查询语句:" + query);
executionResult = graphQL.execute(query);
System.out.println("查询结果:" + executionResult.getData().toString());
System.out.println();
query = "{getMale{name,age}}";
System.out.println("查询男用户的名字和年龄,查询语句:" + query);
executionResult = graphQL.execute(query);
System.out.println("查询结果:" + executionResult.getData().toString());
System.out.println();
}
}
执行结果如下:
根据截图可以看到,结果是按需查询。这只是一个很基本的示例,也并非web应用,但在实际开发的时候可以只提供一个web接口,由前端请求该接口传入查询语句。
上面的示例都比较简单,也不包含参数,关于带参数的定义,我贴一个我实际开发中的写法,仅作参考,就不再提供相关demo了:
哪些项目在使用它
其实我知道的只有skywalking,因为最近在skywalking上开发一些功能的时候,它的后端是采用GraphQL,便顺便把相关的官方文档看了遍,研究了一下,如果想参考skywalking的相关用法,这里是它的github地址:https://github.com/apache/skywalking
末语
如果不了解GraphQL,只是看上面的示例代码,应该还是比较头晕的。如果感兴趣想要系统地了解应用在自己项目中,这里有一些官方文档,示例还比较清楚:
- GraphQL 介绍
- GraphQL Java Kickstart
- graphql-java github
- Getting started with GraphQL Java and Spring Boot