第 2 層建構 - AWS 方案指引

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

第 2 層建構

AWS CDK 開放原始碼儲存庫主要是使用 TypeScript 程式設計語言撰寫,由許多套件和模組組成。主要套件程式庫稱為 aws-cdk-lib,大致分為每個 AWS 服務一個套件,但情況並非總是如此。如前所述,L1 建構會在建置程序期間自動產生,所以當您查看儲存庫時,看到的所有程式碼為何? 這些是 L2 建構,這是 L1 建構的抽象。

這些套件也包含 TypeScript 類型、列舉和介面的集合,以及新增更多功能的協助程式類別,但這些項目都提供 L2 建構。所有 L2 建構函數會在執行個體化時在其建構函數中呼叫其對應的 L1 建構函數,而建立的 L1 建構函數可以從第 2 層存取,如下所示:

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 的特殊類別。在 AWS CDK Construct 類別的第 1 版中, 類別是內建在開發套件中,但在第 2 版中則是獨立的套件。因此,雲端開發套件 (CDKTF) 等其他套件可以將其納入做為相依性。繼承類別的任何Construct類別都是 L1, L2 或 L3 建構。L2 建構模組會直接擴展此類別,而 L1 建構模組則會擴展名為 的類別CfnResource,如下表所示。

L1 繼承樹

L2 繼承樹

L1 建構

→ CfnResource 類別 CfnResource

→→ 抽象類別 CfnRefElement

→→→ 抽象類別 CfnElement

→→→→ 類別建構

L2 建構

→ 類別建構

如果 L1 和 L2 建構都繼承 Construct類別,為什麼不 L2 建構只延伸 L1? 類別Construct與層 1 之間的類別會將 L1 建構鎖定到位,做為 CloudFormation 資源的鏡像影像。它們包含抽象方法 (下游類別必須包含的方法),例如 _toCloudFormation,這會強制建構直接輸出 CloudFormation 語法。L2 建構會略過這些類別,並直接擴展該Construct類別。這可讓他們彈性地抽象 L1 建構所需的大部分程式碼,方法是在建構函數中分別建置這些程式碼。

上一節針對來自 CloudFormation 範本的 S3 儲存貯體,以及轉譯為 L1 建構的相同 S3 儲存貯體,side-by-side比較。該比較顯示屬性和語法幾乎相同,L1 建構與 CloudFormation 建構相比,只會儲存三行或四行。現在,讓我們比較 L1 建構與相同 S3 儲存貯體的 L2 建構:

S3 儲存貯體的 L1 建構

S3 儲存貯體的 L2 建構

new CfnBucket(this, "amzns3demobucket", { bucketName: "amzn-s3-demo-bucket", bucketEncryption: { serverSideEncryptionConfiguration: [ { serverSideEncryptionByDefault: { sseAlgorithm: "AES256" } } ] }, metricsConfigurations: [ { id: "myConfig" } ], ownershipControls: { rules: [ { objectOwnership: "BucketOwnerPreferred" } ] }, publicAccessBlockConfiguration: { blockPublicAcls: true, blockPublicPolicy: true, ignorePublicAcls: true, restrictPublicBuckets: true }, versioningConfiguration: { status: "Enabled" } });
new Bucket(this, "amzns3demobucket", { bucketName: "amzn-s3-demo-bucket", encryption: BucketEncryption.S3_MANAGED, metrics: [ { id: "myConfig" }, ], objectOwnership: ObjectOwnership.BUCKET_OWNER_PREFERRED, blockPublicAccess: BlockPublicAccess.BLOCK_ALL, versioned: true });

如您所見,L2 建構體小於 L1 建構體大小的一半。L2 建構會使用多種技術來完成此整合。其中一些技術適用於單一 L2 建構模組,但其他技術可以在多個建構模組之間重複使用,以便將其分隔成自己的類別以重複使用。L2 建構會以多種方式合併 CloudFormation 語法,如以下章節所述。

