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

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

JavaScript 解析器概述

Amazon AppSync 允许您通过对数据源执行操作来响应 GraphQL 请求。对于要运行查询、变更或订阅的每个 GraphQL 字段,必须附加解析器。每个解析器都配置了一个或多个与数据源通信的函数。

解析器是 GraphQL 和数据源之间的连接器。它们讲述Amazon AppSync 如何将传入的 GraphQL 请求转换为后端数据源的指令,以及如何将来自该数据源的响应转换回 GraphQL 响应。使用Amazon AppSync,您可以使用 JavaScript APPSYNC_JS运行时编写解析器函数。有关APPSYNC_JS运行时支持的特性和功能的完整列表,请参阅解析器和函数的JavaScript 运行时功能

JavaScript 管道解析器的剖析

管道解析器由定义请求和响应处理程序以及函数列表的代码组成。每个函数都有一个请求响应处理程序,用于对数据源执行。由于管道解析器将运行委托给函数列表,因此它不链接到任何数据源。单位解析器(用 VTL 编写)和函数是针对数据源执行操作的原语。

管道解析器请求处理器

管道解析器的请求处理程序(之前的步骤)允许您在运行定义的函数之前执行一些准备逻辑。

函数列表

管道解析程序将按顺序运行的函数的列表。管道解析器请求处理程序评估结果以如下方式提供给第一个函数ctx.prev.result。每个函数评估结果都可用作下一个函数ctx.prev.result

管道解析器响应处理器

管道解析器的响应处理程序(后续步骤)允许执行从最后一个函数的输出到预期的 GraphQL 字段类型的某些最终逻辑。函数列表中最后一个函数的输出可在管道解析器响应处理程序中以ctx.prev.result或的形式获得ctx.result

执行流程

假定管道解析器由两个函数组成,下面的列表表示调用解析器时的执行流程:

  1. 管道解析器请求处理器(在步骤之前)

  2. 函数 1:函数请求处理器

  3. 函数 1:数据源调用

  4. 函数 1:函数响应处理器

  5. 函数 2:函数请求处理器

  6. 函数 2:数据源调用

  7. 函数 2:函数响应处理器

  8. 管道解析器响应处理程序(步骤之后)

注意

管道解析程序执行流是单向的,并在解析程序上静态定义。

有用的APPSYNC_JS运行时内置实用程序

在使用管道解析程序时,以下实用工具可为您提供帮助。

ctx.stash

stash 是一个对象,可在每个解析器以及函数请求和响应处理程序中使用。同一个存储实例通过单次解析器运行而存在。这意味着您可以使用存储在请求和响应处理程序之间以及管道解析器中的函数之间传递任意数据。你可以像普通 JavaScript 对象一样测试藏匿处。

ctx.prev.result

ctx.prev.result 表示已在管道中执行的上一个操作的结果。如果之前的操作是管道解析器请求处理器,则ctx.prev.result表示模板评估的输出并可供链中的第一个函数使用。如果上一个操作是第一个函数,则 ctx.prev.result 表示第一个函数的输出,并且可供管道中的第二个函数使用。如果前一个操作是最后一个函数,则ctx.prev.result表示最后一个函数的输出并可供管道解析器响应处理程序使用。

util.error

util.error 实用工具对于引发字段错误很有用。在函数请求或响应处理程序util.error内部使用会立即引发字段错误,这会阻止后续函数的执行。有关更多详细信息和其他util.error签名,请访问解析器和函数的JavaScript运行时功能

util.appenDerror

util.appendError类似于util.error(),主要区别在于它不会中断处理程序的评估。相反,它表示该字段存在错误,但允许对处理程序进行评估并随后返回数据。在函数中使用 util.appendError 将不会中断管道的执行流。有关更多详细信息和其他util.error签名,请访问解析器和函数的JavaScript 运行时功能

使用 APPSYNC_JS 运行时编写解析器

