如何优雅地扩展GraphQL系统能力

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"为什么要扩展GraphQL系统能力"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"GraphQL可将API表示的数据治理在GraphQL的schema中,为API提供一套类型化的完整描述,使得客户端能够根据所需准确地获取相应数据。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在真实业务场景中,除了获取基础数据外,往往还会有一些对数据进行加工转换和编排控制的需求,例如对数值字段取精或者转换成展示文案、对列表字段进行排序过滤去重、根据条件判断是否请求查询中的某些字段、将一个字段的解析结果作为另外一个字段的入参等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"原生的GraphQL查询为获取基础数据提供了便捷,但是计算能力不足导致其结果经常不能满足业务需求,数据往往需要加工转换、甚至经过多次编排查询,才能展示给用户。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"GraphQL的能力扩展机制"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"GraphQL 提供指令作为执行和校验能力的扩展机制"},{"type":"text","text":"。指令的定义包括指令名称、参数列表、可使用位置和是否可在同一位置重复使用等四个元素,用户可以使用指令描述自定义的执行行为或校验规则。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"以内置指令"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@skip"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"为例,该指令定义如下:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"@skip"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","text":"主要是解决指定条件满足时跳过某些字段的获取解析。判断条件结果为指令参数"},{"type":"codeinline","content":[{"type":"text","text":"if"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","text":"。该指令可使用的位置有查询字段、命名片段和内联片段,使用时将指令放置在要生效的元素后即可,示例如下:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"query myQuery($someTest: Boolean!) {\n experimentalField @skip(if: $someTest)\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在实际业务场景中,是否跳过某些字段获取的条件大多情况需要根据请求变量进行计算判断。例如为App渲染数据时,低于指定版本的客户端不用请求某些字段,该条件判断无法通过请求变量只有客户端版本号的原生查询实现。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"GraphQL原生指令只有"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@skip"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" 、"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@include"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" 、"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@deprecated"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" 和"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@specifiedBy"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" ,说明见"},{"type":"link","attrs":{"href":"https:\/\/spec.graphql.org\/draft\/#sec-Type-System.Directives","title":null,"type":null},"content":[{"type":"text","text":"Type-System.Directives"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":",提供的能力有限,不能满足业务计算所需。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"GraphQL系统能力扩展实践"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文以"},{"type":"link","attrs":{"href":"https:\/\/github.com\/graphql-calculator\/graphql-calculator","title":null,"type":null},"content":[{"type":"text","text":"GraphQL Calculator"}]},{"type":"text","text":"为例,介绍对GraphQL系统能力进行扩展的实践。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"开源代码托管地址:"},{"type":"link","attrs":{"href":"https:\/\/github.com\/graphql-calculator\/graphql-calculator","title":null,"type":null},"content":[{"type":"text","text":"https:\/\/github.com\/graphql-calculator\/graphql-calculator"}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"指令分类"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"指令使用位置分为两类:可执行位ExecutableDirectiveLocation和类型系统位TypeSystemDirectiveLocation。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"# ExecutableDirectiveLocation\nQUERY # 查询操作\nMUTATION # 更新操作\nSUBSCRIPTION # 订阅操作\nFIELD # 查询字段\nFRAGMENT_DEFINITION # 命名片段定义\nFRAGMENT_SPREAD # 命名片段\nINLINE_FRAGMENT # 内联片段\nVARIABLE_DEFINITION # 查询变量\n\n\n# TypeSystemDirectiveLocation\nSCALAR # 标量\nOBJECT # 对象\nFIELD_DEFINITION # 字段定义\nARGUMENT_DEFINITION # 参数定义\nINTERFACE # 接口\nUNION # 联合类型\nENUM # 枚举\nENUM_VALUE # 枚举值\nINPUT_OBJECT # 输入对象\nINPUT_FIELD_DEFINITION # 输入字段定义"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"GraphQL规范并不会限制指令只能定义在可执行位或者类型系统位,但是为了明确指令是用在查询上、还是对于类型系统生效,往往只将指令的生效位置限定在其中一种:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"对于可执行位指令,其作用往往跟业务场景相关。例如,每个查询所要跳过的字段都可能不同,因此"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@skip"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"的生效位置为"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":";"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"对于类型系统位指令,主要是对类型系统本身额外信息、执行行为的描述。 例如"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@deprecated"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"说明了一个字段将要被废弃的原因,其定义位置为"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"FIELD_DEFINITION | ENUM_VALUE"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"本文重点讲解查询指令的实现:根据不同的业务场景,对查询进行不同的计算。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"定义指令"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"指令应该服务于特定类型的数据结构和通用的算法处理,而不是特定的业务场景,为特定的业务场景定义指令将使得指令系统变得臃肿、难以维护。"},{"type":"link","attrs":{"href":"https:\/\/github.com\/graphql-calculator\/graphql-calculator","title":null,"type":null},"content":[{"type":"text","text":"GraphQL Calculator"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"参考了常见的编程概念对指令进行定义:"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"字段加工:通过表达式对结果字段进行加工转换;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"数组处理:对结果中的数组字段进行过滤、排序、去重;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"参数转换:对请求参数进行转换,包括加工、过滤、使用其他字段获取结果进行替换;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"数据编排:将指定字段的获取结果作为全局可获取的上下文,为其他字段或参数的加工转换提供可依赖的数据;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"控制流:"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@skip"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"和"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@include"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"拓展版本,通过表达式判断是否请求注解的字段或片断。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"指令的命名会直接影响指令的易用性。"},{"type":"link","attrs":{"href":"https:\/\/github.com\/graphql-calculator\/graphql-calculator","title":null,"type":null},"content":[{"type":"text","text":"GraphQL Calculator"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"指令的命名和语义参考了"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"java.util.stream.Stream"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"和GraphQL规范原生指令,易于理解和使用,例如"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@filter"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"、"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@sort"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"、"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@skipBy"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"执行引擎"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"GraphQL的Java实现提供了"},{"type":"link","attrs":{"href":" https:\/\/www.graphql-java.com\/documentation\/v17\/instrumentation","title":null,"type":null},"content":[{"type":"text","text":"Instrumentation"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"机制,该机制可在查询的各个阶段获取到执行上下文,可对执行信息进行记录、修改。该机制的核心接口有"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Instrumentation"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"、"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"InstrumentationContext"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"和"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"InstrumentationState"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Instrumentation"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Instrumentation"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","text":"主要可获取指令及执行上下文信息,并对数据进行记录、修改。该接口部分方法及说明例举如下:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public interface Instrumentation {\n \/\/ 创建执行上下文对象\n default InstrumentationState createState() {\n return null;\n }\n\n\n \/\/ 根据本次请求上下文和schema创建执行上下文对象\n default InstrumentationState createState(InstrumentationCreateStateParameters parameters) {\n return createState();\n }\n \n \/\/ 在解析查询dsl前调用\n InstrumentationContext beginParse(InstrumentationExecutionParameters parameters);\n\n\n \/\/ 在验证查询对象前调用\n InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters);\n\n\n \/\/ 修改DataFetcher行为\n default DataFetcher> instrumentDataFetcher(DataFetcher> dataFetcher, InstrumentationFieldFetchParameters parameters) {\n return dataFetcher;\n }\n \n \/\/ 在列表字段结束之后可进行的回调动作\n default InstrumentationContext beginFieldListComplete(InstrumentationFieldCompleteParameters parameters) {\n return noOp();\n }\n\n\n \/\/ 对最终的查询结果进行修改\n default CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) {\n return CompletableFuture.completedFuture(executionResult);\n }\n\n\n ......\n\n\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"InstrumentationContext"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"InstrumentationContext"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"为"},{"type":"codeinline","content":[{"type":"text","text":"Instrumentation"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" 部分方法的返回结果,该对象包含"},{"type":"text","text":"两个回调方法,回调动作将在"},{"type":"codeinline","content":[{"type":"text","text":"Instrumentation"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","text":" 的方法对应的执行阶段被调用。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public interface InstrumentationContext {\n\n\n void onDispatched(CompletableFuture result);\n \n void onCompleted(T result, Throwable t);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"InstrumentationState"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"InstrumentationState"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"可保存查询产生的中间数据,经常在记录中间数据或在不同的执行线程间传递数据。为了保证该对象可被多个线程同时读写,其实现一般是线程安全的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"此外,指令的合法使用往往有些前置条件,例如过滤指令不可用在简单对象或基本类型字段上。GraphQL的Java库提供了基于访问者模式实现的"},{"type":"codeinline","content":[{"type":"text","text":"QueryVisitor"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" ,可在其方法中获取到查询的字段、内联片段和片段定义的上下文信息,便于实现自定义的校验规则。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public interface QueryVisitor {\n void visitField(QueryVisitorFieldEnvironment queryVisitorFieldEnvironment);\n \n void visitInlineFragment(QueryVisitorInlineFragmentEnvironment queryVisitorInlineFragmentEnvironment);\n\n\n void visitFragmentSpread(QueryVisitorFragmentSpreadEnvironment queryVisitorFragmentSpreadEnvironment);\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"实现示例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"定义指令"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"定义"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@filter"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"对数组类型字段进行过滤,保留断言表达式"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"predicate"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" 结果为true的元素,"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"predicate"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"参数为所注解数组元素的字段名称与字段值的映射Map。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"directive @filter(predicate: String!) on FIELD"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"实现指令"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"GraphQL执行引擎获取查询结果可分为fetch和complete两个阶段:fetch阶段根据请求参数和上下文获取该节点的原始数据,并分析该节点类型,递归获取其子孙节点的原始数据;complete阶段对应fetch的递归出栈,处于complete阶段的节点及其子孙节点已经全部完成解析和异常处理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"例如,对于获取用户详情列表的查询queryUserList,对应的示意图如下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"query queryUserList($userId:[Int]){\n # userInfoList类型为 [UserInfo]\n userInfoList(userIds: $userId)\n {\n userId\n age\n firstName\n lastName\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/8a\/0d\/8aaa9fcab2d49632yy2ac1b134f7200d.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Instrumentation#beginFieldListComplete"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"中可获取到解析完成的列表字段结果,该方法可过滤不符合断言的元素。继承实现如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic InstrumentationContext beginFieldListComplete(InstrumentationFieldCompleteParameters parameters) {\n return new InstrumentationContext() {\n @Override\n public void onDispatched(CompletableFuture result) {\n \/\/ ignored\n }\n @Override\n public void onCompleted(ExecutionResult result, Throwable t) {\n if (result == null || result.getData() == null || CollectionUtil.arraySize(result.getData()) == 0) {\n return;\n }\n List directives = parameters.getExecutionStepInfo().getField().getSingleField().getDirectives();\n if (directives != null && !directives.isEmpty()) {\n \/\/ 数据过滤\n filterResultByDirective(result, directives);\n }\n }\n };\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"由于在"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Instrumentation#beginFieldListComplete"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" 节点只能获取到数组对象,但不能返回新的对象进行替换,因此需要保证在此获取到的数组类型是可进行过滤操作的,例如"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"java.util.Collection"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"的实现类,不可是不能改变大小的数组类型。可在"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Instrumentation#instrumentDataFetcher"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"中对fetch阶段的结果进行转换,替换为可进行过滤操作的集合类型。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"校验指令使用"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"通过"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"QueryVisitor"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"实现自定义指令的校验规则,以校验"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@filter"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"参数表达式不可为空为例,其实现核心代码如下:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class BasicRule implements QueryVisitor {\n \n @Override\n public void visitField(QueryVisitorFieldEnvironment environment) {\n \/\/ 只在进入该节点的时候进行校验处理,避免重复处理\n if (environment.getTraverserContext().getPhase() != TraverserContext.Phase.ENTER) {\n return;\n }\n \n for (Directive directive : environment.getField().getDirectives()) {\n if (Objects.equals(directive.getName(), FILTER.getName())) {\n GraphQLType innerType = GraphQLTypeUtil.unwrapNonNull(\n environment.getFieldDefinition().getType()\n );\n \/\/ 判断指令指令是否注解在列表类型字段上\n if (!GraphQLTypeUtil.isList(innerType)) {\n String errorMsg = String.format(\"@filter must define on list type, instead {%s}.\", fieldFullPath);\n addValidError(location, errorMsg);\n continue;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"使用指令"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"获取用户详情列表时,通过"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"@filter"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"过滤出年龄大于等于18的用户。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"query filterUserByAge($userId:[Int]){\n userInfoList(userIds: $userId)\n @filter(predicate: \"age>=18\")\n {\n userId\n age\n firstName\n lastName\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"参考资料:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/spec.graphql.org\/","title":null,"type":null},"content":[{"type":"text","text":"https:\/\/spec.graphql.org"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/github.com\/graphql-calculator\/graphql-calculator","title":null,"type":null},"content":[{"type":"text","text":"https:\/\/github.com\/graphql-calculator\/graphql-calculator"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.graphql-java.com\/documentation\/v17\/instrumentation\/","title":null,"type":null},"content":[{"type":"text","text":"https:\/\/www.graphql-java.com\/documentation\/v17\/instrumentation"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"作者介绍:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"杜艮魁,GraphQL Calculator作者,GraphQL Java活跃contributor。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章