本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
在中使用 AWS Lambda 解析器 AWS AppSync
注意
我们现在主要支持 APPSYNC _JS 运行时及其文档。请考虑在此处使用 APPSYNC _JS 运行时及其指南。
你可以 AWS Lambda 和一起使用 AWS AppSync 来解析任何 GraphQL 字段。例如,GraphQL 查询可能会向亚马逊关系数据库服务 (AmazonRDS) 实例发送调用,而 GraphQL 突变可能会写入亚马逊 Kinesis 流。在本节中,我们说明了如何编写 Lambda 函数,以根据 GraphQL 字段操作调用执行业务逻辑。
创建 Lambda 函数
以下示例显示了一个使用 Node.js
编写的 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 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 函数对 event.field
执行 switch
语句以确定当前解析的字段。
使用 AWS 管理控制台或堆栈创建此 Lambda 函数。 AWS CloudFormation 要从 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 函数的亚马逊资源名称 (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
、allPosts: [Post]
、addPost(id:
ID!, author: String!, title: String, content: String, url: String): Post!
和 Post.relatedPosts: [Post]
。
在 AWS AppSync 控制台的架构编辑器中,在右侧选择连接解析器。getPost(id:ID!):
Post
然后,在 “操作” 菜单中,选择 “更新运行时”,然后选择 “设备解析器”(VTL仅限)。
然后,选择您的 Lambda 数据来源。在请求映射模板部分中,选择 Invoke And Forward Arguments (调用并转发参数)。
修改 payload
对象,添加字段名称。您的模板应该类似以下内容:
{ "version": "2017-02-28", "operation": "Invoke", "payload": { "field": "getPost", "arguments": $utils.toJson($context.arguments) } }
在响应映射模板部分中,选择 Return Lambda Result (返回 Lambda 结果)。
在本例中,按原样使用基本模板。它应该类似以下内容:
$utils.toJson($context.result)
选择保存。您已成功附加了您的首个解析器。针对其余字段重复此操作,如下所示:
对于 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 控制台的左侧,选择 Quer ies,然后粘贴以下代码:
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
对于将额外的数据返回客户端很有用。在 GraphQL 最终响应中,data
对象将添加到 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
字段将返回 5 篇文章。由于我们指定还希望为每个返回的文章解析 relatedPosts
,因此,将 relatedPosts
字段操作调用 5 次。
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 函数可能会解析给定批次的文章的相关文章列表,而不是让 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 函数需要返回一批相关的帖子,则可以选择返回一个对象列表,其中每个对象都有可选数据errorMessage、和errorType字段。Response
如果该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
操作才能使用批处理。
您也可以使用以下命令,在直接 Lambda 解析器上启用和配置批处理:
$ 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 解析器,除非他们直接在中将其指定为操作,否则最大批量大小将不起作用。BatchInvoke
VTL同样,如果您执行顶级变更,则不会为变更执行批处理,因为 GraphQL 规范要求按顺序执行并行变更。
例如,采用以下变更:
type Mutation { putItem(input: Item): Item putItems(inputs: [Item]): [Item] }
通过使用第一个变更,我们可以创建 10 个 Items
,如下面的代码片段所示:
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 解析器中将最大批次大小设置为 10,也不会以 10 为一组对 Items
进行批处理,而是根据 GraphQL 规范按顺序执行它们。
要执行实际的批处理变更,您可以按照以下示例使用第二个变更:
mutation MyMutation { putItems([$someItem1, $someItem2, $someItem3,$someItem4, $someItem5, $someItem6, $someItem7, $someItem8, $someItem9, $someItem10]) { id, name } }
有关使用直接 Lambda 解析器进行批处理的更多信息,请参阅直接 Lambda 解析器。