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

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

教程:Lambda 解析器

您可以使用Amazon Lambda和Amazon AppSync 来解析任何 GraphQL 字段。例如,GraphQL 查询可能会向Amazon Relational Database Service (Amazon RDS) 实例发送调用,而 GraphQL 变异可能会写入Amazon Kinesis 流。在本节中,我们将向您展示如何编写基于调用 GraphQL 字段操作来执行业务逻辑的 Lambda 函数。

创建 Lambda 函数

以下示例显示了编写的 Lambda 函数Node.js,该函数作为博客文章应用程序的一部分对博客文章执行不同的操作。

exports.handler = async (event) => { console.log('Received event {}', JSON.stringify(event, 3)) const 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', }, } const 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.') let result switch (event.field) { case 'getPost': return posts[event.arguments.id] case 'allPosts': return Object.values(posts) case 'addPost': // return the arguments back return event.arguments case 'addPostErrorWithData': result = posts[event.arguments.id] // attached additional error information to the post result.errorMessage = 'Error with the mutation, data has changed' result.errorType = 'MUTATION_ERROR' return result case 'relatedPosts': return relatedPosts[event.source.id] default: throw new Error('Unknown field, unable to resolve ' + event.field) } }

此 Lambda 函数按 ID 检索帖子、添加帖子、检索帖子列表以及提取给定帖子的相关帖子。

注意

Lambda 函数使用上的switch语句event.field来确定当前正在解析哪个字段。

使用Amazon管理控制台创建此 Lambda 函数。

为 Lambda 配置数据源

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

选择 “创建数据源”,输入友好的数据源名称(例如,Lambda),然后对于 “数据源类型”,选择 “Amazon Lambda函数”。对于区域,请选择与您的函数相同的区域。对于函数 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 数据源。

要创建使用APPSYNC_JS运行时的解析器,您需要将解析器配置为管道,并创建用于与 Lambda 函数交互的函数。要了解有关使用编写Amazon AppSync函数的更多信息 JavaScript,请参阅解析器和函数的JavaScript 运行时功能

有关 Lambda 映射模板的更多信息,请参阅 Lamb da 的JavaScript 解析器函数参考

首先,创建一个调用您的 LambdaAmazon AppSync 函数的新函数。转到函数,然后选择创建函数。选择您的 Lambda 数据源,命名您的函数lambdaInvoker,并提供以下代码:

import { util } from '@aws-appsync/utils'; export function request(ctx) { const {source, args} = ctx return { operation: 'Invoke', payload: { field: ctx.info.fieldName, arguments: args, source }, }; } export function response(ctx) { return ctx.result; }

此函数在调用 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 控制台的架构编辑器中,在右侧选择 Attach Resolver forgetPost(id:ID!): Post。在 “函数” 部分中,选择Ade function并选择该lambdaInvoker函数。您不需要编辑默认解析程序代码。选择 Create(创建)

您已成功附加了您的首个解析程序。对其余字段重复此操作。

测试您的 GraphQL API

现在您的 Lambda 函数已与 GraphQL 解析程序连接,您可以使用控制台或客户端应用程序运行一些更改和查询。

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

返回错误

任何给定的场分辨率都可能导致错误。使用Amazon AppSync,您可以从以下来源引发错误:

  • 解析程序响应处理程序

  • Lambda 函数

来自解析器响应处理程序

要引发故意错误,可以使用util.error实用方法。它可接收 errorMessageerrorType 以及可选的 data 值作为参数。出现错误后,data 对于将额外的数据返回客户端很有用。在 GraphQL 最终响应中,data 对象将添加到 errors

以下示例说明如何在Post.relatedPosts: [Post]解析程序响应处理程序中使用它。注意我们是如何使用ctx.prev.result的。这是lambdaInvoker响应处理程序返回的结果。

// the Post.relatedPosts response handler export function response(ctx) { util.error("Failed to fetch relatedPosts", "LambdaFailure", ctx.prev.result) return ctx.prev.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 = async (event) => { console.log('Received event {}', JSON.stringify(event, 3)) throw new Error('I fail. always') }

错误是在您的函数响应处理程序中收到的。您可以在 GraphQL 响应中将其发回,方法是将错误附加到响应中util.appendError。为此,请将您的Amazon AppSync 函数响应处理程序更改为:

// the lambdaInvoke response handler export function response(ctx) { const { error, result } = ctx; if (error) { util.appendError(error.message, error.type, result); } return result; }

这将返回与以下内容类似的 GraphQL 响应:

{ "data": { "allPosts": null }, "errors": [ { "path": [ "allPosts" ], "data": null, "errorType": "Lambda:Unhandled", "errorInfo": null, "locations": [ { "line": 2, "column": 3, "sourceName": null } ], "message": "I fail. always" } ] }

高级用例:批处理

此示例中的 Lambda 函数有一个relatedPosts字段,该字段返回给定帖子的相关帖子列表。在示例查询中,来自 Lambda 函数的allPosts字段调用返回了五个帖子。由于我们指定还要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 函数解析单个给定帖子的相关帖子列表,而是可以解析给定批次帖子的相关帖子列表。

为了演示这一点,让我们更新我们的lambdaInvoker函数来处理批处理。更新您的Amazon AppSync 函数代码:

import { util } from '@aws-appsync/utils'; export function request(ctx) { const {source, args} = ctx return { operation: ctx.info.fieldName === 'relatedPosts' ? 'BatchInvoke' : 'Invoke', payload: { field: ctx.info.fieldName, arguments: args, source }, }; } export function response(ctx) { const { error, result } = ctx; if (error) { util.appendError(error.message, error.type, result); } return result; }

现在,当解析完毕BatchInvokeInvoke,代码将fieldName操作从更改为relatedPosts。现在,在 “配置批处理” 部分中启用该功能的批处理。将 “最大批处理大小” 设置为5。选择 Save(保存)。

进行此更改后,在解析时relatedPosts,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 ]

