本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
在 中使用 AWS Lambda 解析器 AWS AppSync
注意
我們現在主要支援 APPSYNC_JS 執行期及其文件。請考慮在此處使用 APPSYNC_JS 執行期及其指南。
您可以使用 AWS Lambda 搭配 AWS AppSync 來解析任何 GraphQL 欄位。例如,GraphQL 查詢可能會將呼叫傳送至 Amazon Relational Database Service (Amazon RDS) 執行個體,而 GraphQL 突變可能會寫入 Amazon Kinesis 串流。在本節中,我們將示範如何撰寫 Lambda 函數,根據 GraphQL 欄位操作的叫用執行業務邏輯。
建立 Lambda 函數
下列範例顯示寫入 的 Lambda 函數Node.js
,該函數會在部落格文章應用程式中對部落格文章執行不同的操作。
exports.handler = (event, context, callback) => { console.log("Received event {}", JSON.stringify(event, 3)); var posts = { "1": {"id": "1", "title": "First book", "author": "Author1", "url": "https://amazon.com/", "content": "SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1", "ups": "100", "downs": "10"}, "2": {"id": "2", "title": "Second book", "author": "Author2", "url": "https://amazon.com", "content": "SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT", "ups": "100", "downs": "10"}, "3": {"id": "3", "title": "Third book", "author": "Author3", "url": null, "content": null, "ups": null, "downs": null }, "4": {"id": "4", "title": "Fourth book", "author": "Author4", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4", "ups": "1000", "downs": "0"}, "5": {"id": "5", "title": "Fifth book", "author": "Author5", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT", "ups": "50", "downs": "0"} }; var relatedPosts = { "1": [posts['4']], "2": [posts['3'], posts['5']], "3": [posts['2'], posts['1']], "4": [posts['2'], posts['1']], "5": [] }; console.log("Got an Invoke Request."); switch(event.field) { case "getPost": var id = event.arguments.id; callback(null, posts[id]); break; case "allPosts": var values = []; for(var d in posts){ values.push(posts[d]); } callback(null, values); break; case "addPost": // return the arguments back callback(null, event.arguments); break; case "addPostErrorWithData": var id = event.arguments.id; var result = posts[id]; // attached additional error information to the post result.errorMessage = 'Error with the mutation, data has changed'; result.errorType = 'MUTATION_ERROR'; callback(null, result); break; case "relatedPosts": var id = event.source.id; callback(null, relatedPosts[id]); break; default: callback("Unknown field, unable to resolve" + event.field, null); break; } };
此 Lambda 函數會依 ID 擷取文章、新增文章、擷取文章清單,以及擷取指定文章的相關文章。
注意:Lambda 函數使用 上的switch
陳述式event.field
來判斷目前正在解決的欄位。
使用 AWS 管理主控台或 AWS CloudFormation 堆疊建立此 Lambda 函數。若要從 CloudFormation 堆疊建立函數,您可以使用下列 AWS Command Line Interface (AWS CLI) 命令:
aws cloudformation create-stack --stack-name AppSyncLambdaExample \ --template-url https://s3.us-west-2.amazonaws.com/awsappsync/resources/lambda/LambdaCFTemplate.yaml \ --capabilities CAPABILITY_NAMED_IAM
您也可以從此處在美國西部 (奧勒岡) AWS 區域中啟動 AWS AWS CloudFormation 堆疊:
設定 Lambda 的資料來源
建立 Lambda 函數之後,請在 AWS AppSync 主控台API中導覽至 GraphQL,然後選擇資料來源索引標籤。
選擇建立資料來源 ,輸入易記的資料來源名稱 (例如 Lambda
),然後針對資料來源類型 ,選擇AWS Lambda 函數 。針對區域 ,選擇與函數相同的區域。(如果您從提供的 CloudFormation 堆疊建立函數,則函數可能位於 US-WEST-2。) 針對函數 ARN,選擇 Lambda 函數的 Amazon Resource Name (ARN)。
選擇 Lambda 函數後,您可以建立新的 AWS Identity and Access Management (IAM) 角色 (為其 AWS AppSync 指派適當的許可),或選擇具有下列內嵌政策的現有角色:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "lambda:InvokeFunction" ], "Resource": "arn:aws:lambda:REGION:ACCOUNTNUMBER:function/LAMBDA_FUNCTION" } ] }
您還必須為 AWS AppSync IAM角色與 設定信任關係,如下所示:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "appsync.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
建立 GraphQL 結構描述
現在資料來源已連接至 Lambda 函數,請建立 GraphQL 結構描述。
從 AWS AppSync 主控台的結構描述編輯器中,確定您的結構描述符合下列結構描述:
schema { query: Query mutation: Mutation } type Query { getPost(id:ID!): Post allPosts: [Post] } type Mutation { addPost(id: ID!, author: String!, title: String, content: String, url: String): Post! } type Post { id: ID! author: String! title: String content: String url: String ups: Int downs: Int relatedPosts: [Post] }
設定解析程式
現在您已註冊 Lambda 資料來源和有效的 GraphQL 結構描述,您可以使用解析器將 GraphQL 欄位連接至 Lambda 資料來源。
若要建立解析程式,您需要映射範本。若要進一步了解映射範本,請參閱 Resolver Mapping Template Overview。
如需 Lambda 映射範本的詳細資訊,請參閱 Resolver mapping template reference for Lambda。
在此步驟中,您將解析器連接至下列欄位的 Lambda 函數:getPost(id:ID!): Post
、addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!
、 allPosts: [Post]
和 Post.relatedPosts: [Post]
。
從 AWS AppSync 主控台的結構描述編輯器右側,選擇連接解析程式getPost(id:ID!): Post
。
然後,在動作選單 中,選擇更新執行時間 ,然後選擇 Unit Resolver (VTL 僅限 )。
之後,選擇您的 Lambda 資料來源。在 request mapping template (請求映射範本) 區段中,選擇 Invoke And Forward Arguments (叫用並轉發引數)。
修改 payload
物件以新增欄位名稱。您的範本看起來應該如下所示:
{ "version": "2017-02-28", "operation": "Invoke", "payload": { "field": "getPost", "arguments": $utils.toJson($context.arguments) } }
在 response mapping template (回應映射範本) 區段中,選擇 Return Lambda Result (傳回 Lambda 結果)。
在此案例中,使用原本的基礎範本。它看起來應該如下所示:
$utils.toJson($context.result)
選擇 Save (儲存)。您已成功連接第一個解析程式。對剩餘欄位重複此操作,如下所示:
addPost(id: ID!, author: String!, title: String, content: String, url:
String): Post!
請求映射範本:
{ "version": "2017-02-28", "operation": "Invoke", "payload": { "field": "addPost", "arguments": $utils.toJson($context.arguments) } }
addPost(id: ID!, author: String!, title: String, content: String, url:
String): Post!
回應映射範本:
$utils.toJson($context.result)
allPosts: [Post]
請求映射範本:
{ "version": "2017-02-28", "operation": "Invoke", "payload": { "field": "allPosts" } }
allPosts: [Post]
回應映射範本:
$utils.toJson($context.result)
Post.relatedPosts: [Post]
請求映射範本:
{ "version": "2017-02-28", "operation": "Invoke", "payload": { "field": "relatedPosts", "source": $utils.toJson($context.source) } }
Post.relatedPosts: [Post]
回應映射範本:
$utils.toJson($context.result)
測試您的 GraphQL API
現在,您的 Lambda 函式已經連接到 GraphQL 解析程式,您可以使用主控台或用戶端應用程式執行一些變動和查詢。
在 AWS AppSync 主控台左側,選擇查詢 ,然後貼上下列程式碼:
addPost 突變
mutation addPost { addPost( id: 6 author: "Author6" title: "Sixth book" url: "https://www.amazon.com/" content: "This is the book is a tutorial for using GraphQL with AWS AppSync." ) { id author title content url ups downs } }
getPost 查詢
query getPost { getPost(id: "2") { id author title content url ups downs } }
allPosts 查詢
query allPosts { allPosts { id author title content url ups downs relatedPosts { id title } } }
傳回錯誤
任何指定的欄位解析度都可能導致錯誤。使用 AWS AppSync,您可以從下列來源提出錯誤:
-
請求或回應映射範本
-
Lambda 函數
從映射範本
若要提出刻意錯誤,您可以使用 Velocity 範本語言 (VTL) 範本中的$utils.error
協助程式方法。它使用 errorMessage
、errorType
,及選用的 data
值做為引數。發生錯誤時,data
可將額外的資料傳回給用戶端。data
物件會新增到 GraphQL 最後回應中的 errors
。
以下範例說明如何在 Post.relatedPosts:
[Post]
回應映射範本中使用它:
$utils.error("Failed to fetch relatedPosts", "LambdaFailure", $context.result)
這會產生類似下列的 GraphQL 回應:
{ "data": { "allPosts": [ { "id": "2", "title": "Second book", "relatedPosts": null }, ... ] }, "errors": [ { "path": [ "allPosts", 0, "relatedPosts" ], "errorType": "LambdaFailure", "locations": [ { "line": 5, "column": 5 } ], "message": "Failed to fetch relatedPosts", "data": [ { "id": "2", "title": "Second book" }, { "id": "1", "title": "First book" } ] } ] }
其中的 allPosts[0].relatedPosts
為 null,因為錯誤以及 errorMessage
、errorType
和 data
出現在 data.errors[0]
物件中。
從 Lambda 函式
AWS AppSync 也了解 Lambda 函數擲出的錯誤。Lambda 程式設計模型可讓您提出已處理的錯誤。如果 Lambda 函數擲回錯誤, 會 AWS AppSync 失敗以解析目前的欄位。回應中只會設定 Lambda 傳回的錯誤訊息。目前,您無法透過從 Lambda 函數提出錯誤,將任何多餘的資料傳遞給用戶端。
注意:如果您的 Lambda 函數引發未處理的錯誤, AWS AppSync 會使用 Lambda 設定的錯誤訊息。
下列 Lambda 函式會引發錯誤:
exports.handler = (event, context, callback) => { console.log("Received event {}", JSON.stringify(event, 3)); callback("I fail. Always."); };
這會傳回類似下列的 GraphQL 回應:
{ "data": { "allPosts": [ { "id": "2", "title": "Second book", "relatedPosts": null }, ... ] }, "errors": [ { "path": [ "allPosts", 0, "relatedPosts" ], "errorType": "Lambda:Handled", "locations": [ { "line": 5, "column": 5 } ], "message": "I fail. Always." } ] }
進階使用案例:批次處理
此範例中的 Lambda 函數有一個relatedPosts
欄位,可傳回指定帖子的相關帖子清單。在範例查詢中,來自 Lambda 函數allPosts
的欄位調用會傳回五個帖子。因為我們指定了我們也要relatedPosts
針對每個傳回的帖子解析,所以relatedPosts
欄位操作會叫用五次。
query allPosts { allPosts { // 1 Lambda invocation - yields 5 Posts id author title content url ups downs relatedPosts { // 5 Lambda invocations - each yields 5 posts id title } } }
雖然這在本特定範例中聽起來可能不重要,但此複合式過度擷取可能會快速破壞應用程式。
如果您要在同一查詢中,再次擷取傳回之相關 Posts
的 relatedPosts
,那麼叫用的次數將大幅增加。
query allPosts { allPosts { // 1 Lambda invocation - yields 5 Posts id author title content url ups downs relatedPosts { // 5 Lambda invocations - each yield 5 posts = 5 x 5 Posts id title relatedPosts { // 5 x 5 Lambda invocations - each yield 5 posts = 25 x 5 Posts id title author } } } }
在此相對簡單的查詢中, AWS AppSync 會叫用 Lambda 函數 1 + 5 + 25 = 31 次。
這是一個相當常見的挑戰,通常稱為 N+1 問題 (在此例中,N = 5),它可能會增加應用程式的延遲和成本。
解決此問題的方法之一,是將類似的欄位解析程式請求一起批次處理。在此範例中,它可以改為解析指定批次帖子的相關帖子清單,而不是讓 Lambda 函數解析單一指定帖子的相關帖子清單。
為了示範,讓我們將 Post.relatedPosts: [Post]
解析程式切換為啟用批次的解析程式。
在 AWS AppSync 主控台的右側,選擇現有的Post.relatedPosts: [Post]
解析程式。將請求映射範本變更為以下內容:
{ "version": "2017-02-28", "operation": "BatchInvoke", "payload": { "field": "relatedPosts", "source": $utils.toJson($context.source) } }
只有 operation
欄位從 Invoke
變更為 BatchInvoke
。承載欄位現在會成為範本中指定之任何項目的陣列。在此範例中,Lambda 函數會接收下列項目作為輸入:
[ { "field": "relatedPosts", "source": { "id": 1 } }, { "field": "relatedPosts", "source": { "id": 2 } }, ... ]
在請求映射範本中指定 BatchInvoke
時,Lambda 函數會收到請求清單,並傳回結果清單。
具體而言,結果清單必須符合請求承載項目的大小和順序,以便 AWS AppSync 可以相應地符合結果。
在此批次範例中,Lambda 函數會傳回一批結果,如下所示:
[ [{"id":"2","title":"Second book"}, {"id":"3","title":"Third book"}], // relatedPosts for id=1 [{"id":"3","title":"Third book"}] // relatedPosts for id=2 ]
Node.js 中的下列 Lambda 函數示範 Post.relatedPosts
欄位的此批次處理功能,如下所示:
exports.handler = (event, context, callback) => { console.log("Received event {}", JSON.stringify(event, 3)); var posts = { "1": {"id": "1", "title": "First book", "author": "Author1", "url": "https://amazon.com/", "content": "SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1", "ups": "100", "downs": "10"}, "2": {"id": "2", "title": "Second book", "author": "Author2", "url": "https://amazon.com", "content": "SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT", "ups": "100", "downs": "10"}, "3": {"id": "3", "title": "Third book", "author": "Author3", "url": null, "content": null, "ups": null, "downs": null }, "4": {"id": "4", "title": "Fourth book", "author": "Author4", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4", "ups": "1000", "downs": "0"}, "5": {"id": "5", "title": "Fifth book", "author": "Author5", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT", "ups": "50", "downs": "0"} }; var relatedPosts = { "1": [posts['4']], "2": [posts['3'], posts['5']], "3": [posts['2'], posts['1']], "4": [posts['2'], posts['1']], "5": [] }; console.log("Got a BatchInvoke Request. The payload has %d items to resolve.", event.length); // event is now an array var field = event[0].field; switch(field) { case "relatedPosts": var results = []; // the response MUST contain the same number // of entries as the payload array for (var i=0; i< event.length; i++) { console.log("post {}", JSON.stringify(event[i].source)); results.push(relatedPosts[event[i].source.id]); } console.log("results {}", JSON.stringify(results)); callback(null, results); break; default: callback("Unknown field, unable to resolve" + field, null); break; } };
傳回個別錯誤
上述範例顯示,可以從 Lambda 函數傳回單一錯誤,或從映射範本提出錯誤。對於批次調用,從 Lambda 函數提出錯誤會將整個批次標記為失敗。對於發生無法復原錯誤的特定案例,例如與資料存放區連線失敗,這可能可以接受。不過,如果批次中的某些項目成功,而其他項目失敗,則有可能同時傳回錯誤和有效資料。由於 AWS AppSync 需要批次回應來列出符合批次原始大小的元素,因此您必須定義一個資料結構,以便區分有效資料與錯誤。
例如,如果 Lambda 函數預期會傳回一批相關的文章,您可以選擇傳回Response
物件清單,其中每個物件都有選用的資料 errorMessage、 和 errorType 欄位。如果errorMessage欄位存在,則表示發生錯誤。
下列程式碼顯示如何更新 Lambda 函數:
exports.handler = (event, context, callback) => { console.log("Received event {}", JSON.stringify(event, 3)); var posts = { "1": {"id": "1", "title": "First book", "author": "Author1", "url": "https://amazon.com/", "content": "SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1 SAMPLE TEXT AUTHOR 1", "ups": "100", "downs": "10"}, "2": {"id": "2", "title": "Second book", "author": "Author2", "url": "https://amazon.com", "content": "SAMPLE TEXT AUTHOR 2 SAMPLE TEXT AUTHOR 2 SAMPLE TEXT", "ups": "100", "downs": "10"}, "3": {"id": "3", "title": "Third book", "author": "Author3", "url": null, "content": null, "ups": null, "downs": null }, "4": {"id": "4", "title": "Fourth book", "author": "Author4", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4 SAMPLE TEXT AUTHOR 4", "ups": "1000", "downs": "0"}, "5": {"id": "5", "title": "Fifth book", "author": "Author5", "url": "https://www.amazon.com/", "content": "SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT AUTHOR 5 SAMPLE TEXT", "ups": "50", "downs": "0"} }; var relatedPosts = { "1": [posts['4']], "2": [posts['3'], posts['5']], "3": [posts['2'], posts['1']], "4": [posts['2'], posts['1']], "5": [] }; console.log("Got a BatchInvoke Request. The payload has %d items to resolve.", event.length); // event is now an array var field = event[0].field; switch(field) { case "relatedPosts": var results = []; results.push({ 'data': relatedPosts['1'] }); results.push({ 'data': relatedPosts['2'] }); results.push({ 'data': null, 'errorMessage': 'Error Happened', 'errorType': 'ERROR' }); results.push(null); results.push({ 'data': relatedPosts['3'], 'errorMessage': 'Error Happened with last result', 'errorType': 'ERROR' }); callback(null, results); break; default: callback("Unknown field, unable to resolve" + field, null); break; } };
在此範例中,下列回應映射範本會剖析 Lambda 函數的每個項目,並引發發生的任何錯誤:
#if( $context.result && $context.result.errorMessage ) $utils.error($context.result.errorMessage, $context.result.errorType, $context.result.data) #else $utils.toJson($context.result.data) #end
此範例會傳回類似下列的 GraphQL 回應:
{ "data": { "allPosts": [ { "id": "1", "relatedPostsPartialErrors": [ { "id": "4", "title": "Fourth book" } ] }, { "id": "2", "relatedPostsPartialErrors": [ { "id": "3", "title": "Third book" }, { "id": "5", "title": "Fifth book" } ] }, { "id": "3", "relatedPostsPartialErrors": null }, { "id": "4", "relatedPostsPartialErrors": null }, { "id": "5", "relatedPostsPartialErrors": null } ] }, "errors": [ { "path": [ "allPosts", 2, "relatedPostsPartialErrors" ], "errorType": "ERROR", "locations": [ { "line": 4, "column": 9 } ], "message": "Error Happened" }, { "path": [ "allPosts", 4, "relatedPostsPartialErrors" ], "data": [ { "id": "2", "title": "Second book" }, { "id": "1", "title": "First book" } ], "errorType": "ERROR", "locations": [ { "line": 4, "column": 9 } ], "message": "Error Happened with last result" } ] }
設定批次大小上限
依預設,使用 時BatchInvoke
, 會將請求以最多五個項目的批次 AWS AppSync 傳送至 Lambda 函數。您可以設定 Lambda 解析程式的批次大小上限。
若要在解析器上設定批次大小上限,請在 AWS Command Line Interface () 中使用下列命令AWS CLI:
$ aws appsync create-resolver --api-id <api-id> --type-name Query --field-name relatedPosts \ --request-mapping-template "<template>" --response-mapping-template "<template>" --data-source-name "<lambda-datasource>" \ --max-batch-size X
注意
提供請求映射範本時,您必須使用 BatchInvoke
操作來使用批次處理。
您也可以使用下列命令來啟用和設定 Direct Lambda Resolvers 上的批次處理:
$ aws appsync create-resolver --api-id <api-id> --type-name Query --field-name relatedPosts \ --data-source-name "<lambda-datasource>" \ --max-batch-size X
具有VTL範本的批次大小組態上限
對於具有請求VTL中範本的 Lambda Resolvers,除非其在 中直接指定為BatchInvoke
操作,否則最大批次大小不會生效VTL。同樣地,如果您執行頂層突變,則不會針對突變執行批次處理,因為 GraphQL 規格需要依序執行平行突變。
例如,採取下列突變:
type Mutation { putItem(input: Item): Item putItems(inputs: [Item]): [Item] }
使用第一個突變,我們可以建立 10Items
,如以下程式碼片段所示:
mutation MyMutation { v1: putItem($someItem1) { id, name } v2: putItem($someItem2) { id, name } v3: putItem($someItem3) { id, name } v4: putItem($someItem4) { id, name } v5: putItem($someItem5) { id, name } v6: putItem($someItem6) { id, name } v7: putItem($someItem7) { id, name } v8: putItem($someItem8) { id, name } v9: putItem($someItem9) { id, name } v10: putItem($someItem10) { id, name } }
在此範例中,即使 Lambda Resolver 中的批次大小上限為 10,也不會以 10 的Items
群組批次處理 。相反地,它們會根據 GraphQL 規格依序執行。
若要執行實際批次突變,您可以使用第二個突變遵循下列範例:
mutation MyMutation { putItems([$someItem1, $someItem2, $someItem3,$someItem4, $someItem5, $someItem6, $someItem7, $someItem8, $someItem9, $someItem10]) { id, name } }
如需使用 Direct Lambda 解析程式批次處理的詳細資訊,請參閱 Direct Lambda 解析程式。