前言
继上次使用动态代理构建GraphQL的客户端之后,本次将使用纯粹的代码生成实现(也就是本次会为resolver接口在编译期间生成自己的实现,而不是通过代理来运行时构造实现)。
众所周知,动态代理有大量反射的使用,这可能存在潜在的性能影响(主要是指resolver 接口的动态代理调用),并且Builder模式虽然一定程度上简化了复杂对象的构造,但是对使用客户端的用户来说,过多的参数需要使用setXX方法,这同样增加了使用的难度。
所以这次将摒弃动态代理的方式,将全部代码使用类库生成。当然,这样也有一些缺点,包括
- 由类库实现的代码生成必然会涉及到对服务端响应的结果的反序列化,这些依赖于第三方类库,如何能支持客户端使用者动态替换反序列化类库可能是个问题,目前没有解决,固定使用
Jackson
。 - 虽然已经尽量尝试与原作者讨论,但是,作者觉得,该功能不应该属于类库本身的逻辑,并且可能带来过度设计、维护难的问题。感觉作者不太能理解我们的应用场景。其实我们的目的是简化graphql的使用,并且尽可能保留graphql的功能,以及不带来太多额外的性能损失。虽然类库本身只是Java平台的,但是其总体逻辑应与其他语言平台类型是类似的。这么一来最大的影响是,我们得自己维护类库,并跟进原库上的一些bug fix和features。
- 其实代码生成类库本身并不在意模式的具体含义,其只是将schema转化为document,并在内存中进行转化。所以它不存在上下文的概念,它对每个schema都是单独的处理,但是对于resolver的实现来说,其必须有request和response projection,才能构造GraphQLRequest(
GraphQLRequest graphQLRequest = new GraphQLRequest(request, projection);
)。
有了GraphQLRequest
对象就可以以HTTP POST的方式调用graphql server api了。这与动态代理的调用逻辑是相同的,可以参考上一篇。为了解决这个问题,在这里,我们在源库上引入了CodegenContext
来记录graphql document之间的映射关系。 - 既然把实现都生成了,那么很多配置比如服务端地址,鉴权自然是需要支持的,由此可见,在类库上支持resolver的实现,确实会加重类库的责任。在这里为了支持配置,我们引入了
GrowingIOConfig
来为客户端使用者提供对外的一些配置信息。
实例
resolver接口
假设现有resolver接口AclsQueryResolver
:
public interface AclsQueryResolver {
java.util.List<UserAccessCtrlDto> acls(String resourceType) throws Exception;
}
resolver实现类
现在,我们想要使用类库生成默认的实现类,生成的DefaultAclsQueryResolver
代码如下:
final public class DefaultAclsQueryResolver implements AclsQueryResolver {
private GrowingIOConfig growingIOConfig;
public DefaultAclsQueryResolver(GrowingIOConfig growingIOConfig) {
this.growingIOConfig = growingIOConfig;
}
private DefaultAclsQueryResolver() {
}
@Override
public java.util.List<UserAccessCtrlDto> acls(String resourceType) throws Exception {
AclsQueryRequest request = new AclsQueryRequest();
List<String> keys = Arrays.asList("resourceType");
List<?> values = Arrays.asList(resourceType);
//参数其实是存储在map中的,通过 keys zip values,可以组成map<k, v>。
//在动态代理方案时,我们这里使用反射获取方法参数的名称列表,其实是不够准确的
//因为参数名可能是经过处理的,比如,参数名是int时(Java关键字),生成的实际参数是处理后的Int,但是graphql 的请求参数依然需要int,这可能导致参数名匹配不上,请求失败的问题。
//但是我们在这里,keys不是resolver的参数名列表,而是graphql schema 字段的原始名称:也就是int,后面看模板就清楚了。
Map<String, ?> parameters = JavaCollectionUtils.listToMap(keys, values);
request