Amazon AppSync 管道解析器包含多达 10 个函数,这些函数按顺序执行以解析查询、突变或订阅。每个函数都与一个数据源相关联,并提供代码来告诉Amazon AppSync 服务如何从该数据源读取或写入数据。此代码定义了请求处理程序和响应处理程序。请求处理程序将上下文对象作为参数,并以 JSON 对象的形式返回请求负载,用于调用您的数据源。响应处理程序从数据源接收载荷以及已执行请求的结果。响应处理程序将此负载转换为适当的格式(可选)并返回。在以下示例中,函数从 Amazon DynamoDB 数据源检索项目:

import { util } from '@aws-appsync/utils' /** * Request a single item with `id` from the attached DynamoDB table datasource * @param ctx the context object holds contextual information about the function invocation. */ export function request(ctx) { const { args: { id } } = ctx return { operation: 'GetItem', key: util.dynamodb.toMapValues({ id }) } } /** * Returns the result directly * @param ctx the context object holds contextual information about the function invocation. */ export function response(ctx) { return ctx.result }

管道解析器还围绕管道中函数的运行有一个请求和一个响应处理程序:其请求处理程序在第一个函数的请求之前运行,其响应处理程序在最后一个函数的响应之后运行。解析器请求处理器可以设置数据以供管道中的函数使用。解析器响应处理程序负责返回映射到 GraphQL 字段输出类型的数据。在下面的示例中,解析器请求处理程序定义allowedGroups;返回的数据应属于这些组之一。解析器的函数可以使用此值来请求数据。解析器的响应处理程序进行最终检查并筛选结果,以确保仅返回属于允许组的项目。

import { util } from '@aws-appsync/utils'; /** * Called before the request function of the first AppSync function in the pipeline. * @param ctx the context object holds contextual information about the function invocation. */ export function request(ctx) { ctx.stash.allowedGroups = ['admin']; ctx.stash.startedAt = util.time.nowISO8601(); return {}; } /** * Called after the response function of the last AppSync function in the pipeline. * @param ctx the context object holds contextual information about the function invocation. */ export function response(ctx) { const result = []; for (const item of ctx.prev.result) { if (ctx.stash.allowedGroups.indexOf(item.group) > -1) result.push(item); } return result; }

Amazon AppSync 函数使您能够编写可在架构中的多个解析器中重复使用的通用逻辑。例如,您可以调Amazon AppSync 用一个函数QUERY_ITEMS,负责查询来自 Amazon DynamoDB 数据源的项目。对于您想要查询项目的解析器,只需将该函数添加到解析器的管道中并提供要使用的查询索引即可。不必重新实现逻辑。

编写代码

假设您想在名为的字段上附加管道解析器getPost(id:ID!),该字段使用以下 GraphQL 查询从 Amazon DynamoDB 数据源返回Post类型:

getPost(id:1){ id title content }

首先,Query.getPost用下面的代码附加一个简单的解析器。这是简单解析器代码的示例。请求处理程序中没有定义任何逻辑,响应处理程序只是返回最后一个函数的结果。

/** * Invoked **before** the request handler of the first AppSync function in the pipeline. * The resolver `request` handler allows to perform some preparation logic * before executing the defined functions in your pipeline. * @param ctx the context object holds contextual information about the function invocation. */ export function request(ctx) { return {} } /** * Invoked **after** the response handler of the last AppSync function in the pipeline. * The resolver `response` handler allows to perform some final evaluation logic * from the output of the last function to the expected GraphQL field type. * @param ctx the context object holds contextual information about the function invocation. */ export function response(ctx) { return ctx.prev.result }

接下来定义从您的数据源检索帖子的函数GET_ITEM

import { util } from '@aws-appsync/utils' /** * Request a single item from the attached DynamoDB table datasource * @param ctx the context object holds contextual information about the function invocation. */ export function request(ctx) { const { id } = ctx.args; return { operation: 'GetItem', key: util.dynamodb.toMapValues({ id }) }; } /** * Returns the result * @param ctx the context object holds contextual information about the function invocation. */ export function response(ctx) { const { error, result } = ctx; if (error) { return util.appendError(error.message, error.type, result); } return ctx.result; }

如果在请求期间出现错误,则该函数的响应处理程序会附加一个错误,该错误将在 GraphQL 响应中返回给调用客户端。将该GET_ITEM函数添加到您的解析器函数列表中。当您执行查询时,该GET_ITEM函数的请求处理程序使用id作为密钥创建DynamoDBGetItem请求。

