GraphQL-Calculator開源:基於指令和表達式實現查詢的動態計算

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"GraphQL查詢出的基礎數據和業務需求往往有些差異,需要研發同學加工後才能渲染展示。而通過硬編碼的方式對數據進行加工處理無法滿足應用快速開發的需求,也與GraphQL配置化的思想相悖。本文將介紹如何通過"},{"type":"link","attrs":{"href":"https:\/\/spec.graphql.org\/draft\/#sec-Language.Directives","title":null,"type":null},"content":[{"type":"text","text":"指令"}]},{"type":"text","text":"和表達式實現GraphQL查詢的計算能力,以減少代碼開發和服務發版上線,提高業務迭代效率。"}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"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":"GraphQL作爲接口描述語言,可對其治理的數據進行便捷的查詢,但真實業務場景除了獲取基礎數據外,往往還需要對數據進行加工處理,概括如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"結果字段加工:對基礎數據進行加工後展示。例如將‘分’單位的數字價格轉爲‘元’單位的價格文案、使用默認值兜底null、將狀態code轉換成對應文案等;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"列表過濾、排序:通過id列表查詢出數據詳情列表之後,往往需要根據詳情信息對結果列表進行過濾排序,例如過濾掉商品列表中在售狀態爲false的商品,將商品按照銷量進行排序;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"參數處理:對參數列表進行過濾,例如過濾掉itemIdList中爲0的itemId;對參數進行轉換,例如將Redis的key前綴拼接到itemId前邊、作爲請求Redis數據源的key;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"數據編排依賴:類似於MySQL中的子查詢,將一個字段的解析結果作爲另一個字段的獲取參數;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"控制流:通過請求變量判斷是否請求指定的字段,GraphQL原生指令@include和@skip只支持bool類型的變量,但真實的業務場景判斷規則更加複雜,往往存在邏輯計算。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"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":"如果將GraphQL僅作爲僵硬的取數工具,就違背了GraphQL配置化的初衷,也忽略了GraphQL的擴展能力。 作爲“接口查詢語言”,GraphQL提供指令作爲查詢執行能力的擴展機制。指令類似於Java註解,可對其進行註解的語言元素進行額外的信息描述。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#a5a5a5","name":"user"}}],"text":"Directives provide a way to describe alternate runtime execution and type validation behavior in a GraphQL document."}]},{"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","marks":[{"type":"color","attrs":{"color":"#a5a5a5","name":"user"}}],"text":"In some cases, you need to provide options to alter GraphQL’s execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor."}]}]},{"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官方指定的能力拓展機制,GraphQL生態的框架對指令有更好的支持,基於指令的能力拓展和框架本身也具有更好的兼容性。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"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":"指令主要是對GraphQL語言元素的信息描述,例如使用@include指令描述是否請求某個字段:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"query userInfo($userId:Int, $needEmail:Boolean!){\n userInfo(userId:$userId){\n userId\n userName\n age\n # 當 $needEmail 爲true時纔會請求、返回email字段\n email @include(if:$needEmail)\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":"link","attrs":{"href":"https:\/\/github.com\/graphql-java\/graphql-java","title":"xxx","type":null},"content":[{"type":"text","text":"GraphQL-java"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"框架集成了"},{"type":"link","attrs":{"href":"https:\/\/spec.graphql.org\/June2018\/","title":"xxx","type":null},"content":[{"type":"text","text":"GraphQL協議"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"原生指令:在執行引擎中判斷每個字段是否帶有 @incldue 指令,有的話則根據起用到的變量信息判斷是否請求該字段,@skip實現同理。"}]},{"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":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"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":2,"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":3,"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-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":",該機制類似於spring中的切面,可在數據處理的各個階段獲取到校驗、查詢各個階段的上下文信息,並可改變執行上下文信息和結果、或中斷查詢的執行。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"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"}},{"type":"strong"}],"text":"基於Instrumentation,"},{"type":"link","attrs":{"href":"https:\/\/github.com\/dugenkui03\/graphql-java-calculator","title":"xxx","type":null},"content":[{"type":"text","text":"GraphQL-calculator"}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"實現了一套具有參數處理、結果字段加工、數據依賴編排和控制流能力的指令集。"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"該指令集可使表達式對上下文數據進行加工轉換,其默認表達式引擎爲"},{"type":"link","attrs":{"href":"https:\/\/github.com\/killme2008\/aviatorscript","title":null,"type":null},"content":[{"type":"text","text":"aviatorscript"}],"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":"heading","attrs":{"align":null,"level":4},"content":[{"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":"通過id列表獲取到數據詳情集合之後,往往需要根據數據詳情對集合進行過濾,或者按照指定規則對集合進行排序。"}]},{"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":"如下查詢,通過商品id列表獲取到商品詳情集合,業務場景需要將庫存爲0、非在售狀態的商品過濾掉,然後按照售價遞增排序。 如果硬編碼形式實現則需要走編碼、調試、部署、上線等步驟,流程長、響應慢。"}]},{"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":null},"content":[{"type":"text","text":"query commodityInfo($ItemIds:[Int]){\n commodity{\n filteredItemList: itemList(itemIds: $ItemIds){\n itemId\n onSale\n name\n salePrice\n stockAmount\n }\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"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":"針對集合過濾、排序的需求,GraphQL-calculator定義了@filter和@srotBy指令對集合進行動態處理:"}]},{"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":null},"content":[{"type":"text","text":"directive @filter(predicate: String!) on FIELD"}]},{"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":"predicate:過濾判斷表達式,會應用在每個集合元素上,結果爲true的元素會被保留,當@filter用在葉子節點上時,表達式變量爲key爲"},{"type":"codeinline","content":[{"type":"text","text":"ele"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"、value爲元素值。"}]}]}]},{"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":null},"content":[{"type":"text","text":"directive @sortBy(comparator: String!, reversed: Boolean = false) on FIELD"}]},{"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":"comparator:用戶比較列表元素順序的比較器,當@filter用在葉子節點上時,表達式變量爲key爲"},{"type":"codeinline","content":[{"type":"text","text":"ele"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"、value爲元素值;"}]}]},{"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":"reversed:是否逆序排序;"}]}]}]},{"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":"使用 @filter 和 @sortBy 指令對商品列表進行過濾並排序的查詢如下:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"query filterUnSaleAndSortCommodity($ItemIds:[Int]){\n commodity{\n filteredItemList: itemList(itemIds: $ItemIds)\n @filter(predicate: \"onSale && stockAmount>0\")\n @sortBy(comparator: \"salePrice\")\n {\n itemId\n onSale\n name\n salePrice\n stockAmount\n }\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"參數處理"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"例如下述查詢,查詢在線用戶詳情信息。調用方傳遞的參數可能存在未登錄用戶參數,即userId爲0。如果數據源接口沒有兼容這種異常情況、則會導致接口意想不到的行爲或結果。此時需要我們對參數進行過濾。"}]},{"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":null},"content":[{"type":"text","text":"query simpleArgumentTransformTest($userIds:[Int]){\n consumer{\n userInfoList(userIds: $userIds){\n userId\n name\n age\n }\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"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":"針對需要對參數進行處理的場景,GraphQL-calculator定義了@argumentTransform對請求參數進行處理,包括參數轉換、列表參數過濾、元素轉換:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"directive @argumentTransform(argumentName:String!, operateType:ParamTransformType!, expression:String!, dependencySources:[String!]) on FIELD\n\n\nenum ParamTransformType{\n MAP # 參數轉換\n FILTER # 列表類型參數過濾\n LIST_MAP # 列表類型參數元素轉換\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":"argumentName:進行轉換的參數名稱,參數必須定義在被註解的字段上;"}]}]},{"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":"operateType:操作類型;"}]}]},{"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":"expression:計算新值、或者對參數進行過濾的表達式;"}]}]},{"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":"dependencySources:表達式依賴的source,如果和參數變量同名則會覆蓋後者,source具體含義見數據編排。"}]}]}]},{"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":"使用@argumentTransform對參數進行過濾的查詢如下:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"query simpleArgumentTransformTest($userIds:[Int]){\n consumer{\n userInfoList(userIds: $userIds)\n @argumentTransform(argumentName: \"userIds\",operateType: FILTER,expression: \"ele!=0\")\n {\n userId\n name\n age\n }\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"數據編排"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"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":"所謂的數據編排就是將一個字段的結果、作爲另外一個字段的輸入。例如從商品列表中抽取出商品的貨主id列表、作爲參數去獲取賣家個人信息詳情。"}]},{"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}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"通過第一次查詢"},{"type":"codeinline","content":[{"type":"text","text":"queryItemInfo"}],"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":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"解析"},{"type":"codeinline","content":[{"type":"text","text":"queryItemInfo"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"查詢結果,獲取商品列表中的賣家id列表;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"使用第2步解析的賣家id列表,獲取賣家個人信息;"}]}]}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"# step 1: 獲取商品詳情列表\n\n\nquery queryItemInfo($itemIds:[Int]){\n commodity{\n itemList(itemIds: $itemIds){\n itemId\n # 商品貨主id\n sellerId\n name\n salePrice\n stockAmount\n }\n }\n}\n\n\n\n\n# step 2:解析queryItemInfo結果,獲取$sellerIds;\n\n\n\n\n\n\n# step 3:獲取賣家詳情列表\n\n\nquery querySellerInfo($sellerIds:[Int]){\n business{\n sellerInfoList(sellerIds: $sellerIds){\n sellerId\n name\n age\n email\n }\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"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":"類似MySQL中的子查詢,如果依賴邏輯合理,任何字段的獲取結果都應當可以作爲請求其他字段的參數。GraphQL-calculator通過@fetchSource對作爲參數的字段進行描述:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"directive @fetchSource(name: String!, sourceConvert:String) on FIELD"}]},{"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":"name:被註解的字段作爲被依賴數據時的source名稱,一個查詢中的source名稱具有唯一性;"}]}]},{"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":"sourceConvert:對source進行轉換的表達式,如果被註解的字段在列表中、則每個元素都會被該表達式轉換。"}]}]}]},{"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":"@fetchSource是進行數據編排的基礎,不管是作爲參數進行流程編排、還是後續講到的數據加工。當要用到其他字段結果作爲參數進行計算時、都是通過@fetchSource將被依賴的數據進行描述、保存爲其他字段指令可獲取的數據。"}]},{"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}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"query simpleOrchestration($itemIds:[Int]){\n commodity{\n itemList(itemIds: $itemIds){\n itemId\n # 將被依賴的數據使用@fetchSource進行描述\n sellerId @fetchSource(name: \"sellerIdList\")\n name\n salePrice\n stockAmount\n }\n }\n\n\n business{\n sellerInfoList(sellerIds: 1)\n # 用@argumentTransform對參數進行轉換\n @argumentTransform(argumentName: \"sellerIds\",operateType: MAP,expression: \"sellerIdList\",dependencySources: [\"sellerIdList\"])\n {\n sellerId\n name\n age\n email\n }\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"結果加工"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"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":"當從某個業務域接口獲取到基礎數據後,往往需要對數據進行加工處理後才能在頁面展示,例如根據用戶id拼接出用戶主頁鏈接,將‘分’單位的數字價格轉爲‘元’單位的價格文案、使用默認值兜底null、將狀態code轉換成對應文案等。"}]},{"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":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"query itemBaseInfo_case01($itemIds:[Int]){\n commodity{\n itemList(itemIds: $itemIds){\n itemId\n name\n \n # 分->元:salePrice\/100\n salePrice\n \n # 1. 自營;2.第三方店鋪:分別使用文案 自營正品、三方好貨 描述\n itemType\n }\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"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":"GraphQL-calculator定義了@map指令用於字段結果的加工計算,該指令可通過參數dependencySources獲取到其他字段結果、實現類似於mysql中join計算的能力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"directive @map(mapper:String!, dependencySources:String) on FIELD"}]},{"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":"mapper:計算被註解字段值的表達式,被註解字段綁定的DataFetcher不會執行;"}]}]},{"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":"dependencySources:表達式依賴的source,sourceName如果和父節點綁定DataFetcher的獲取結果key相同,則計算表達式時會覆父節點中的數據。"}]}]}]},{"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":"使用@map對字段結果進行加工的查詢如下:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"query itemBaseInfo_case01($itemIds:[Int]){\n commodity{\n itemList(itemIds: $itemIds){\n itemId\n name\n # 分->元:salePrice\/100\n salePrice @map(mapper:\"salePrice\/100\") \n # 1. 自營;2.第三方店鋪:分別使用文案 自營正品、三方好貨 描述\n itemTypeDesc: name @map(mapper:\"itemType==1?' 自營正品':'三方好貨'\")\n }\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"控制流"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"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":"GraphQL內置了@skip和@include 來決定是否請求指定字段,其參數爲bool類型。但真實的場景往往存在邏輯計算,無法使用一個簡單的bool類型參數表示是否請求指定字段。"}]},{"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":"如下查詢,期望只有v2版本的客戶端纔可以看到email字段。這種"},{"type":"codeinline","content":[{"type":"text","text":"if"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"控制流的實現放在DataFetcher中硬編碼實現則不夠靈活,難以滿足各種場景的控制需求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"query userInfoQuery($userId:Int){\n consumer{\n userInfo(userId: $userId){\n userId\n age\n name\n # 期望只有v2版本的客戶端可以獲取到該字段\n # 客戶端版本可以作爲請求變量\n email\n }\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"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":"GraphQL-calculator定義了@includeBy指令判斷是否請求指定字段,該指令可理解爲GraphQL內置指令@include的拓展版本,但起判斷邏輯爲表達式、表達式參數爲所有請求變量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"directive @includeBy(predicate: String!, dependencySources:[String!]) on FIELD"}]},{"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":"predicate:判斷是否解析該字段的表達式;"}]}]},{"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":"dependencySources:表達式參數除了請求變量外,還可使用其他source。"}]}]}]},{"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":"使用@includeBy判斷是否請求email的查詢如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"query queryMoreDetail_case01($userId:Int,$clientVersion:String){\n consumer{\n userInfo(\n userId: $userId,\n # 受限於GraphQL原生語法校驗,變量必須被明確的作爲參數使用\n clientVersion: $clientVersion){\n userId\n age\n name\n # 只在v2版本的客戶端中展示\n email @includeBy(predicate: \"clientVersion == 'v2'\")\n }\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":"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":"xxx","type":null},"content":[{"type":"text","text":"https:\/\/spec.graphql.org\/"}]}]}]},{"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-java\/graphql-java","title":"xxx","type":null},"content":[{"type":"text","text":"https:\/\/github.com\/graphql-java\/graphql-java"}]}]}]},{"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\/dugenkui03\/graphql-java-calculator","title":"xxx","type":null},"content":[{"type":"text","text":"https:\/\/github.com\/dugenkui03\/graphql-java-calculator"}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/stepzen.com\/blog\/graphql-directives","title":"xxx","type":null},"content":[{"type":"text","text":"https:\/\/stepzen.com\/blog\/graphql-directives"}]}]}]}]},{"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":"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-java的活躍contributor,主要參與了15、16版本的指令能力升級和語法校驗,GraphQL協議contributor。先後在美團快手從事GraphQL的平臺化開發。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章