Graphql-Java实践(三)-工具类graphql-java-tools

开门尖山,放上github地址,大家可自行查阅使用,我这边只是尝试一下
HomePage
注:graphql-java-tools已经不更新了,内部引用的GraphQL-java版本为6.0但最新GraphQL已经更新到8.0,去除了一些6.0中的Class,所以不能另外引用最新的GraphQL-java
首先Maven:新版本有冲突,需要注释

1
2
3
4
5
6
7
8
9
10
11

    com.graphql-java
    graphql-java-tools
    4.3.0



    
    
    

 

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
# 省略schema,使用默认Query,Mutation
type Query {
    # 获取所有用户列表
    user: [ToolUser]
    # 根据id获取狗嘻嘻
    dog(userId:String):[ToolDog]
}
type Mutation{
    createUser(addUser:[InputToolUser]):[ToolUser]
}

input InputToolUser{
    id: String!
    age: Int
    userName: String!
    dogs:[InputToolDog]
}
input InputToolDog {
    id: String!
    dogName: String!
}

type ToolUser {
    id: String!
    age: Int
    userName: String!
    dogs:[ToolDog]
}
type ToolDog {
    id: String!
    dogName: String!
}

 

当你申明完了Schema文件,只需在代码中定义与IDL中一样的类名与属性名,工具类会帮助你进行自动映射绑定,在执行查询时帮助我们获取数据,关于绑定映射,有以下两种情况:
标量类型:变量名字一致自动绑定
复杂/引用类型(标量类型也可以使用来设置除getXXX之外的新的返回值):需要定义对应的Resolvers与其绑定,GraphQLResolver

同时,针对外部API,有特定的RootResolver GraphQLQueryResolver ,GraphQLMutationResolverGraphQLSubscriptionResolver,对应着查询、变更、订阅。

无论是自定义类型或者Resolver,工具类制定了一套方法名和Schema中Field字段的对应规则,通过这些接口的实现类,可以帮助我们简化DataFetcher和Field的绑定关系。
具体可查询Github:
Field Mapping Priority(字段映射优先级)
The field mapping is done by name against public/protected methods and public/protected/private fields, with the following priority:

First on the resolver or root resolver (note that dataClassInstance doesn’t apply for root resolvers):

method (dataClassInstance, fieldArgs [, DataFetchingEnvironment])
method is(dataClassInstance, 
fieldArgs [, DataFetchingEnvironment]), only if the field returns a Boolean
method get(dataClassInstance, fieldArgs [, DataFetchingEnvironment])
method getField(dataClassInstance, 
fieldArgs [, DataFetchingEnvironment])
Then on the data class:

method (fieldArgs [, DataFetchingEnvironment])
method is(
fieldArgs [, DataFetchingEnvironment]), only if the field returns a Boolean
method get(fieldArgs [, DataFetchingEnvironment])
method getField(
fieldArgs [, DataFetchingEnvironment])
field

自己测试了一下,针对DataClass,定义的method好像在执行时不会进行调用,所以暂时还是通过Resolver来获取想要的数据,无论是标量类型还是复杂类型

这是种实现, 当提供了resolver时优先使用(在pojo是A类型,而IDL是B类型时很有用,Date类型也是), 其次是 class this.get方法。
自定义Resolver中多了一个对象实例的参数,这个对象是当前正在处理的Field字段所在的对象实例,可以帮助我们获取对象中的属性值,在一些需要根据对象内部字段判断的场景内很有用。比如User类内部有List属性,那么getDogs方法的第一个参数就可以是user实例。
换一个角度思考,GraphQL对于Field的获取是冒泡向下获取的,直到遇到标量类型。GraphQL-java基础包中也说明了针对所有非标量类型,都可以定义一个DataFetcher,而不是使用默认的PropertyDataFetcher。这样一来,Resolver的作用也就不言而喻了。

简单来说,只要实现类中的公有方法有与Schema中Field名称满足上述规则,针对Resolver,方法的参数依次为(当前数据对象–RootResolver没有该属性、参数、环境上下文–可选);针对单纯的自定义类型,方法参数依次为(参数,环境上下文–可选),Resolver多了一个当前处理的对象信息。可以看出,对于自定义类型,其实就是默认的get/set方法,和官方基础包中的PropertyDataFetcher功能一致。

至此,继续例子代码:
自定义类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data
@AllArgsConstructor
public class ToolUser implements Serializable{
    private static final long serialVersionUID = -3272807693514763753L;

    private String id;
    private String userName;
    private int age;
    private List dogs;

}
@Data
@AllArgsConstructor
public class ToolDog implements Serializable{
    private static final long serialVersionUID = -3272807693514763753L;
    private String id;
    private String dogName;
}

 