{ "operation" : "GetItem", "key" : { "id" : { "S" : "1" } } }

Amazon AppSync使用请求从Amazon DynamoDB 获取数据。数据返回后,由GET_ITEM函数的响应处理程序处理,该处理程序检查是否有错误,然后返回结果。

{ "result" : { "id": 1, "title": "hello world", "content": "<long story>" } }

最后,解析器的响应处理程序直接返回结果。

处理错误

如果在请求期间您的函数中出现错误,则该错误将在您的函数响应处理程序中显示ctx.error。您可以使用该util.appendError实用程序将错误附加到 GraphQL 响应中。您可以使用存储区将错误提供给管道中的其他函数。请参见以下示例:

/** * Returns the result * @param ctx the context object holds contextual information about the function invocation. */ export function response(ctx) { const { error, result } = ctx; if (error) { if (!ctx.stash.errors) ctx.stash.errors = [] ctx.stash.errors.push(ctx.error) return util.appendError(error.message, error.type, result); } return ctx.result; }

实用程序

Amazon AppSync提供了两个库来帮助使用APPSYNC_JS运行时开发解析器:

  • @aws-appsync/eslint-plugin-在开发过程中快速发现并修复问题。

  • @aws-appsync/utils-在代码编辑器中提供类型验证和自动补全功能。

配置 eslint 插件

ESLint 是一种静态分析您的代码以快速发现问题的工具。您可以将 ESLint 作为持续集成管道的一部分运行。 @aws-appsync/eslint-plugin是一个 ESLint 插件,在利用APPSYNC_JS运行时时会捕获代码中的无效语法。该插件允许您在开发期间快速获得有关代码的反馈,而不必将更改推送到云端。

@aws-appsync/eslint-plugin提供了两个可以在开发期间使用的规则集。

“插件:@aws-appsync/base” 配置了一组你可以在项目中利用的基本规则:

Rule 描述
非异步 不支持异步流程和承诺。
不用等待 不支持异步流程和承诺。
不上课 不支持课程。
不适合 for不支持(支持for-infor-of除外)
不继续 不支持 continue
没有发电机 不支持生成器。
不收益 不支持 yield
无标签 不支持标签。
不这个 this不支持关键字的关键字。
不试一试 不支持 try/catch 结构。
没时间 虽然不支持循环。
no-disallowed-unary-operators ++--、和~不允许使用一元运算符。
no-disallowed-binary-operators 不允许使用以下运算符:
  • &

  • |

  • ^

  • <<

  • >>

  • >>>

  • instanceof

  • <<=

  • >>=

  • >>>=

  • |=

  • ^=

  • &=

没有承诺 不支持异步流程和承诺。

“插件:@aws-appsync/recompredive” 提供了一些其他规则,但也要求您向项目添加 TypeScript 配置。

Rule 描述
不递归 不允许递归函数调用。
no-disallowed-methods 某些方法是不允许的。有关支持的全套内置函数,请参阅参考资料
no-function-passing 不允许将函数作为函数参数传递给函数。
no-function-reassign 无法重新分配函数。
no-function-return 函数不能是函数的返回值。

要将插件添加到您的项目中,请按照 ESLint 入门中的安装和使用步骤进行操作。然后,使用您的项目包管理器(例如 npm、yarn 或 pnpm)在您的项目中安装该插件

$ npm install @aws-appsync/eslint-plugin

在您的.eslintrc.{js,yml,json}文件中,向该extends属性添加 “插件:@aws-appsync/base”“插件:@aws-appsync/推荐”。以下代码段是以下内容的基本示例.eslintrc配置 JavaScript:

{ "extends": ["plugin:@aws-appsync/base"] }

要使用 “插件:@aws-appsync/推荐” 规则集,请安装所需的依赖项:

$ npm install -D @typescript-eslint/parser

然后,创建一个.eslintrc.js文件:

