教程:Lambda 解析器 - Amazon AppSync
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

教程:Lambda 解析器

注意

我们现在主要支持 APPSYNC_JS 运行时环境及其文档。请考虑使用 APPSYNC_JS 运行时环境和此处的指南。

您可以将 Amazon Lambda 与 Amazon AppSync 一起使用以解析任何 GraphQL 字段。例如,GraphQL 查询可能会向 Amazon Relational Database Service (Amazon RDS) 实例发送调用,而 GraphQL 变更可能会写入到 Amazon 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 语句以确定当前解析的字段。

使用 Amazon 管理控制台或 Amazon CloudFormation 堆栈创建该 Lambda 函数。要从 CloudFormation 堆栈中创建函数,您可以使用以下 Amazon Command Line Interface (Amazon 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

也可以从此处在您的 Amazon 账户的美国西部(俄勒冈州)Amazon 区域中启动 Amazon CloudFormation 堆栈:

为 Lambda 配置数据源

在创建 Lambda 函数后,在 Amazon AppSync 控制台中导航到您的 GraphQL API,然后选择数据源选项卡。

选择创建数据源,输入友好的数据源名称(例如 Lambda),然后为数据源类型选择 Amazon Lambda 函数。对于区域,选择与您的函数相同的区域。(如果您从提供的 CloudFormation 堆栈中创建该函数,则该函数可能位于 US-WEST-2 中。) 对于函数 ARN,选择您的 Lambda 函数的 Amazon 资源名称 (ARN)。

在选择您的 Lambda 函数后,您可以创建新的 Amazon Identity and Access Management (IAM) 角色(Amazon AppSync 为其分配相应的权限),或选择具有以下内联策略的现有角色:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "lambda:InvokeFunction" ], "Resource": "arn:aws:lambda:REGION:ACCOUNTNUMBER:function/LAMBDA_FUNCTION" } ] }

您还必须为 IAM 角色建立与 Amazon AppSync 信任关系,如下所示:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "appsync.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }

创建 GraphQL 架构

数据源现已连接到您的 Lambda 函数,请创建 GraphQL 架构。

从 Amazon 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!): PostallPosts: [Post]addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!Post.relatedPosts: [Post]

从 Amazon 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 解析器连接,您可以使用控制台或客户端应用程序运行一些变更和查询。

在 Amazon 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 Amazon 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 } } }

返回错误

解析任何给定的字段可能会导致错误。在使用 Amazon AppSync 时,您可以从以下源中引发错误:

  • 请求或响应映射模板

  • Lambda 函数

从映射模板

要故意引发错误,您可以使用 Velocity 模板语言 (VTL) 模板中的 $utils.error 帮助程序方法。它可接收 errorMessageerrorType 以及可选的 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].relatedPostsnull,而 errorMessageerrorTypedata 体现在 data.errors[0] 对象中。

从 Lambda 函数

Amazon AppSync 还可以识别 Lambda 函数引发的错误。Lambda 编程模型允许您引发处理的 错误。如果 Lambda 函数引发错误,则 Amazon AppSync 无法解析当前字段。仅在响应中设置从 Lambda 返回的错误消息。目前,您无法通过从 Lambda 函数中引发错误,将任何无关数据传回到客户端。

注意:如果您的 Lambda 函数引发未处理的 错误,Amazon 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 } } } }

在这个相对简单的查询中,Amazon AppSync 将调用 Lambda 函数 1 + 5 + 25 = 31 次。

这是相当常见的挑战,常被称为 N+1 问题(在本例中 N = 5),会导致延迟增加,以及应用程序费用提升。

我们解决此问题的方式是批处理类似的字段解析器请求。在该示例中,Lambda 函数可能会解析给定批次的文章的相关文章列表,而不是让 Lambda 函数解析单个给定文章的相关文章列表。

为了演示此操作,让我们将 Post.relatedPosts: [Post] 解析器转换为启用批处理的解析器。

在 Amazon 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 函数将收到请求列表并返回结果列表。

具体来说,结果列表必须与请求负载条目的大小和顺序匹配,以使 Amazon 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 函数中引发错误会将整个批次标记为失败。对于发生不可恢复错误的特定场景(例如,到数据存储的连接失败),这可能是可以接受的。不过,如果批次中的某些项目成功,而其他项目失败,则可能会同时返回错误和有效的数据。由于 Amazon AppSync 要求批处理响应列出与批次的原始大小匹配的元素,因此,您必须定义一个可以区分有效数据和错误的数据结构。

例如,如果 Lambda 函数预计返回一批相关文章,您可以选择返回 Response 对象列表,其中每个对象具有可选的 dataerrorMessageerrorType 字段。如果出现 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 时,Amazon AppSync 向您的 Lambda 函数批量发送请求(每个批次最多 5 个项目)。您可以配置 Lambda 解析器的最大批次大小。

要配置解析器上的最大批处理大小,请在 Amazon Command Line Interface (Amazon 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 解析器,最大批次大小无效,除非它们在 VTL 中直接将其指定为 BatchInvoke 操作。同样,如果您执行顶级变更,则不会为变更执行批处理,因为 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 解析器