自定义Resolver(解决user中的非标量类型dogs的数据获取):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ToolUserResolver implements GraphQLResolver {

    //根据userId获取dogs
    public List getDogs(ToolUser user){
        String userId = user.getId();
        System.out.println("UserResolver , userId = " +userId);
        return DemoRepository.dogs.get(userId);
    }
}
//标量类型,在ToolDog中定义使用无效
public class ToolDogResolver implements GraphQLResolver {

    //根据userId获取dogs
    public String getId(ToolDog dog,DataFetchingEnvironment env){
        String dogId = dog.getId();
        System.out.println("ToolDogResolver , dogId = " +dogId);
        return "resolver - " + dogId;
    }
}

 

RootResolvers-Query:

1
2
3
4
5
6
7
8
9
10
11
12
public class ToolQuery implements GraphQLQueryResolver {

    //schema user
    public List user(DataFetchingEnvironment env){
        return DemoRepository.users.values().stream().collect(Collectors.toList());
    }

    //schema dogs
    public List dog(String userId){
        return DemoRepository.dogs.get(userId);
    }
}

 

RootResolvers-Mutation:

1
2
3
4
5
6
7
8
9
public class ToolMutation implements GraphQLMutationResolver {

    //schema user
    public List createUser(List toolUsers){
        System.out.println("createUser - " + toolUsers);
        return DemoRepository.users.values().stream().collect(Collectors.toList());
    }

}

 

这里说一来,对于Mutation,由于是根据字段名及类名进行的自动绑定,故需要编写对应的input 自定义类型,其实蛮傻的,相当于一个类型定义了两次,官方因为只能通过map获取入参,反而没有这方面的困扰:

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
@Data
@AllArgsConstructor
public class InputToolUser implements Serializable{
    private static final long serialVersionUID = -3272807693514763753L;

    private String id;
    private String userName;
    private int age;
    private List dogs;
}
public class InputToolDog implements Serializable,Cloneable{
    private static final long serialVersionUID = -3272807693514763753L;

    private String id;
    private String dogName;
    public InputToolDog(){}

    @Override
    protected InputToolDog clone()  {
        InputToolDog clone = null;
        try{
            clone = (InputToolDog) super.clone();

        }catch(CloneNotSupportedException e){
            throw new RuntimeException(e); // won't happen
        }

        return clone;
    }

}

 

这边有一点要注意的是,前面几篇文章中也提到过,到遇到复杂对象的Variable时,GraphQL引擎只会接受Map类型的数据结构。细心的你可能已经发现,这次Mutation操作的入参是一个List,所以这次的数据结构应该是List,大家在用Json作数据转换的时候需要注意了。
在这里,工具类又帮我们完成了一件事,基础包版本中,复杂类型的入参只能通过getArgument获得,而且返回值是一个Map,而不是这边已经帮我们转换过的对象List toolUsers。

Main函数:

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
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        new DemoRepository();
        GraphQLSchema schema = SchemaParser.newParser()
                .file("graphql/tool.graphqls").resolvers(new ToolQuery(),
                new ToolUserResolver(),new ToolDogResolver(),new ToolMutation()).build().makeExecutableSchema();

        GraphQL graphQL = GraphQL.newGraphQL(schema).build();
        Map variable = Maps.newHashMap();
       variable.put("addUser", MutationVariableHandler.getListVariablesMapFromString(JSON.toJSONString(DemoRepository.inputUsers.values().stream().
               collect(Collectors.toList()), SerializerFeature.DisableCircularReferenceDetect)));
//        ExecutionInput executionInput = ExecutionInput.newExecutionInput().variables(variable).
//                query("query Query{user{id,userName,age,dogs{id,dogName}}}").build();
//        ExecutionInput executionInput = ExecutionInput.newExecutionInput().variables(variable).
//                query("query Query($userId:String){dog(userId:$userId){id,dogName}}").build();

        ExecutionInput executionInput = ExecutionInput.newExecutionInput().variables(variable).
                query("mutation Mutation($addUser:[InputToolUser])" +
                        "{createUser(addUser:$addUser){id,userName,age,dogs{id,dogName}}}").build();

        ExecutionResult result = graphQL.execute(executionInput);
        Map data = result.getData();
        List errors = result.getErrors();
        System.out.println(data);
        System.out.println("errors = "+errors);
    }

 

