本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
教程:Lambda 解析程序
您可以使用Amazon Lambda和Amazon 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
以确定当前正在解析的字段。
使用创建此 Lambda 函数Amazon管理控制台或Amazon CloudFormation堆栈。从中创建函数 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 CloudFormation堆栈在美国西部(俄勒冈)Amazon您的区域Amazon从这里开始的账户:
配置 Lambda 的数据源
创建 Lambda 函数之后,在Amazon AppSync控制台,然后选择数据源选项卡。
选择创建数据源,输入一个友好的Data source name(例如,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" } ] }
您还必须与建立信任关系Amazon AppSync 针对 IAM 角色,如下所示:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "appsync.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
CreateGraphQL 架构
现在数据源已与您的 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!): Post
、allPosts: [Post]
、addPost(id:
ID!, author: String!, title: String, content: String, url: String): Post!
, 和Post.relatedPosts: [Post]
.
从中的模式编辑器中Amazon AppSync 控制台,在右侧选择附加解析程序为了getPost(id:ID!):
Post
.
选择 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 解析程序连接,您可以使用控制台或客户端应用程序运行一些更改和查询。
在左侧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(id: "2") { id author title content url ups downs } }
allPosts 查询
query { allPosts { id author title content url ups downs relatedPosts { id title } } }
返回错误
对已知字段进行解析可能导致错误。与AmazonAppSync,您可以从以下源引发错误:
-
请求或响应映射模板
-
Lambda 函数
从映射模板
要引发故意错误,你可以使用$utils.error
来自 Velocity 模板语言 (VTL) 模板的帮助程序方法。它可接收 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 函数
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
字段,返回给定文章的相关文章列表。在示例查询中,allPosts
来自 Lambda 函数的字段调用返回了 5 个帖子。因为我们指出我们还想解决relatedPosts
对于每个返回的帖子,relatedPosts
现场操作被调用五次。
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 } } } }
在这个相对简单的查询中,Amazon AppSync 将调用 Lambda 函数 1 + 5 + 25 = 31 次。
这是相当常见的挑战,常被称为 N+1 问题(在本例中 N = 5),会导致延迟增加,以及应用程序费用提升。
我们解决此问题的方式是批处理类似的字段解析程序请求。在本例中,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
每个对象都有可选的对象数据、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
、Amazon AppSync 分批向您的 Lambda 函数发送请求,最多包含五个项目。您可以配置 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 解析器,除非他们直接将其指定为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 } }
在本例中,Items
即使在 Lambda 解析器中将最大批量设置为 10,也不会在 10 组中批处理。相反,它们将根据 GraphQL 规范顺序执行。
要执行实际的批量突变,你可以使用第二个突变遵循以下示例:
mutation MyMutation { putItems([$someItem1, $someItem2, $someItem3,$someItem4, $someItem5, $someItem6, $someItem7, $someItem8, $someItem9, $someItem10]) { id, name } }
有关将批处理与直接 Lambda 解析程序结合使用的更多信息,请参阅直接 Lambda 解析程序.