您可以更新您的 Lambda 代码以处理relatedPosts以下方面的批处理:

exports.handler = async (event) => { console.log('Received event {}', JSON.stringify(event, 3)) //throw new Error('I fail. always') const 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', }, } const relatedPosts = { 1: [posts['4']], 2: [posts['3'], posts['5']], 3: [posts['2'], posts['1']], 4: [posts['2'], posts['1']], 5: [], } if (!event.field && event.length){ console.log(`Got a BatchInvoke Request. The payload has ${event.length} items to resolve.`); return event.map(e => relatedPosts[e.source.id]) } console.log('Got an Invoke Request.') let result switch (event.field) { case 'getPost': return posts[event.arguments.id] case 'allPosts': return Object.values(posts) case 'addPost': // return the arguments back return event.arguments case 'addPostErrorWithData': result = posts[event.arguments.id] // attached additional error information to the post result.errorMessage = 'Error with the mutation, data has changed' result.errorType = 'MUTATION_ERROR' return result case 'relatedPosts': return relatedPosts[event.source.id] default: throw new Error('Unknown field, unable to resolve ' + event.field) } }

返回个人错误

前面的示例表明,可以从 Lambda 函数返回单个错误或从映射模板中引发错误。对于批量调用,从 Lambda 函数引发错误会将整个批次标记为失败。对于发生不可恢复的错误的特定场景,例如与数据存储的连接失败,这可能是可以接受的。但是,如果批次中的某些项目成功而其他项目失败,则可以同时返回错误和有效数据。由于Amazon AppSync 需要批处理响应才能列出与批次原始大小相匹配的元素,因此必须定义能够区分有效数据和错误的数据结构。

例如,如果预计 Lambda 函数返回一批相关帖子,则可以选择返回一个对象列表,其中每个对象都有可选数据ErrorMessage 和 Er errorType 段。Response如果出现 errorMessage 字段,则表示出现错误。

以下代码显示了如何更新 Lambda 函数:

exports.handler = async (event) => { console.log('Received event {}', JSON.stringify(event, 3)) // throw new Error('I fail. always') const 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', }, } const relatedPosts = { 1: [posts['4']], 2: [posts['3'], posts['5']], 3: [posts['2'], posts['1']], 4: [posts['2'], posts['1']], 5: [], } if (!event.field && event.length){ console.log(`Got a BatchInvoke Request. The payload has ${event.length} items to resolve.`); return event.map(e => { // return an error for post 2 if (e.source.id === '2') { return { 'data': null, 'errorMessage': 'Error Happened', 'errorType': 'ERROR' } } return {data: relatedPosts[e.source.id]} }) } console.log('Got an Invoke Request.') let result switch (event.field) { case 'getPost': return posts[event.arguments.id] case 'allPosts': return Object.values(posts) case 'addPost': // return the arguments back return event.arguments case 'addPostErrorWithData': result = posts[event.arguments.id] // attached additional error information to the post result.errorMessage = 'Error with the mutation, data has changed' result.errorType = 'MUTATION_ERROR' return result case 'relatedPosts': return relatedPosts[event.source.id] default: throw new Error('Unknown field, unable to resolve ' + event.field) } }

更新lambdaInvokerAmazon AppSync 函数代码:

import { util } from '@aws-appsync/utils'; export function request(ctx) { const {source, args} = ctx return { operation: ctx.info.fieldName === 'relatedPosts' ? 'BatchInvoke' : 'Invoke', payload: { field: ctx.info.fieldName, arguments: args, source }, }; } export function response(ctx) { const { error, result } = ctx; if (error) { util.appendError(error.message, error.type, result); } else if (result.errorMessage) { util.appendError(result.errorMessage, result.errorType, result.data) } else if (ctx.info.fieldName === 'relatedPosts') { return result.data } else { return result } }

响应处理程序现在检查 Lambda 函数在Invoke操作中返回的错误,检查BatchInvoke操作的单个项目返回的错误,最后检查fieldName。对于relatedPosts,函数返回result.data。对于所有其他字段,函数仅返回result。此示例返回与以下内容类似的 GraphQL 响应:

{ "data": { "allPosts": [ { "id": "1", "relatedPosts": [ { "id": "4" } ] }, { "id": "2", "relatedPosts": null }, { "id": "3", "relatedPosts": [ { "id": "2" }, { "id": "1" } ] }, { "id": "4", "relatedPosts": [ { "id": "2" }, { "id": "1" } ] }, { "id": "5", "relatedPosts": [] } ] }, "errors": [ { "path": [ "allPosts", 1, "relatedPosts" ], "data": null, "errorType": "ERROR", "errorInfo": null, "locations": [ { "line": 4, "column": 5, "sourceName": null } ], "message": "Error Happened" } ] }

配置最大批处理大小

默认情况下,使用时BatchInvoke,分批向您的 Lambda 函数Amazon AppSync 发送请求,最多包含五个项目。您可以像上面那样配置 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操作才能使用批处理。