GraphQL Java - Data Fetcher

一、graphql如何获取数据

每个graphql中定义的字段都有一个相关联的graphql.schema.DataFetcher。

有些字段使用自定义的data fetcher代码,用于访问数据库以及从数据库中获取字段信息。而大多数字段仅使用字段名称和普通旧Java对象(POJO)模式从返回的内存对象中获取数据以获取数据。

在其他的graphq 实现当中,Data Fetcher会有时称为resolvers。

假设一个类型定义如下:

    type Query {
        products(match : String) : [Product]   # a list of products
    }

    type Product {
        id : ID
        name : String
        description : String
        cost : Float
        tax : Float
        launchDate(dateFormat : String = "dd, MMM, yyyy') : String
    }

Query.products字段有一个Data Fetcher,Product类型中的每个字段也一样。

Query.products字段的Data Fetcher其可能是一个更加复杂的Data Fetcher,包含从数据库中读取Product对象的操作。它使用一个可选的match参数,进而可以对products结果中的对象进行过滤。

其可能的示例如下:

        DataFetcher productsDataFetcher = new DataFetcher<List<ProductDTO>>() {
            @Override
            public List<ProductDTO> get(DataFetchingEnvironment environment) {
                DatabaseSecurityCtx ctx = environment.getContext();

                List<ProductDTO> products;
                String match = environment.getArgument("match");
                if (match != null) {
                    products = fetchProductsFromDatabaseWithMatching(ctx, match);
                } else {
                    products = fetchAllProductsFromDatabase(ctx);
                }
                return products;
            }
        };

每个DataFetcher都传递一个graphql.schema.DataFetchingEnvironment对象,该对象包含正在获取的字段,提供给字段的参数以及其他信息,例如字段的类型,父类型,查询根对象或查询上下文对象。

请注意上面的数据获取器代码如何使用上下文对象作为特定于应用程序的安全句柄来访问数据库。 这是提供下层调用上下文的常用技术。

一旦我们有了ProductDTO对象列表,我们通常不需要在每个字段上使用专门的数据获取器。 graphql-java附带了一个智能graphql.schema.PropertyDataFetcher,它知道如何根据字段名称遵循POJO模式。 在上面的示例中,有一个名称字段,因此它将尝试查找公共String getName()POJO方法来获取数据。

graphql.schema.PropertyDataFetcher是默认情况下自动与每个字段关联的数据获取器。

但是,仍然可以访问DTO方法中的graphql.schema.DataFetchingEnvironment。 这允许在发送之前调整值。 例如,上面我们有一个launchDate字段,它接受一个可选的dateFormat参数。 我们可以让ProductDTO具有将此日期格式应用于所需格式的逻辑。

    class ProductDTO {

        private ID id;
        private String name;
        private String description;
        private Double cost;
        private Double tax;
        private LocalDateTime launchDate;

        // ...

        public String getName() {
            return name;
        }

        // ...

        public String getLaunchDate(DataFetchingEnvironment environment) {
            String dateFormat = environment.getArgument("dateFormat");
            return yodaTimeFormatter(launchDate,dateFormat);
        }
    }

二、定制化PropertyDataFetcher

如上所述,graphql.schema.PropertyDataFetcher是graphql-java中字段的默认数据获取器,它将使用标准模式来获取对象字段值。

它以Java习惯的方式支持POJO方法和Map方法。 默认情况下,它假定对于graphql字段fieldX,如果支持对象是Map,它可以找到名为fieldX的POJO属性或名为fieldX的映射键。

但是,graphql架构命名和运行时对象命名之间可能存在细微差别。 例如,假设Product.description实际上在运行时支持Java对象中表示为getDesc()。

如果使用SDL指定schema,则可以使用@fetch指令指示此重新映射。

    directive @fetch(from : String!) on FIELD_DEFINITION

    type Product {
        id : ID
        name : String
        description : String @fetch(from:"desc")
        cost : Float
        tax : Float
    }

这将告诉graphql.schema.PropertyDataFetcher在获取名为description的graphql字段的数据时使用属性名desc。

如果使用代码手动构造schema,那么只需要在代码中直接进行指定即可。

        GraphQLFieldDefinition descriptionField = GraphQLFieldDefinition.newFieldDefinition()
                .name("description")
                .type(Scalars.GraphQLString)
                .build();

        GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry()
                .dataFetcher(
                        coordinates("ObjectType", "description"),
                        PropertyDataFetcher.fetching("desc"))
                .build();

三、DataFetchingEnvironment

每个数据获取器都传递一个graphql.schema.DataFetchingEnvironment对象,该对象允许它更多地了解正在获取的内容以及提供的参数。 以下是DataFetchingEnvironment的一些更有趣的部分。

  • T getSource():source对象用于获取字段的信息。 它的对象是父字段的值提取的结果。 在通常情况下,它是内存DTO对象,因此简单的POJO getter将用于字段值。 在更复杂的情况下,可以检查它以了解如何获取当前字段的特定信息。 在执行graphql字段树时,每个返回的字段值都将成为其下子字段的source对象。
  • T getRoot():此特殊对象用于为graphql查询设定种子。 对于顶级字段,根和源是相同的。 根对象在查询期间永远不会更改,并且可能为null(不会使用)。
  • Map<String, Object> getArguments():表示已在字段上提供的参数以及从传递的变量,AST literal和默认参数值中解析的那些参数的值。 可以对特定field使用argument参数来控制它返回的值。
  • T getContext():上下文是在首次执行查询时设置的对象,并且在查询的生命周期内保持不变。 上下文可以是任何值,通常用于为每个data fetcher提供尝试获取字段数据时所需的一些调用上下文。 例如,当前用户凭证或数据库连接参数可以包含在上下文对象中,以便data fetcher进行业务层调用。 作为graphql系统设计者,关键设计决策之一就是如何使用上下文。 有些情况下,设计者使用依赖框架,自动将上下文注入数据提取器,这种情况下不需要使用它。
  • ExecutionStepInfo getExecutionStepInfo():字段类型信息,是执行查询时构建的所有字段类型信息。
  • DataFetchingFieldSelectionSet getSelectionSet():选择集表示在当前正在执行的字段下已“选中”的子字段。
  • ExecutionId getExecutionId():每次查询都有一个唯一的id。可以在日志上使用它来标记每个单独的查询。

3.1 ExecutionStepInfo

graphql查询的执行过程中,会创建了一个包含字段及其类型的调用树。graphql.execution.ExecutionStepInfo.getParentTypeInfo允许你在树上向上遍历,并且查看哪些类型或字段出发当前字段的执行。

graphql.execution.ExecutionStepInfo.getPath方法返回了一个path的表示。这对于打印日志或调试query非常有用。

还有一些辅助方法,可以获取Nonnull或list包装类型内部的原始类型。

3.2 DataFetchingFieldSelectionSet

假设一个如下的查询

    query {
        products {
            # the fields below represent the selection set
            name
            description
            sellingLocations {
                state
            }
        }
    }

products字段的子字段表示该字段的选择集。 了解所要求的子选择可能很有用,因此data fetcher可以优化数据访问查询。 例如,SQL支持的系统可能能够使用字段的选择集仅检索已请求的列。

在上面的示例中,我们要求sellingLocations信息,因此我们可以在我们同时询问产品信息和销售位置信息时进行更有效的数据访问查询。

猜你喜欢

转载自www.cnblogs.com/pku-liuqiang/p/11502414.html