在 JavaScript 中使用 AWS CDK - AWS Cloud Development Kit (AWS CDK) v2

这是 AWS CDK v2 开发者指南。较旧的 CDK v1 于 2022 年 6 月 1 日进入维护阶段,并于 2023 年 6 月 1 日终止支持。

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

在 JavaScript 中使用 AWS CDK

JavaScript 是一种 AWS CDK 完全支持且视为稳定的客户端语言。在 JavaScript 中使用 AWS Cloud Development Kit (AWS CDK) 采用了熟悉的工具,包括 Node.js 和节点包管理器 (npm)。如果您愿意,也可以使用 Yarn,不过本指南中的示例使用的是 NPM。通过 NPM 存储库 npmjs.org 来分发构成 AWS 构造库的模块。

您可以使用任何编辑器或 IDE。许多 AWS CDK 开发人员都使用 Visual Studio 代码(或其开源等效版本 VSCodium),该编辑器为 JavaScript 提供很好的支持。

开始使用 JavaScript

要使用 AWS CDK,您必须拥有 AWS 账户和凭证,并已安装 Node.js 和 AWS CDK Toolkit。请参阅 开始使用 AWS CDK

除此之外,JavaScript AWS CDK 应用程序不需要其他先决条件。

注意

第三方语言弃用:语言版本仅在供应商或社区共享其 EOL(生命周期终止)之前才受支持,如有更改,会另行通知。

创建项目

您可以通过在空目录中调用 cdk init 来创建一个新的 AWS CDK 项目。使用 --language 选项并指定 javascript

mkdir my-project cd my-project cdk init app --language javascript

创建项目还会安装 aws-cdk-lib 模块及其依赖项。

cdk init 使用项目文件夹的名称来命名项目的各种元素,包括类、子文件夹和文件。文件夹名称中的连字符都将转换为下划线。但是,名称应遵循 JavaScript 标识符的形式;例如,名称不应以数字开头,也不应包含空格。

使用本地 cdk

在大多数情况下,本指南假设您已全局安装 CDK Toolkit (npm install -g aws-cdk),并且提供的命令示例(例如 cdk synth)也遵循此假设。这种方法可以很容易让 CDK Toolkit 保持为最新版本,而且由于 CDK 采取了严格的向后兼容性方法,所以始终使用最新版本通常风险很小。

有些团队更喜欢在每个项目中指定所有依赖项,包括 CDK Toolkit 等工具。通过这种做法,您可以将此类组件固定到特定版本,并确保团队中的所有开发人员(以及您的 CI/CD 环境)都恰好使用这些版本。这从根源上消除了可能发生的变更,有助于使构建和部署更加一致且可重复。

CDK 在 JavaScript 项目模板的 package.json 中包含了 CDK Toolkit 的依赖项,因此,如果您想使用这种方法,无需对项目进行任何更改。您所需要做的就是使用稍微不同的命令来构建应用程序以及发出 cdk 命令。

操作 使用全局 CDK Toolkit 使用本地 CDK Toolkit
初始化项目 cdk init --language javascript npx aws-cdk init --language javascript
运行 CDK Toolkit 命令 cdk ... npm run cdk ... or npx aws-cdk ...

npx aws-cdk 运行当前项目中本地安装的 CDK Toolkit 版本(如果存在),如有则回退到全局安装。如果不存在全局安装,则 npx 会下载 CDK Toolkit 的临时副本并运行该副本。您可以使用 @ 语法(npx aws-cdk@1.120 --version 打印 1.120.0)来指定 CDK Toolkit 的任意版本。

提示

设置别名,以便您可以在安装本地 CDK Toolkit 时使用 cdk 命令。

macOS/Linux
alias cdk="npx aws-cdk"
Windows
doskey cdk=npx aws-cdk $*

管理 AWS 构造库模块

使用节点软件包管理器 (npm) 来安装和更新供您应用程序使用的 AWS 构造库模块以及您需要的其他软件包。(如果您愿意,可以用 yarn 代替 npm。)npm 还会自动安装这些模块的依赖项。

大多数 AWS CDK 构造都位于名为 aws-cdk-lib 的 CDK 主包中,这是 cdk init 创建的新项目中的默认依赖项。“实验性”AWS 构造库模块(其中更高级别的构造仍在开发中)的命名类似于 aws-cdk-lib/SERVICE-NAME-alpha。服务名称带有 aws- 前缀。如果您不确定某个模块的名称,请在 NPM 上进行搜索

注意

CDK API Reference 也显示了包名称。

例如,以下命令用于安装 AWS CodeStar 的实验模块。

npm install @aws-cdk/aws-codestar-alpha

某些服务的构造库支持位于多个命名空间中。例如,除 aws-route53 之外,还有另外三个 Amazon Route 53 命名空间,分别是 aws-route53-targetsaws-route53-patternsaws-route53resolver