預設屬性

合併用於佈建資源的程式碼最簡單的方法是將最常見的屬性設定轉換為預設值。 AWS CDK 可存取強大的程式設計語言,而 CloudFormation 則無法存取,因此這些預設值通常具有條件性。有時可以從 AWS CDK 程式碼中消除數行 CloudFormation 組態,因為這些設定可以從傳遞給建構模組的其他屬性值推斷。

結構、類型和界面

雖然 AWS CDK 提供多種程式設計語言,但會以 TypeScript 原生撰寫,因此語言的類型系統可用來定義組成 L2 建構的類型。深入探索該類型的系統超出本指南的範圍;如需詳細資訊,請參閱 TypeScript 文件。總而言之,TypeScript type會描述特定變數所保留的資料類型。這可以是基本資料,例如 string,或更複雜的資料,例如 object。TypeScript interface是表達 TypeScript 物件類型的另一種方式,而 struct是介面的另一種名稱。

TypeScript 不會使用 struct 一詞,但如果您在 AWS CDK API 參考中查看,您會看到 struct 實際上只是程式碼中的另一個 TypeScript 界面。API 參考也稱特定界面為界面。如果結構和界面是相同的,為什麼文件會 AWS CDK 區分它們?

AWS CDK 稱為 結構的界面代表 L2 建構所使用的任何物件。這包括在執行個體化期間傳遞給 L2 建構的屬性引數的物件類型,例如 BucketProps S3 儲存貯體建構TableProps和 DynamoDB Table 建構,以及 中使用的其他 TypeScript 介面 AWS CDK。簡而言之,如果它是 內的 TypeScript 介面 AWS CDK ,而且其名稱不是字母 的字首I,則 會將其 AWS CDK 呼叫為結構

相反地, AWS CDK 使用 術語界面來表示純物件需要被視為特定建構函數或協助程式類別的適當表示的基本元素。也就是說,界面說明 L2 建構的公有屬性必須是什麼。所有 AWS CDK 界面名稱都是字母 前綴的現有建構或協助程式類別的名稱I。所有 L2 建構模組都會擴展 Construct類別,但也會實作其對應的界面。因此 L2 建構會Bucket實作 IBucket 界面。

靜態方法

L2 建構的每個執行個體也是其對應界面的執行個體,但反向不是真的。這在查看結構時很重要,以查看需要哪些資料類型。如果結構具有稱為 的屬性bucket,其需要資料類型 IBucket,您可以傳遞包含IBucket介面中列出屬性的物件或 L2 的執行個體Bucket。其中一個都可以運作。不過,如果該bucket屬性針對 L2 呼叫 Bucket,則您只能在該欄位中傳遞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 建構模組建立相同 IAM Role 角色的兩種方式。

沒有協助程式方法:

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 類別的完整功能。協助程式類別仍然可以透過公開靜態屬性來充當列舉,但這些屬性可以在內部使用協助程式類別建構函數或協助程式方法中的條件邏輯來設定其值。

因此,雖然列舉可以減少在 S3 BucketEncryption 儲存貯體上設定加密演算法所需的程式碼數量,但相同的策略無法用於設定時間持續時間,因為只有太多可能的值可供選擇。為每個值建立列舉會比它更麻煩。因此,協助程式類別用於 S3 儲存貯體的預設 S3 物件鎖定組態設定,如 ObjectLockRetention 類別所示。 ObjectLockRetention包含兩種靜態方法:一種用於合規保留,另一種用於控管保留。這兩種方法都需要持續時間協助程式類別的執行個體做為引數,以表示應該設定鎖定的時間量。

另一個範例是 AWS Lambda 協助程式類別執行期。乍看之下,與此類別相關聯的靜態屬性可能會由列舉處理。不過,在幕後,每個屬性值代表Runtime類別本身的執行個體,因此在類別建構函數中執行的邏輯無法在列舉內實現。