Console(查询执行的结果大家可以变换注释自己执行):
createUser - [InputToolUser(id=input-user-3, userName=uc, age=30, dogs=[InputToolDog(id=dog-2, dogName=db), InputToolDog(id=dog-3, dogName=dc)]), InputToolUser(id=input-user-2, userName=ub, age=20, dogs=[InputToolDog(id=dog-2, dogName=db)]), InputToolUser(id=input-user-1, userName=ua, age=10, dogs=[InputToolDog(id=dog-1, dogName=da), InputToolDog(id=dog-2, dogName=db)])]
UserResolver , userId = user-1
ToolDogResolver , dogId = dog-1
ToolDogResolver , dogId = dog-2
UserResolver , userId = user-3
ToolDogResolver , dogId = dog-2
ToolDogResolver , dogId = dog-3
UserResolver , userId = user-2
ToolDogResolver , dogId = dog-2
{createUser=[{id=user-1, userName=ua, age=10, dogs=[{id=resolver - dog-1, dogName=da}, {id=resolver - dog-2, dogName=db}]}, {id=user-3, userName=uc, age=30, dogs=[{id=resolver - dog-2, dogName=db}, {id=resolver - dog-3, dogName=dc}]}, {id=user-2, userName=ub, age=20, dogs=[{id=resolver - dog-2, dogName=db}]}]}
errors = []

备注:
DemoRepository:只是Mock了一些数据而已,随便写的。其实这边在运行时还碰到了一个小问题,由于构建的dog对象实例在不同的user对象中都使用了,导致触发了alibaba fastJson的循环调用监测,生成的json中会多一个$ref字段,可以设置SerializerFeature.DisableCircularReferenceDetect来禁止循环引用检测。后面其实稍微改了一下,增加了clone方法,确保实例不同

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
public class DemoRepository {
    //userId ,users
    public static Map users = Maps.newHashMap();
    //userId ,dogs
    public static Map> dogs = Maps.newHashMap();
    //input user
    public static Map inputUsers = Maps.newHashMap();

    public DemoRepository() throws InstantiationException, IllegalAccessException {


        ToolDog da = new ToolDog("dog-1","da");
        ToolDog db = new ToolDog("dog-2","db");
        ToolDog dc = new ToolDog("dog-3","dc");
        List dogList = Lists.newArrayList(da,db,dc);

        ToolUser ua = new ToolUser("user-1","ua",10,Arrays.asList(da,db));
        ToolUser ub = new ToolUser("user-2","ub",20,Arrays.asList(db));
        ToolUser uc = new ToolUser("user-3","uc",30,Arrays.asList(db,dc));

        //dogs = dogList.stream().collect(Collectors.toMap(ToolDog::getId, Function.identity()));
        users = Stream.of(ua,ub,uc).collect(Collectors.toMap(ToolUser::getId, Function.identity()));
        dogs = Stream.of(ua,ub,uc).collect(Collectors.toMap(ToolUser::getId, ToolUser::getDogs));

        List inputToolDogs = convert(InputToolDog.class,dogList.toArray(new ToolDog[3]));
        InputToolUser iua = new InputToolUser("input-user-1","ua",10,Arrays.asList(inputToolDogs.get(0).clone(),inputToolDogs.get(1).clone()));
        InputToolUser iub = new InputToolUser("input-user-2","ub",20,Arrays.asList(inputToolDogs.get(1).clone()));
        InputToolUser iuc = new InputToolUser("input-user-3","uc",30,Arrays.asList(inputToolDogs.get(1).clone(),inputToolDogs.get(2).clone()));

        //dogs = dogList.stream().collect(Collectors.toMap(ToolDog::getId, Function.identity()));
        inputUsers = Stream.of(iua,iub,iuc).collect(Collectors.toMap(InputToolUser::getId, Function.identity()));
    }

    public static  List convert(Class clazz,T... source) throws IllegalAccessException, InstantiationException {
        List result = Lists.newArrayList();
        if(source != null && source.length > 0){
            for(T a : source){
                R r = clazz.newInstance();
                BeanUtils.copyProperties(a, r);
                result.add(r);
            }
        }
        return result;
    }

}

 

MutationVariableHandler.getListVariablesMapFromString:由于这次是List的入参,所以对应的Jackson的Reference也做了相应的变化:

1
2
3
4
5
6
7
8
9
private static TypeReference>> typeRefReadJsonList = new TypeReference>>() {
    };
public static List> getListVariablesMapFromString(String variablesFromRequest) {
        try {
            return jacksonObjectMapper.readValue(variablesFromRequest, typeRefReadJsonList);
        } catch (IOException exception) {
            throw new GraphQLException("Cannot parse variables", exception);
        }
    }

 

总结:怎么说呢,工具类有不少优势,把整体的项目结构与Schema的关系梳理的更加清晰,用实现类的方式把我们从为Field定义DataFetcher解脱了出来,只用根据名称就可以进行互绑;但另一方面,因为GraphQL-java还在不断升级迭代,版本已经远远超过了工具包中自带的基础jar包版本,所以还是慎用吧。。。。,不然以后的迁移和兼容也有的搞了

你可能感兴趣的:(Graphql-X)