您项目的依赖项在 package.json 中进行维护。您可以编辑此文件,将部分或全部依赖项锁定到特定版本,或者允许在特定条件下将其更新到较新版本。根据您在以下 package.json 中指定的规则,将项目的 NPM 依赖项更新到允许的最新版本:

npm update

在 JavaScript 中,您可以将模块导入到代码中,其名称与使用 NPM 安装模块时使用的名称相同。在应用程序中导入 AWS CDK 类和 AWS 构造库模块时,我们建议采用以下做法。遵循这些准则将有助于让您的代码与其他 AWS CDK 应用程序保持一致并更易于理解。

  • 请使用 require() 而不是 ES6 风格的 import 指令。Node.js 的旧版本不支持 ES6 导入,因此使用旧语法更具兼容性。(如果您真的想使用 ES6 导入,请使用 esm 来确保您的项目与所有支持的 Node.js 版本兼容。)

  • 通常,从 aws-cdk-lib 中导入单个类。

    const { App, Stack } = require('aws-cdk-lib');
  • 如果您需要 aws-cdk-lib 中的许多类,则可以使用 cdk 的命名空间别名,而不是导入单个类。避免同时执行这两项操作。

    const cdk = require('aws-cdk-lib');
  • 通常,使用短命名空间别名来导入 AWS 构造库。

    const { s3 } = require('aws-cdk-lib/aws-s3');

在 JavaScript 中管理依赖项

在 JavaScript CDK 项目中,在项目主目录的 package.json 文件中指定依赖项。核心 AWS CDK 模块位于名为 aws-cdk-lib 的单个 NPM 包中。

当您使用 npm install 来安装包时,NPM 会在 package.json 中记录该包。

如果您愿意,可以用 Yarn 代替 NPM。但是,CDK 不支持 Yarn 的即插即用模式,该模式是 Yarn 2 中的默认模式。将以下内容添加到项目的 .yarnrc.yml 文件以关闭此功能。

nodeLinker: node-modules

CDK 应用程序

以下是 cdk init --language typescript 命令生成的示例 package.json 文件。为 JavaScript 生成的文件类似,只是没有与 TypeScript 相关的条目。

{ "name": "my-package", "version": "0.1.0", "bin": { "my-package": "bin/my-package.js" }, "scripts": { "build": "tsc", "watch": "tsc -w", "test": "jest", "cdk": "cdk" }, "devDependencies": { "@types/jest": "^26.0.10", "@types/node": "10.17.27", "jest": "^26.4.2", "ts-jest": "^26.2.0", "aws-cdk": "2.16.0", "ts-node": "^9.0.0", "typescript": "~3.9.7" }, "dependencies": { "aws-cdk-lib": "2.16.0", "constructs": "^10.0.0", "source-map-support": "^0.5.16" } }

对于可部署的 CDK 应用程序,必须在 package.jsondependencies 部分中指定 aws-cdk-lib。您可以使用脱字符(^)版本号说明符来表示您将接受比指定版本更高的版本,前提是它们位于同一个主版本内。

对于实验性构造,请为 alpha 构造库模块指定确切版本,这些模块的 API 可能会发生更改。请勿使用 ^ 或 ~,因为这些模块的更高版本可能会导致 API 发生更改,从而导致您的应用程序中断。

package.jsondevDependencies 部分中指定测试应用程序所需的库和工具版本(例如,jest 测试框架)。或者使用 ^ 来指定可接受更高版本的兼容版本。

第三方构造库

如果您正在开发构造库,请结合 peerDependenciesdevDependencies 部分来指定其依赖项,如以下示例 package.json 文件所示。

{ "name": "my-package", "version": "0.0.1", "peerDependencies": { "aws-cdk-lib": "^2.14.0", "@aws-cdk/aws-appsync-alpha": "2.10.0-alpha", "constructs": "^10.0.0" }, "devDependencies": { "aws-cdk-lib": "2.14.0", "@aws-cdk/aws-appsync-alpha": "2.10.0-alpha", "constructs": "10.0.0", "jsii": "^1.50.0", "aws-cdk": "^2.14.0" } }

peerDependencies 中,使用脱字符(^)来指定您的库所使用的 aws-cdk-lib 的最低版本。这样可以最大限度地提高您的库与一系列 CDK 版本的兼容性。为 alpha 构造库模块指定确切版本,这些模块的 API 可能会发生更改。使用 peerDependencies 可以确保 node_modules 树中所有 CDK 库副本只有一个副本。