module.exports = { "env": { "es2021": true, "node": true }, "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", *"plugin:@aws-appsync/recommended"* ], "overrides": [ ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": "latest", "sourceType": "module", "project": "./tsconfig.json" // your project tsconfig file }, "plugins": [ "@typescript-eslint" ], "rules": { } }

测试

在将代码保存到解析器或函数之前,您可以使用EvaluateCode API 命令使用模拟数据远程测试解析器和函数处理程序。要开始使用该命令,请确保您已将权appsync:evaluateMappingTemplate限添加到策略中。例如:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "appsync:evaluateCode", "Resource": "arn:aws:appsync:<region>:<account>:*" } ] }

您可以使用 AmazonCLIAmazonSDK 来利用该命令。例如,要使用 CLI 测试您的代码,只需指向您的文件,提供上下文并指定要评估的处理程序即可:

aws appsync evaluate-code \ --code file://code.js \ --function request \ --context file://context.json \ --runtime name=APPSYNC_JS,runtimeVersion=1.0.0

响应包含一个evaluationResult包含您的处理程序返回的有效负载。它还包含一个logs对象,用于保存您的处理程序在评估期间生成的日志列表。这样可以轻松调试代码执行情况并查看有关评估的信息以帮助进行故障排除。例如:

{ "evaluationResult": "{\"operation\":\"PutItem\",\"key\":{\"id\":{\"S\":\"record-id\"}},\"attributeValues\":{\"owner\":{\"S\":\"John doe\"},\"expectedVersion\":{\"N\":2},\"authorId\":{\"S\":\"Sammy Davis\"}}}", "logs": [ "INFO - code.js:5:3: \"current id\" \"record-id\"", "INFO - code.js:9:3: \"request evaluated\"" ] }

评估结果可以解析为 JSON,它给出:

{ "operation": "PutItem", "key": { "id": { "S": "record-id" } }, "attributeValues": { "owner": { "S": "John doe" }, "expectedVersion": { "N": 2 }, "authorId": { "S": "Sammy Davis" } } }

使用 SDK,您可以轻松整合测试套件中的测试来验证模板的行为。我们这里的示例使用了 Jest 测试框架,但是任何测试套件都可以使用。以下代码段显示了假设的验证运行。请注意,我们希望评估响应是有效的 JSON,因此我们使用JSON.parse从字符串响应中检索 JSON:

const AWS = require('aws-sdk') const fs = require('fs') const client = new AWS.AppSync({ region: 'us-east-2' }) const runtime = {name:'APPSYNC_JS',runtimeVersion:'1.0.0') test('request correctly calls DynamoDB', async () => { const code = fs.readFileSync('./code.js', 'utf8') const context = fs.readFileSync('./context.json', 'utf8') const contextJSON = JSON.parse(context) const response = await client.evaluateCode({ code, context, runtime, function: 'request' }).promise() const result = JSON.parse(response.evaluationResult) expect(result.key.id.S).toBeDefined() expect(result.attributeValues.firstname.S).toEqual(contextJSON.arguments.firstname) })

这会生成以下结果:

