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

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

教程:Amazon Lambda解析程序

AmazonAppSync 使你能够使用Amazon Lambda解析任何 GraphQL 字段。例如,GraphQL 查询可能会向 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 检索文章、添加文章、检索文章列表,并提取与已知文章相关的文章。

注意:这些区域有:switch关于的语句event.field允许 Lambda 函数确定当前正在解析的字段。

现在,让我们使用 Lambda 函数使用Amazon使用管理控制台或Amazon CloudFormation选择以下选项:

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 CloudFormation在您的美国西部 2(俄勒冈)区域堆叠在您的Amazon账户:

为 Amazon Lambda 配置数据源

在之后Amazon Lambda函数已创建,导航到AmazonAppSync GraphQL API 在控制台中选择数据源选项卡。

选择New输入数据源的友好名称(例如,"Lambda"),然后选择Amazon Lambda在数据源类型. 选择适当的Amazon区域,然后您应看到 Lambda 函数列于此处。

选择 Lambda 函数之后,您可以创建新角色(为其创建新角色)AmazonAppSync 分配适当的权限)或选择具有以下内联策略的现有角色:

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

您还需要与建立信任关系。Amazon该角色的 AppSync 如下所示:

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

创建 GraphQL 架构

现在数据源已与您的 Lambda 函数连接,让我们创建一个 GraphQL 架构。

从中的模式编辑器AmazonAppSync 控制台,确保您的架构与以下架构匹配:

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] }

配置解析程序

我们已注册了 Amazon Lambda 数据源和一个有效的 GraphQL 架构,现在可以通过解析程序将 GraphQL 字段连接到 Lambda 数据源。

要创建解析程序,我们需要映射模板。要详细了解映射模板,请阅读AmazonAppSync 映射模板概述解析程序映射模板概述.

有关 Lambda 映射模板的更多信息,请参阅 Lambda 解析程序映射模板参考

在此步骤中,您将一个解析程序附加到 Lambda 函数的以下字段:getPost(id:ID!): PostallPosts: [Post]addPost(id: ID!, author: String!, title: String, content: String, url: String): Post!, 和Post.relatedPosts: [Post].

从中的模式编辑器AmazonAppSync 控制台,右侧选择附加解析程序对于 来说为getPost(id:ID!): Post.

选择您的 Amazon 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)

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

在左侧AmazonAppSync 控制台,选择查询选项卡,然后粘贴以下代码:

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(id: "2") { id author title content url ups downs } }

allPosts 查询

query { allPosts { id author title content url ups downs relatedPosts { id title } } }

返回错误

对已知字段进行解析可能导致错误。AmazonAppSync 允许您从以下来源引发错误:

  • 请求或响应映射模板

  • Lambda 函数

从映射模板

您可以通过 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 函数

AmazonAppSync 还可理解 Lambda 函数引发的错误。Lambda 编程模型允许您加注已处理错误消息。如果 Lambda 函数引发错误,AmazonAppSync 无法解析当前字段。响应只会设置为 Lambda 返回的错误消息。目前,您无法通过从 Lambda 函数引发错误将无关数据传递回客户端。

注意:如果 Lambda 函数引发未处理错误消息,AmazonAppSync 使用由设置的错误消息Amazon 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字段,返回已知文章的相关文章列表。在示例查询中,allPostsLambda 函数的字段调用返回了 5 个文章。由于我们已经指定,我们还希望针对每个返回的文章解析 relatedPosts,因此 relatedPosts 字段操作将被调用 5 次。

query { 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 { // 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 } } } }

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

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

我们解决此问题的方式是批处理类似的字段解析程序请求。在本示例中,Lambda 函数并不解析单个给定文章的相关文章列表,而是解析已知的一批文章的相关文章列表。

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

在右侧AmazonAppSync 控制台,选择现有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 函数将接收一组请求并返回一组结果。

具体来说,结果清单必须匹配请求负载的大小和顺序,以便AmazonAppSync 可以相应地匹配结果。

在本批处理示例中,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 编写的以下 Amazon 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 函数引发的错误会将整个批次标记为失败。如果发生了不可恢复的错误,例如与数据存储的连接失败,这样的结果可能是可以接受的。但是,如果批处理中的某些项目成功了,但另一些失败了,可能会同时返回错误和有效数据。由于AmazonAppSync 要求批处理响应列出与批处理的原始大小匹配的元素,您需要定义可以区分有效数据与错误的数据结构。

例如,如果 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" } ] }