devDependencies 中,指定测试所需的工具和库,也可以使用 ^ 来表示可以接受更高版本的兼容版本。确切指定(不带 ^ 或 ~)您宣称您的库与之兼容的 aws-cdk-lib 和其他 CDK 软件包的最低版本。这种做法可确保您的测试是针对这些版本运行的。这样,如果您无意中使用了仅较新版本中具备的功能,则您的测试可以将其捕捉到。

警告

只有 NPM 7 及更高版本才能自动安装 peerDependencies。如果您使用的是 NPM 6 或更早版本,或者使用的是 Yarn,则必须在 devDependencies 中包含依赖项的依赖项。否则,将不会进行安装,并且您将收到有关对等依赖项未解决的警告。

安装和更新依赖项

使用以下命令来安装项目的依赖项。

NPM
# Install the latest version of everything that matches the ranges in 'package.json' npm install # Install the same exact dependency versions as recorded in 'package-lock.json' npm ci
Yarn
# Install the latest version of everything that matches the ranges in 'package.json' yarn upgrade # Install the same exact dependency versions as recorded in 'yarn.lock' yarn install --frozen-lockfile

要更新已安装的模块,可以使用前面的 npm installyarn upgrade 命令。任一命令都会将 node_modules 中的软件包更新为可满足 package.json 中规则的最新版本。但是,它们不会更新 package.json 本身,您可能需要执行此操作来设置新的最低版本。如果您将包托管在 GitHub 上,则可以配置 Dependabot 版本更新 来自动更新 package.json。或者,可以使用 npm-check-updates

重要

根据设计,当您安装或更新依赖项时,NPM 和 Yarn 会选择可满足 package.json 中指定要求的每个包的最新版本。这些版本始终存在损坏风险(无论是无意还是有意)。更新项目的依赖项后,请彻底进行测试。

JavaScript 中的 AWS CDK 习语

Props

所有 AWS 构造库类都使用三个参数进行实例化:在其中定义构造的 scope(构造树中的父级)、idprops(构造用于配置其创建的 AWS 资源的键/值对捆绑包)。其他类和方法也使用“属性捆绑包”模式作为参数。

使用具有良好 JavaScript 自动完成功能的 IDE 或编辑器将有助于避免属性名称出现拼写错误。如果一个构造需要一个 encryptionKeys 属性,而您将其拼写为 encryptionkeys,那么在实例化构造时,您就没有传递您想要传递的值。如果该属性为必需,则这可能会导致在合成时出现错误;如果该属性为可选,则会无提示忽略该属性。在后一种情况下,您可能会得到一个您想要覆盖的默认行为。在这里要特别小心。

对 AWS 构造库类进行子类化(或重写采用类似 props 参数的方法)时,您可能需要接受其他属性供自己使用。这些值将被父类或重写的方法忽略,因为在该代码中永远不会访问它们,因此通常可以传递收到的所有 props。

AWS CDK 的未来版本可能会碰巧添加一个新属性,其名称与您用于自己属性的名称相同。将收到的值沿继承链向上传递可能会导致意外行为。更安全的做法是传递您收到的 props 的浅副本,同时删除属性或将其设置为 undefined。例如:

super(scope, name, {...props, encryptionKeys: undefined});

或者,为您的属性命名,以便清楚地表明它们属于您的构造。这样,其就不太可能在未来的 AWS CDK 版本中与属性发生冲突。如果有很多属性,请使用一个适当命名的对象来存放它们。

缺失值

对象中的缺失值(例如 props)在 JavaScript 中的值为 undefined。可以使用常用技术来处理这些问题。例如,以下是一个常见的习惯用法,用于访问可能未定义的值的属性:

// a may be undefined, but if it is not, it may have an attribute b // c is undefined if a is undefined, OR if a doesn't have an attribute b let c = a && a.b;

但是,如果 aundefined 之外还可能具有其他“false”值,则最好让测试更加明确。在这里,我们将利用 null 等同于 undefined 的事实,来同时测试这两者:

let c = a == null ? a : a.b;
提示

Node.js 14.0 及更高版本支持新的运算符,这些运算符可以简化未定义值的处理。有关更多信息,请参阅可选链接Null 值合并建议。

将 TypeScript 示例与 JavaScript 结合使用

TypeScript 是我们用于开发 AWS CDK 的语言,也是第一种支持开发应用程序的语言,因此许多可用的 AWS CDK 代码示例都是用 TypeScript 编写的。这些代码示例对于 JavaScript 开发者来说可能是一个很好的资源;您只需要删除代码中特定于 TypeScript 的部分即可。

TypeScript 代码片段通常使用较新的 ECMAScript importexport 关键字从其他模块导入对象,并声明这些对象在当前模块之外可用。Node.js 刚好在其最新版本中开始支持这些关键词。根据您正在使用(或希望支持)的 Node.js 版本,您可以重写导入和导出以使用旧语法。