Ran all test suites. > jest PASS ./index.test.js ✓ request correctly calls DynamoDB (543 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 totalTime: 1.511 s, estimated 2 s

从 VTL 迁移到 JavaScript

Amazon AppSync 允许您使用 VTL 或为解析器和函数编写业务逻辑 JavaScript。使用这两种语言,您都可以编写逻辑来指导Amazon AppSync 服务如何与您的数据源进行交互。使用 VTL,您可以编写映射模板,这些模板必须计算为有效的 JSON 编码字符串。使用 JavaScript,您可以编写返回对象的请求和响应处理程序。你不返回 JSON 编码的字符串。

例如,使用以下 VTL 映射模板来获取Amazon DynamoDB 项目:

{ "operation": "GetItem", "key": { "id": $util.dynamodb.toDynamoDBJson($ctx.args.id), } }

该实用程序$util.dynamodb.toDynamoDBJson返回 JSON 编码的字符串。如果设置$ctx.args.id<id>,则模板的计算结果为有效的 JSON 编码字符串:

{ "operation": "GetItem", "key": { "id": {"S": "<id>"}, } }

使用时 JavaScript,您无需在代码中打印出原始的 JSON 编码字符串,toDynamoDBJson也不需要使用类似的实用程序。上面映射模板的等效示例是:

import { util } from '@aws-appsync/utils'; export function request(ctx) { return { operation: 'GetItem', key: {id: util.dynamodb.toDynamoDB(ctx.args.id)} }; }

另一种方法是使用util.dynamodb.toMapValues,这是处理值对象的推荐方法:

import { util } from '@aws-appsync/utils'; export function request(ctx) { return { operation: 'GetItem', key: util.dynamodb.toMapValues({ id: ctx.args.id }), }; }

其计算结果为:

{ "operation": "GetItem", "key": { "id": { "S": "<id>" } } }

再举一个例子,以下面的映射模板将项目放入 Amazon DynamoDB 数据源:

{ "operation" : "PutItem", "key" : { "id": $util.dynamodb.toDynamoDBJson($util.autoId()), }, "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args) }

评估时,此映射模板字符串必须生成有效的 JSON 编码字符串。使用时 JavaScript,直接返回请求对象:

import { util } from '@aws-appsync/utils'; export function request(ctx) { const { id = util.autoId(), ...values } = ctx.args; return { operation: 'PutItem', key: util.dynamodb.toMapValues({ id }), attributeValues: util.dynamodb.toMapValues(values), }; }

其计算结果为:

{ "operation": "PutItem", "key": { "id": { "S": "2bff3f05-ff8c-4ed8-92b4-767e29fc4e63" } }, "attributeValues": { "firstname": { "S": "Shaggy" }, "age": { "N": 4 } } }

在直接访问数据源和通过 Lambda 数据源代理之间进行选择

使用Amazon AppSync 和APPSYNC_JS运行时,您可以编写自己的代码,通过使用Amazon AppSync 函数访问数据源来实现自定义业务逻辑。这使您可以轻松地直接与 Amazon DynamoDB、Aurora Serverless、 OpenSearch 服务、HTTP API 和其他Amazon服务等数据源进行交互,而无需部署额外的计算服务或基础设施。Amazon AppSync 还可以通过配置 Lambda 数据源轻松与Amazon Lambda函数进行交互。Lambda 数据源允许您使用其全套功能运行复杂Amazon Lambda的业务逻辑来解析 GraphQL 请求。在大多数情况下,直接连接到其目标数据源的Amazon AppSync 函数将提供您需要的所有功能。在您需要实现APPSYNC_JS运行时不支持的复杂业务逻辑的情况下,您可以使用 Lambda 数据源作为代理来与目标数据源进行交互。

直接数据源集成 用作代理的 Lambda 数据源
使用案例 Amazon AppSync functions interact directly with API data sources. Amazon AppSync functions call Lambdas that interact with API data sources.
Runtime APPSYNC_JS (JavaScript) 支持的任何 Lambda 运行时
Maximum size of code 每个Amazon AppSync函数 32,000 个字符 每个 Lambda 50 MB(已压缩,可直接上传)
External modules 有限——仅支持 APPSYNC_JS 的功能
Call any Amazon service 是-使用Amazon AppSync HTTP 数据源 是-使用Amazon SDK
Access to the request header
Network access
File system access
Logging and metrics
Build and test entirely within AppSync
Cold start 否-使用预置的并发性
Auto-scaling 是的——透明地Amazon AppSync 是-与 Lambda 中的配置相同
Pricing 无额外费用 按照 Lambda 使用量收费

Amazon AppSync 直接与目标数据源集成的函数,极其适合以下使用案例等:

  • 与Amazon DynamoDB、Aurora 无服务器和 OpenSearch 服务互动

  • 与 HTTP API 交互并传递传入标头

  • 使用 HTTP 数据源与Amazon服务交互(使用提供的数据源角色Amazon AppSync自动签署请求)

  • 在访问数据源之前实施访问控制

  • 在完成请求之前对检索到的数据进行过滤

  • 通过在解析器管道中顺序执行Amazon AppSync函数来实现简单的编排

  • 控制查询和突变中的缓存和订阅连接。

Amazon AppSync 使用 Lambda数据源作为代理的函数,极其适合以下使用案例等:

  • 使用除Veloooococ JavaScript ococopococococy模板

  • 调整和控制 CPU 或内存以优化性能

  • 导入第三方库或在中需要不支持的功能APPSYNC_JS

  • 发出多个网络请求和/或获取文件系统访问权限以完成查询

  • 使用批处理配置对请求进行批处理