GraphQl Calculator计算指令@distinct:使用表达式对列表进行去重

829 阅读2分钟

问题背景

在实际的业务场景中,有时候有对列表元素进行去重的需求,比如对于用户列表、每个年龄段只保留一个用户;对于商品列表、每种品类只保留一个商品。

不同的业务场景往往有不同的去重需求,本文介绍通过表达式和指令、在graphql查询中以配置化的方式实现对列表去重,满足需求的快速开发和即时生效。

解决方案

定义指令

graphql提供了指令机制用于执行能力的动态拓展、指令机制类似于java中的注解。

我们通过定义@distinct指令对列表数据进行去重,该指令只可用在列表类型字段上,其命名和语义参考了 java.util.stream.Stream

directive @distinct(comparator:String) on FIELD
  • comparator:使用该表达式计算元素的唯一key,唯一key相同的元素会被去重,保留有序列表第一个元素。 comparator为可选参数,当未设置该参数时使用System.identityHashCode(object)判断元素是否为相同对象。对列表元素进行去重,当元素为基本类型时、表达式变量为key为ele、value为元素值。

示例查询

根据年龄对用户列表进行去重,每个年龄只保留一个用户。

query distinctUserInfoListByAge($userIds:[Int]){
    consumer{
        distinctUserInfoList: userInfoList(userIds: $userIds)
        @distinct(comparator: "age")
        {
            userId
            name
            age
            email
        }
    }
}

代码实现

该能力可通过graphql-calculator实现,该组件是一款轻量级、高性能的graphql查询计算引擎。使用该组件对原有的GraphQLSchema对象进行包装转换即可,具体步骤如下

继承AsyncDataFetcherInterface

如果项目中使用了异步DataFetcher,则使其则继承AsyncDataFetcherInterface,并在方法实现中返回被包装的DataFetcher和使用的线程池。

public interface AsyncDataFetcherInterface<T> {

    /**
     * Return the dataFetcher which async dataFetcher wrapped.
     *
     * @return the dataFetcher which async dataFetcher wrapped
     */
    DataFetcher<T> getWrappedDataFetcher();

    /**
     * Return the {@code Executor} which async dataFetcher used.
     *
     * @return the {@code Executor} which async dataFetcher used
     */
    Executor getExecutor();
}
创建GraphQLSource

使用配置类Config创建GraphQLSource对象,GraphQLSource包含GraphQLSchemaGraphQL,配置类可指定脚本执行引擎、计算指令引擎使用的线程池和对象转换工具。

Config wrapperConfig = DefaultConfig.newConfig().build();
DefaultGraphQLSourceBuilder graphqlSourceBuilder = new DefaultGraphQLSourceBuilder();
GraphQLSource graphqlSource = graphqlSourceBuilder
        .wrapperConfig(wrapperConfig)
        .originalSchema(GraphQLSourceHolder.getDefaultSchema())
        .preparsedDocumentProvider(new DocumentParseAndValidationCache()).build();

脚本语法使用了aviatorscript,aviator是graphql-java-calculator的默认表达行引擎, 可通过ScriptEvaluatorConfig自定义脚本执行引擎。

执行前校验

使用Validator对计算指令的使用进行语法校验、该校验包含graphql原生语法校验, 建议实现CalculatorDocumentCachedProvider缓存校验结果。

完整示例参考Example