可以将导入替换为对 require() 函数的调用。

TypeScript
import * as cdk from 'aws-cdk-lib'; import { Bucket, BucketPolicy } from 'aws-cdk-lib/aws-s3';
JavaScript
const cdk = require('aws-cdk-lib'); const { Bucket, BucketPolicy } = require('aws-cdk-lib/aws-s3');

可以将导出分配给 module.exports 对象。

TypeScript
export class Stack1 extends cdk.Stack { // ... } export class Stack2 extends cdk.Stack { // ... }
JavaScript
class Stack1 extends cdk.Stack { // ... } class Stack2 extends cdk.Stack { // ... } module.exports = { Stack1, Stack2 }
注意

使用旧式导入和导出的另一种方法是使用 esm 模块。

对导入和导出进行分类后,就可以深入研究实际的代码了。您可能会遇到这些常用的 TypeScript 功能:

  • 类型注释

  • 接口定义

  • 类型转换/强制转换

  • 访问修饰符

可以为变量、类成员、函数参数和函数返回类型提供类型注释。对于变量、参数和成员,则通过在标识符后面加上冒号和类型来指定类型。函数返回值跟在函数签名后,由冒号和类型组成。

要将带类型注释的代码转换为 JavaScript,请移除冒号和类型。类成员在 JavaScript 中必须具有某些值;如果在 TypeScript 中只有类型注释,则将其设置为 undefined

TypeScript
var encrypted: boolean = true; class myStack extends cdk.Stack { bucket: s3.Bucket; // ... } function makeEnv(account: string, region: string) : object { // ... }
JavaScript
var encrypted = true; class myStack extends cdk.Stack { bucket = undefined; // ... } function makeEnv(account, region) { // ... }

在 TypeScript 中,接口用于为必需和可选属性的捆绑包及其类型命名。然后,您可以使用接口名称作为类型注释。TypeScript 将确保您使用的对象(例如用作函数参数的对象)具有正确类型的必需属性。

interface myFuncProps { code: lambda.Code, handler?: string }

JavaScript 不具备接口功能,因此,一旦您删除了类型注释,就要完全删除接口声明。

当函数或方法返回通用类型(例如 object),但您想将该值视为更具体的子类型以访问不属于更通用类型接口的属性或方法时,TypeScript 允许您使用 as 后跟类型或接口名称来强制转换该值。JavaScript 不支持(或不需要)此功能,因此只需删除 as 和以下标识符即可。一种不太常见的强制转换语法是在括号中使用类型名称 <LikeThis>;也必须删除这些强制转换。

最后,TypeScript 支持类成员的访问修饰符 publicprotectedprivate。JavaScript 中的所有类成员都是公有的。在任何地方看到这些修饰符,将其删除即可。

了解如何识别和删除这些 TypeScript 功能对于将简短的 TypeScript 代码片段改编成 JavaScript 大有帮助。但是以这种方式转换较长的 TypeScript 示例可能不切实际,因为它们更可能会使用其他 TypeScript 功能。对于这些情况,我们建议使用 Sucrase。例如,如果代码使用了未定义的变量,Sucrase 就不会像 tsc 那样报错。如果该变量在语法上是有效的,那么除了少数例外,Sucrase 都可以将其转换为 JavaScript。因此,其对于转换可能无法自行运行的代码片段尤为有价值。

迁移到 TypeScript

随着项目规模越来越大且越来越复杂,许多 JavaScript 开发人员开始转向使用 TypeScript。TypeScript 是 JavaScript 的超集,所有 JavaScript 代码都是有效的 TypeScript 代码,因此无需对代码进行任何更改,同时 TypeScript 也是一种支持的 AWS CDK 语言。类型注释和其他 TypeScript 功能是可选的,可以根据需要将其添加到 AWS CDK 应用程序中。TypeScript 还可让您在新的 JavaScript 功能(例如可选链接和 Null 值合并)最终确定之前抢先体验这些功能,而且无需升级 Node.js。

TypeScript“基于形状”的接口定义了对象中的必需和可选属性捆绑包(及其类型),可以编写代码时捕获常见错误,并让您的 IDE 更容易提供强大的自动完成功能和其他实时编码建议。

在 TypeScript 中编码确实涉及一个额外的步骤:即使用 TypeScript 编译器 tsc 来编译应用程序。对于常见的 AWS CDK 应用程序,编译最多需要几秒钟。

将现有 JavaScript AWS CDK 应用程序迁移到 TypeScript 的最简单方法是使用 cdk init app --language typescript 创建一个新的 TypeScript 项目,然后将源文件(以及任何其他必需文件,例如 AWS Lambda 函数源代码等资产)复制到新项目中。将您的 JavaScript 文件重命名为已 .ts 结尾,然后开始用 TypeScript 进行开发。