GraphQL系列四 Java实战入门

本篇主要使用graphql-java来演示如何使用Java来构建GraphQL API,文中使了Gradle作为构建工具,但未使用其特性,因此你也可以使用Maven,只是简单添加依赖即可。主要涉及到枚举(enum)、输入类型(input)、参数(argument)、接口(interface)、联合(union)等服务端,以及客户端查询测试。本例代码存放在 https://github.com/zhaiqianfeng/GraphQL-Demo/tree/master/java
GraphQL

添加依赖

gradle

compile 'com.graphql-java:graphql-java:3.0.0'

maven

<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>3.0.0</version>
</dependency>

本文示例使用的构建工具是Gradle。

示例代码结构

示例结构目录如下

├── advance 高级示例
│   ├── GraphQL_argument.java 传入参数示例
│   ├── GraphQL_interface.java 接口示例
│   └── GraphQL_union.java 联合示例
├── get_start 入门示例
│   └── GraphQL_Simple.java 简单入门示例
└── model Java实体
├── Dog4Interface.java 为接口示例准备的Dog实现
├── Dog4Union.java 为联合示例准备的Dog
├── Fish4Interface.java 为接口示例准备的Fish实现
├── Fish4Union.java 为联合示例准备的Fish
├── IAnimal.java 为接口示例准备的Animal接口
└── User.java 为示例准备的User

每个示例基本遵循流程:提供带查询的示例数据-> 定义GrapQL数据类型 -> 定义暴露给客户端的query api和mutaion api -> 创建GraphQL Schema -> 测试输出

简单示例

示例代码如下

// com.zqf.get_start.GraphQL_Simple
//服务端示例数据
User user=new User();
user.setName("zhaiqianfeng");
user.setSex("男");
user.setIntro("博主,专注于Linux,Java,nodeJs,Web前端:Html5,JavaScript,CSS3");
//定义GraphQL类型
GraphQLObjectType userType = newObject()
.name("User")
.field(newFieldDefinition().name("name").type(GraphQLString))
.field(newFieldDefinition().name("sex").type(GraphQLString))
.field(newFieldDefinition().name("intro").type(GraphQLString))
.build();
//定义暴露给客户端的查询query api
GraphQLObjectType queryType = newObject()
.name("userQuery")
.field(newFieldDefinition().type(userType).name("user").staticValue(user))
.build();
//创建Schema
GraphQLSchema schema = GraphQLSchema.newSchema()
.query(queryType)
.build();
//测试输出
GraphQL graphQL = GraphQL.newGraphQL(schema).build();
Map<String, Object> result = graphQL.execute("{user{name,sex,intro}}").getData();
System.out.println(result);

执行输出

{user={name=zhaiqianfeng, sex=男, intro=博主,专注于Linux,Java,nodeJs,Web前端:Html5,JavaScript,CSS3}}

参数(argument)示例

参数示例代码比较多,请参考github上的源码:GraphQL_argument.java,做如下几点说明。

定义形参

传输参数是通过argument方法传入并指定类型,如

.argument(newArgument()
.name("id")
.type(new GraphQLNonNull(GraphQLInt)))

接收参数

获取参数使用DataFetchingEnvironment,如

int id=environment.getArgument("id");

获取参数有如下两个重构

Map<String, Object> getArguments();
<T> T getArgument(String name);

但当我使用后者把传入的userInfo转换为User时,异常

java.util.LinkedHashMap cannot be cast to com.zqf.model.User

因此不得不如下蹩脚书写

Map<String,Object> userInfoMap=environment.getArgument("userInfo");
User userInfo=new User();
for (String key : userInfoMap.keySet()){
switch (key){
case "name":
userInfo.setName(userInfoMap.get("name").toString());
break;
case "sex":
userInfo.setSex(userInfoMap.get("sex").toString());
break;
case "intro":
userInfo.setIntro(userInfoMap.get("intro").toString());
break;
case "skills":
ArrayList<String> skillsList=(ArrayList<String>) userInfoMap.get("skills");
String[] skills=new String[skillsList.size()];
userInfo.setSkills(skillsList.toArray(skills));
break;
}
}

Resolver

解析数据使用的是DataFetcher,如

.dataFetcher(new DataFetcher() {
@Override
public Object get(DataFetchingEnvironment environment) {
int id=environment.getArgument("id");
return users.get(id);
}
}))

为了简洁,你可以使用lambda表达式

.dataFetcher(environment -> {
int id=environment.getArgument("id");
return users.get(id);
}))

联合(union)示例

这部分没说明好说的,如果了解了union的概念代码示例非常清晰,请参考github上的源码:GraphQL_union.java。使用GraphQLUnionType定义union类型,其中possibleType指定可以union的类型,重点是关注typeResolver来做类型转换,但个人觉得这里实现有待改进,对于Java这种强类型语言自动处理比较人性化。

GraphQLUnionType animalUnion = newUnionType()//定义联合类型(union)
.name("IAnimal")
.possibleType(dogType)
.possibleType(fishType)
.description("动物联合")
.typeResolver(env -> {
if(env.getObject() instanceof Dog4Union){
return dogType;
}if(env.getObject() instanceof Fish4Union){
return fishType;
}
return null;
})
.build();

接口(interface)示例

graphql-java对GraphQL的接口实现确实使用的Java接口,但个人觉得使用集成或许更合适。定义接口使用的GraphQLInterfaceType关键字,实现接口使用的withInterface关键字。

坑:对象为定义

上面的关键字都不难理解,蛋疼的是把如下的定义放在方法体中却提示为定义

GraphQLInterfaceType animalInterface = newInterface()//定义接口(interface)类型
.name("IAnimal")
.description("动物接口")
.field(newFieldDefinition()
.name("name")
.type(GraphQLString))
.typeResolver(new TypeResolver() {
@Override
public GraphQLObjectType getType(TypeResolutionEnvironment env) {
if (env.getObject() instanceof Dog4Interface) {
return dogType; //提示 dogType 未定义
}
if (env.getObject() instanceof Fish4Interface) {
return fishType; //提示 fishType 未定义
}
return null;
}
})
.build();
GraphQLObjectType dogType = newObject()//定义Dog类型
.name("Dog4Interface")
.field(newFieldDefinition().name("name").type(GraphQLString))
.field(newFieldDefinition().name("legs").type(GraphQLInt))
.withInterface(animalInterface)
.build();
GraphQLObjectType fishType = newObject()//定义Fish类型
.name("Fish4Interface")
.field(newFieldDefinition().name("name").type(GraphQLString))
.field(newFieldDefinition().name("tailColor").type(GraphQLString))
.withInterface(animalInterface)
.build();

也看到了有开发者提了issue:Circularity between object and interface type definitions,但依然没看到有效的解决方案,只好顺着作者愿意,把上面的定义作为字段才能解决,具体代码可参考github上的源码:GraphQL_interface.java

Schema加入额外类型

最后还要提示下,由于查询返回的接口,但实现并没有自动添加到Schema中,因此需要在创建Schema时间,将其手动加入

//额外的GraphQL类型
Set<GraphQLType> types=new HashSet<>();
types.add(dogType);
types.add(fishType);
//创建Schema
GraphQLSchema schema = GraphQLSchema.newSchema()
.query(queryType)
.build(types);

结束语

虽然遇到了许多坑,但最后还是把需要的功能演示出来了,但无论是那种类库,基本的流程一般不会改变:基本遵循流程:定义GrapQL数据类型 -> 定义暴露给客户端的query api和mutaion api -> 创建GraphQL Schema

翟前锋 wechat
欢迎订阅我的微信公众号:zhaiqianfeng!