如何優雅地擴展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。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章