本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
第 2 层结构
AWS CDK 开源存储库aws-cdk-lib
为的主软件包库大致分为每项 AWS 服务一个包,尽管情况并非总是如此。如前所述,L1 构造是在构建过程中自动生成的,那么当你查看存储库内部时,你看到的全部代码是什么? 这些是 L2 构造,它们是 L1 构造的抽象。
这些包还包含一组 TypeScript 类型、枚举和接口,以及添加更多功能的辅助类,但是这些项目都提供了 L2 构造。所有 L2 构造在实例化时都会在其构造函数中调用其相应的 L1 构造,并且可以从第 2 层访问所创建的 L1 构造,如下所示:
const role = new Bucket(this, "amzn-s3-demo-bucket", {/*...BucketProps*/}); const cfnBucket = role.node.defaultChild;
L2 构造采用默认属性、便利方法和其他语法糖并将其应用于 L1 构造。这消除了直接在中配置资源所必需的大部分重复和冗长。 CloudFormation
所有 L2 构造都是在引擎盖下构建相应的 L1 结构。但是,L2 构造实际上并没有扩展 L1 构造。L1 和 L2 构造都继承一个名为 Construct 的特殊类。在版本 1 中, AWS CDK 该Construct
类内置在开发套件中,但在版本 2 中,它是一个单独的独立软件包Construct
类都是 L1、L2 或 L3 构造。L2 构造直接扩展该类,而 L1 构造扩展名为的类CfnResource
,如下表所示。
L1 继承树 |
L2 继承树 |
---|---|
L1 构造 → 课堂 CfnResource →→ 抽象类 CfnRefElement →→ 抽象类 CfnElement |
L2 构造 → 类构造 |
如果 L1 和 L2 构造都继承了该Construct
类,为什么 L2 构造不直接扩展 L1? 好吧,类和第 1 层之间的Construct
类将 L1 构造锁定在原处,作为 CloudFormation 资源的镜像。它们包含抽象方法(下游类必须包含的方法)_toCloudFormation
,这会迫使构造直接输出 CloudFormation语法。L2 构造会跳过这些类并直接扩展该Construct
类。这使他们能够灵活地通过在构造函数中单独构建 L1 构造来抽象 L1 构造所需的大部分代码。
上一节 side-by-side比较了 CloudFormation模板中的 S3 存储桶和呈现为 L1 构造的相同 S3 存储桶。该比较表明,属性和语法几乎相同,与该构造相比,L1 构造仅节省了三四行。 CloudFormation 现在,让我们比较同一 S3 存储桶的 L1 构造和 L2 构造:
用于 S3 存储桶的 L1 构造 |
用于 S3 存储桶的 L2 结构 |
---|---|
|
|
如您所见,L2 构造的大小不到 L1 构造的一半。L2 构造使用多种技术来完成这种整合。其中一些技术适用于单个 L2 构造,但其他技术可以在多个构造中重复使用,因此它们被分成自己的类以实现可重用性。L2 以多种方式构造合并 CloudFormation语法,如以下各节所述。
默认属性
整合资源置备代码的最简单方法是将最常见的属性设置转换为默认值。可以访问强大的编程语言, CloudFormation 但没有,因此这些默认值本质上通常是有条件的。 AWS CDK 有时,可以从 AWS CDK 代码中删除几行 CloudFormation 配置,因为这些设置可以从传递给构造的其他属性的值中推断出来。
结构、类型和接口
尽管有多种编程语言可用,但它是用原生语言编写的 TypeScript,因此该语言的类型系统用于定义构成 L2 构造的类型。 AWS CDK 深入研究该类型系统超出了本指南的范围;有关详细信息,请参阅TypeScript文档type
描述了特定变量所包含的数据类型。这可能是基本数据(例如 a)string
,也可以是更复杂的数据,例如object
。A TypeScript interface
是表达 TypeScript对象类型的另一种方式,a struct
是接口的另一种名称。
TypeScript 不使用术语 struct,但是如果你查看AWS CDK API参考文献,你会发现一个结构实际上只是代码中的另一个 TypeScript 接口。《API参考资料》还将某些接口称为接口。如果结构和接口是一回事,为什么 AWS CDK 文档要区分它们?
所 AWS CDK 谓的结构是表示 L2 构造所用任何对象的接口。这包括在实例化期间传递给 L2 构造的属性参数的对象类型,例如 S3 存储桶构造和 TableProps
DynamoDB 表构造的对象类型,以及中使用的其他 TypeScript 接口。BucketProps
AWS CDK简而言之,如果它是中的 TypeScript 接口, AWS CDK 并且其名称没有以字母为前缀I
,则 AWS CDK 称其为结构。
相反, AWS CDK 使用术语接口来表示需要将普通对象视为特定构造或辅助类的正确表示的基本元素。也就是说,接口描述了 L2 构造的公共属性必须是什么。所有 AWS CDK 接口名称都是以字母为前缀的现有构造或辅助类的名称。I
所有 L2 构造都扩展了该Construct
类,但它们也实现了相应的接口。因此,L2 构造Bucket
实现了IBucket
接口。
静态方法
L2 构造的每个实例也是其相应接口的实例,但事实并非如此。在浏览结构以查看需要哪些数据类型时,这一点很重要。如果结构具有名为的属性bucket
,该属性需要数据类型IBucket
,则可以传递一个包含IBucket
接口中列出的属性的对象或 L Bucket
2 的实例。任何一个都行得通。但是,如果该bucket
属性调用 L2Bucket
,则只能在该字段中传递一个Bucket
实例。
当您将先前存在的资源导入堆栈时,这种区别变得非常重要。您可以为堆栈原生的任何资源创建 L2 结构,但是如果您需要引用在堆栈之外创建的资源,则必须使用该 L2 构造的接口。这是因为如果堆栈中尚不存在新资源,则创建 L2 结构会创建一个新资源。对现有资源的引用必须是符合该 L2 构造接口的普通对象。
为了在实践中更容易做到这一点,大多数 L2 构造都有一组与之关联的静态方法,这些方法返回该 L2 构造的接口。这些静态方法通常以单词开头from
。传递给这些方法的前两个参数是相同的,scope
并且是标准 L2 构造所需的id
参数。但是,第三个参数只props
不过是定义接口的一小部分属性(或者有时只是一个属性)。因此,当你传递 L2 构造时,大多数情况下只需要接口的元素。这样您也可以在可能的情况下使用导入的资源。
// Example of referencing an external S3 bucket const preExistingBucket = Bucket.fromBucketName(this, "external-bucket", "name-of-bucket-that-already-exists");
但是,你不应该过分依赖接口。只有在绝对必要时才应导入资源并直接使用接口,因为接口不提供使 L2 构造如此强大的许多属性(例如辅助方法)。
帮助程序方法
L2 构造是一个编程类,而不是一个简单的对象,因此它可以公开类方法,允许您在实例化完成后操作资源配置。这方面的一个很好的例子是 AWS Identity and Access Management (IAM) L2 角色结构。以下片段显示了使用 L2 Role
构造创建相同IAM角色的两种方法。
如果没有辅助方法:
const role = new Role(this, "my-iam-role", { assumedBy: new FederatedPrincipal('my-identity-provider.com'), managedPolicies: [ ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess") ], inlinePolicies: { lambdaPolicy: new PolicyDocument({ statements: [ new PolicyStatement({ effect: Effect.ALLOW, actions: [ 'lambda:UpdateFunctionCode' ], resources: [ 'arn:aws:lambda:us-east-1:123456789012:function:my-function' ] }) ] }) } });
使用辅助方法:
const role = new Role(this, "my-iam-role", { assumedBy: new FederatedPrincipal('my-identity-provider.com') }); role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")); role.attachInlinePolicy(new Policy(this, "lambda-policy", { policyName: "lambdaPolicy", statements: [ new PolicyStatement({ effect: Effect.ALLOW, actions: [ 'lambda:UpdateFunctionCode' ], resources: [ 'arn:aws:lambda:us-east-1:123456789012:function:my-function' ] }) ] }));
与前一层相比,能够在实例化后使用实例方法来操作资源配置,这使得 L2 构造具有更大的灵活性。L1 构造还会继承一些资源方法(例如addPropertyOverride
),但是直到第二层才获得专门为该资源及其属性设计的方法。
枚举
CloudFormation 语法通常要求您指定许多细节才能正确配置资源。但是,大多数用例通常只能由少数几种配置覆盖。使用一系列枚举值来表示这些配置可以大大减少所需的代码量。
例如,在本节前面的 S3 存储桶 L2 代码示例中,您必须使用 CloudFormation 模板的bucketEncryption
属性来提供所有详细信息,包括要使用的加密算法的名称。相反, AWS CDK
提供了BucketEncryption
枚举,它采用了五种最常见的存储桶加密形式,并允许您使用单个变量名来表达每种形式。
枚举未涵盖的边缘情况呢? L2 结构的目标之一是简化配置第 1 层资源的任务,因此第 2 层可能不支持某些不太常用的边缘情况。为了支持这些边缘情况, AWS CDK 允许您使用addPropertyOverride方法直接操作底层 CloudFormation 资源属性。有关属性覆盖的更多信息,请参阅本指南的 “最佳实践” 部分以及文档中的 “抽象和逃生舱口” 部分。 AWS CDK
辅助类
有时,枚举无法完成为给定用例配置资源所需的编程逻辑。在这些情况下, AWS CDK 通常会改为提供辅助类。枚举是一个提供一系列键值对的简单对象,而辅助类则提供类的全部功能。 TypeScript 通过公开静态属性,辅助类仍然可以像枚举一样起作用,但是这些属性的值可以在辅助类构造函数或辅助方法中使用条件逻辑在内部设置。
因此,尽管BucketEncryption
枚举可以减少在 S3 存储桶上设置加密算法所需的代码量,但同样的策略不适用于设置持续时间,因为有太多可能的值可供选择。为每个值创建一个枚举的麻烦远大于其价值。因此,助手类用于 S3 存储桶的默认 S3 对象锁定配置设置,如该ObjectLockRetention类所示。 ObjectLockRetention
包含两种静态方法:一种用于合规性保留,另一种用于监管保留。这两种方法都将 D uration 帮助器类的实例作为参数来表示应配置锁的时间。
另一个例子是 AWS Lambda 辅助类 Runtime。乍一看,与该类相关的静态属性似乎可以通过枚举来处理。但是,在幕后,每个属性值都代表Runtime
类本身的一个实例,因此在类的构造函数中执行的逻辑无法在枚举中实现。