オブジェクトの広場はオージス総研グループのエンジニアによる技術発表サイトです

クラウド/Webサービス

もっとじっくり AWS CDK のコンセプト

第4回 L3 コンストラクトの境界
オージス総研
樋口 匡俊
2024年1月30日

前々回は L1、前回は L2 ときて、今回は L3 コンストラクトをとりあげます。L1 や L2 とくらべて L3 の定義はあまり明確ではありません。AWS CDK 公式の解説を読んでいても、L3 と他のコンストラクトとの境界はどこにあるのか、どれが L3 でありどれが L3 でないのか、判断に迷うことがあります。今回はそんな L3 について、なるべく引用や具体例を挙げつつ、いつもよりは筆者の解釈による補足を多めに説明します。

L3 とは:公式ドキュメントの説明

はじめに、AWS CDK v2 の『Developer Guide』による L3 コンストラクトの説明を見てみましょう。 この公式ガイドには、コンストラクトを L1, L2, L3 の三種類に分けて説明した文章があちこちに散らばっています1。 それぞれ書いてあることが異なりとまどってしまうのですが、ここでは AWS CDK のコンセプトをまとめた『Concepts』の中の一ページ『Constructs』による L3 の説明を紹介します。

the AWS Construct Library includes L3 constructs, which we call patterns. These constructs are designed to help you complete common tasks in AWS, often involving multiple kinds of resources.

AWS コンストラクトライブラリには L3 コンストラクトがあり、パターン と呼ばれている。それらは AWS でよくある作業をやりとげるのに役立つよう設計されており、多くの場合さまざまな種類のリソースを含む。

L3 の別名をパターンというのは、有名な GoF のデザインパターンや各種クラウドデザインパターンを意識してのことでしょう。

L1 と L2 が個々のリソースを表すコンストラクトであるのに対し、L3 はそれらさまざまなリソースを組み合わせるコンストラクトです。 前回説明した用語で言うと、L3 はさまざまな L1 や L2 をコンポジションし、センシブルデフォルトやベストプラクティスを盛り込んだコンストラクトです。

L3 の実装

L3 の説明というと、以上のように短く簡素なものがほとんどです。 L3 の実装もまた、決まりはほとんどありません。

L1, L2 にはそれぞれ共通の親クラス CfnResource, Resource がありますが、L3 にはありません。 コンストラクトの一種として Construct クラスを継承していれば十分です。

L1, L2, L3 のクラス階層

コードとしては、次のようにスコープと ID とプロパティをもつ、なんの変哲もないコンストラクトを書くことになります。

// L3 コンストラクトのプロパティ
export interface XXXXProps {
  ...
}

// L3 コンストラクト
export class XXXX extends Construct {
  constructor(scope: Construct, id: string, props: XXXXProps) {
    ...
  }
}

他に決まりがあるとしたら、モジュール名の末尾に patterns を付けることぐらいでしょうか。 AWS CDK コンストラクトライブラリで該当するのは aws-ecs-patternsaws-route53-patterns の二つです。 ただし、このことに言及しているのはブログ記事2ぐらいで、冒頭の『Developer Guide』には書かれておらず、決まりといえるほどのものではありません。

ゆれ動く L2 と L3 の境界:LambdaRestApi

このように L3 は説明が簡素で、実装上の決まりもほとんどなく、自由に作れるコンストラクトです。 自由なのはよいのですが、そのかわりに、どれが L3 でありどれが L3 でないのか、境界がはっきりしないという問題があります。

そのことを示すよい例が LambdaRestApi コンストラクトです。 筆者はこのコンストラクトを L2 だと思っています。 けれども公式ドキュメントでは、L3 として分類されたり、L2.5 という名前で L2 と L3 の中間に分類されたりしています。

以下ではこの LambdaRestApi を通して、ゆれ動く L2 と L3 の境界を見ていきましょう。

LambdaRestApi は L2 である?

筆者が LambdaRestApi を L2 だと思うのは、前回説明した L2 としての特徴をいくつもそなえているからです。

LambdaRestApi のクラス階層は以下のようになっています。

LambdaRestApi のクラス階層

図の RestApi は Amazon API Gateway の REST API を表すコンストラクトです。 RestApi は L2 共通の親クラスである Resource を継承しています。 また、図には描いていませんが、基底クラスとして RestApiBase クラスを継承し IRestApi インターフェースを実装しています。 さらに、メインの L1 として CfnRestApi をコンポジションしています。 よって RestApi は明らかに L2 です。

LambdaRestApi はこの RestApi を継承し Lambda プロキシ統合を設定しやすくしたコンストラクトです。 継承を is-a 関係として解釈するならば、LambdaRestApiRestApi の一種であり L2 の一種です。

LambdaRestApi は L3 である?

ところが『Developer Guide』では、LambdaRestApi は L3 に分類されています。 また AWS の公式ブログにも、CfnRestApi を L1、RestApi を L2、LambdaRestApi を L3 として解説している記事があります。

そういえば L3 は単なるコンストラクトであり、LambdaRestApi もコンストラクトです。 L3 はよくあるタスクに役立ち、LambdaRestApi も Lambda プロキシ統合の設定というよくあるタスクに役立ちます。 L3 はさまざまなリソースを組み合わせてくれますし、LambdaRestApi も REST API や AWS Lambda 関数などのリソースを組み合わせてくれます。 そのことだけに注目し、上に述べた L2 としての特徴を無視するのであれば、L3 に分類されることもあるでしょう。

さらに先月 2023 年 12 月に発表された公式ドキュメント『The AWS CDK layer guide』の L3 の解説では、L3 の作り方として L2 を直接継承する方法が解説されています3。 そうなると実装の観点からも、L2 である RestApi を継承する LambdaRestApi が L3 であってもよいことになります。

しかしほんとうにそれでよいのでしょうか? それだと抽象度の高い L3 が、抽象度の低い L2 の一種であるということになりそうなのですが。

LambdaRestApi は L2.5 である?

そうした疑問はさておき、もうひとつ、LambdaRestApi を L2 でも L3 でもなく L2.5 コンストラクトとみなす考え方を紹介します。 L2.5 は前回も紹介した AWS CDK コンストラクトライブラリのデザインガイドラインに出てくる分類です。 このガイドライン以外ではあまり見かけない分類なのですが、ちょっとその説明を読んでみましょう。

The next level of abstraction present within the CDK are what we designate as “L2.5s”: a step above the L2s in terms of abstraction, but not quite at the level of complete patterns or applications. These constructs still largely focus on a single logical resource – in constrast to “patterns” which combine multiple resources – but are customized for a specific common usage scenario of an L2. Examples of L2.5s in the CDK are aws-apigateway.LambdaRestApi, aws-lambda-nodejs.NodeJsFunction, aws-rds.ServerlessCluster and eks.FargateCluster.

CDK における (L2 の) 次の抽象化レベルは「L2.5」と呼ばれるものである。L2.5 は抽象化という点では L2 の一段上であるが、完全なパターンやアプリケーションの水準にまでは達していない。それらはなおも (L2 と同様に) 単一の論理リソースに対して主に焦点をあてており、複数のリソースを組み合わせる「パターン」とは大いに異なるが、L2 の特定のよくある利用シナリオのためにカスタマイズされている。L2.5 の例として CDK には aws-apigateway.LambdaRestApi, aws-lambda-nodejs.NodeJsFunction, aws-rds.ServerlessClusterそして eks.FargateCluster がある。

L2.5 は、L2 を用いた特定のよくあるタスクに役立つという点では L3 と同じです。 しかし抽象度は L2 より高いものの、L3 と呼べるほどは高くないので、あいだをとって L2.5 と呼ぼうというわけです4

注目したいのは、コンストラクトが主に表したいものが、単一のリソースなのか複数のリソースの組み合わせなのかということを、L3 の境界として重視していることです。

LambdaRestApi は L2 である RestApi をカスタマイズしたコンストラクトです。 Amazon API Gateway の REST API に対し、Lambda 関数や RequestValidator などさまざまなリソースを組み合わせることができます。 しかし、主な焦点はあくまでも REST API という単一のリソースにあたっています。その点で L3 とは大いに異なると、ここでは考えられているのです。

L3 とは:Elad Ben-Israel 氏の説明

以上のように公式ドキュメントを読んでみても、L3 と他のコンストラクトとの境界はどこにあるのか、どれが L3 でありどれが L3 でないのか、明確に説明するのは難しい状況です。

こういうときは創始者の意見をきいてみましょう。 AWS CDK を生みだしたチームを率いていた Elad Ben-Israel 氏は、2021 年末、CDK の解説書『The CDK Book』の前書きに、今後の CDK の展望を書き記しています。 その中で L3 にもふれているので、少し長いですが引用します。

I believe we are going to see more and more truly high-level abstractions, or as we sometimes call them in CDK parlance, L3 constructs. The CDK offers a powerful programming model for simplifying the cloud, but so far most of the simplification has been up-leveling the API experience for individual AWS resources (what we call L2s). The “S3 Bucket” construct offers a rich, intent-based API for buckets, but it still requires users to understand what a bucket is. On the other hand, a “Static Website” construct offers a higher-level mental model: As long as users can wrap their heads around the concept of a static website, they don’t need to care about the underlying implementation. It shouldn’t matter if behind the scenes there is an Amazon S3 bucket and Amazon CloudFront distributions or Amazon API Gateway and AWS Lambda functions.

真にハイレベルな抽象化、あるいは CDK 用語で L3 コンストラクトと呼ばれるものを、これからますます多く目にするようになると確信している。CDK はクラウドを単純化する強力なプログラミングモデルを提供するが、これまでのところ、そうした単純化のほとんどは個々の AWS リソースに関する API 体験の水準を上げるものだった (いわゆる L2)。「S3 Bucket」コンストラクトはバケットに関するリッチなインテントベースの API を提供するが、それでもなおユーザーはバケットが何であるかを理解しなくてはならない。いっぽうで、「Static Website」コンストラクトはよりハイレベルなメンタルモデルを提供する。ユーザーは静的ウェブサイトという概念を理解しさえすれば、その下の実装を気にしなくてよい。その舞台裏に Amazon S3 バケットと Amazon CloudFront ディストリビューションがあろうが Amazon API Gateway と AWS Lambda 関数があろうが大した問題ではないだろう。

氏は L3 を「真にハイレベルな抽象化」と表現し、その例として「Static Website」という静的ウェブサイトのコンストラクトを挙げています。

AWS で静的ウェブサイトを構築する場合、リソースの組み合わせはいくつか考えられます。 S3 バケットと Route 53 を組み合わせたり、そこに CloudFront と Lambda を組み合わせたり。あるいは EC2 インスタンスを立てたりもするでしょう。

ユーザーにとって重要なのは、それら具体的なリソースではなく、リソースによってどのような静的ウェブサイトが実現されるかです。 「Static Website」コンストラクトは静的ウェブサイトという概念をユーザーに提示し、リソースという実装はユーザーにかわって面倒をみてくれるものです。

ハイレベルな L3、ローレベルな L3

「真にハイレベルな抽象化」である「Static Website」コンストラクトとくらべると、LambdaRestApi コンストラクトの抽象度は低いです。 ユーザーにかわってさまざまな設定をしてくれるとはいえ、REST API や Lambda 関数など主に組み合わせるリソースを、ユーザーにはっきり意識させる形になっています。

もしも先ほど紹介したように LambdaRestApi を L3 とみなすとしても、「Static Website」のようなコンストラクトとは区別できるようにしたいところです。

そこで以下では、「Static Website」コンストラクトのように、リソースと切りはなされた概念を主に表す L3 をハイレベルな L3 と呼ぶことにします。 また、リソースの組み合わせという実装を主に表す L3 をローレベルな L3 と呼ぶことにします。 筆者は LambdaRestApi は L2 だと思いますが、もしも L3 とみなすのであればローレベルな L3 ということになります。

実装は大した問題ではない?

さて、ハイレベルな L3 を使うとリソースをまったく気にしなくてよいならば、願ってもない夢のような話です。 しかし実際はそうではなく、Elad Ben-Israel 氏の話には続きがあります。

Today, constructs are primarily a design-time abstraction—they hide complexity when writing code. But what happens after a construct is deployed? The abstraction is lost, and operators are left to deal with a bunch of resources. If a developer uses the “Static Website” construct in their app they are not required to know how static websites are implemented. However, after their website is deployed, it stops being a website and becomes an Amazon S3 bucket, Amazon CloudFront distribution, and Amazon Route 53 hosted zone—the abstraction does not carry over.

いまのところ、コンストラクトというのは主として設計時の抽象化であり、コードを書くときに複雑さを隠してくれるものだ。けれども、コンストラクトがデプロイされた後はどうだろうか?抽象化は失われ、運用担当者が多数のリソースに対処することになる。開発者はアプリで「Static Website」コンストラクトを利用する場合に静的ウェブサイトがどのように実装されているか知る必要はない。しかしながら、ウェブサイトがデプロイされた後は、それはウェブサイトではなくなって Amazon S3 バケットや Amazon CloudFront ディストリビューションや Amazon Route 53 ホストゾーンになる。抽象化は持ち越されないのだ。

ハイレベルな L3 を使っても、結局のところ作られるのは具体的なリソースです。 AWS CDK アプリをデプロイしたら、それら現実のリソースひとつひとつにさまざまな観点から注意をはらう必要があります。 以下、筆者の思う例をいくつか挙げてみます。

  • コスト:思いもよらない不要なリソースを L3 が作成し無駄なコストが発生しないか?
  • セキュリティ:L3 のリソースが所属組織やプロジェクトのセキュリティ要件を満たしているか?
  • オブザーバビリティ:L3 のリソースが取得するログやメトリクスで十分か?
  • バージョンアップ:新バージョンの L3 が追加・更新・削除するリソースによってどのような影響が出るか?

現実の L3 と「パターン」

このようにハイレベルな L3 であっても、その下のリソースを完全には無視できません。 また、筆者が知るかぎり、現在公開されている L3 でハイレベルなものはあまりありません。 ハイレベルな L3 は、設計理論や開発手法、運用のノウハウなど、難易度の高い未開拓な領域ではないかと思います。

いっぽうでローレベルな L3 は、L3 に類似した「パターン (※ L3 の別名ではなく一般的な意味としての)」のプロジェクトも含め、すでにいくつかよく知られたものがあります。 最後にそれらローレベルな L3 と「パターン」の例をいくつか見てみましょう。

aws-ecs-patterns

aws-ecs-patterns は、AWS CDK コンストラクトライブラリの代表的な L3 のモジュールです。 その名のとおり、Amazon ECS に関する L3 コンストラクトがまとめられています。

ECS には aws-ecs というモジュールもあり、Cluster や Service など ECS の個々のリソースを表す L1, L2 がまとめられています。 aws-ecs-patterns の L3 は、それら L1, L2 を ECS 以外のサービスのリソースと組み合わせてくれます。

たとえば aws-ecs は Service を表す以下の L2 を提供しています。

aws-ecs-patterns はこれらの Service を Application Load Balancer (ALB) と組み合わせる L3 を提供しています。

名前からもわかるとおり、これらは組み合わせる主なリソースが ALB と Service であることを、はっきり意識して利用する L3 です。 このように AWS CDK の代表的な L3 であっても、概念よりは実際に組み合わせるリソースに注目した、ローレベルな L3 なのです。

AWS Solutions Constructs

AWS Solutions Constructs は、70 種類以上の「パターン」のコンストラクトをまとめたオープンソースのライブラリです。 開発元は AWS ですが、本家 AWS CDK とは異なるチームが、独立したリリース計画にもとづき開発しています。

Solutions Constructs のねらいは、AWS Well-Architected に沿ってリソースを組み合わせる際の複雑さを軽減することです5。 以下の例のように、主に組み合わせる 2, 3 種類のリソースごとにコンストラクトが提供されています。

これらのコンストラクトを使うと、名前に示されているリソース6のほかに、アクセスログを出力する S3 バケットなど Well-Architected を考慮した設定もほどこされます。

Solutions Constructs は L3 コンストラクトに見えますが、ドキュメントを読んでいても L3 という呼び名は出てきません。 なぜなのかは調べてもよくわからないのですが、もし L3 とみなすならば、やはりローレベルな L3 と言えるでしょう。

aws-cloudfront-s3 を用いたサンプルアプリとして AWS S3 Static Website というものがあります。 アプリではありますが、ローレベルな L3 を活用して、「Static Website」というハイレベルな L3 を開発する参考になるでしょう。

CDK Patterns

CDK Patterns は、アーキテクトの Matt Coulter 氏がはじめたオープンソースプロジェクトです。 文章と図による抽象的な「パターン」と、それを実現するコードを公開しています。

「パターン」のコードはコンストラクトではありません。 よって L3 でもありません。 コードは AWS CDK アプリのプロジェクトテンプレートとして提供されているので、ユーザーはそれをダウンロードし、適宜修正して利用します。

Matt Coulter 氏は CDK Patterns を開発した背景を解説する記事の中で、CDK を使うときは抽象化しすぎないよう警鐘を鳴らしています。 氏が CDK で気に入っていることは、使いなれた言語やテスト、リンター等を活用してインフラ開発を統制できることであり、つねに高度な抽象化に利点があるわけではないのだそうです。 CDK Patterns がコンストラクトによる抽象化を避けているのは、そうした実践的な考えにもとづいているのでしょう。

同様に「パターン」をコンストラクトでなくサンプルコードやテンプレートとして公開しているものとしては、AWS の Serverless Land による Serverless patterns があります。

おわりに

今回は L3 コンストラクトについて説明しました。 L1 や L2 とくらべて L3 の定義はあまり明確ではありません。 今回試みにハイレベルな L3 とローレベルな L3 という分類をとりいれてみましたが、そのように L3 という分類には、あらためて見直しが必要ではないかと思います。


参考資料

本文で紹介した資料のほかに、『CDK Patterns - The Good, The Bad and The Ugly』というビデオポッドキャストが参考になりました。L3 や「パターン」の利点と欠点についてくわしく語られています。


  1. 少なくとも三箇所。本文で紹介したものと『Getting started with the AWS CDK』と『Abstractions and escape hatches』。 

  2. Working with the AWS Cloud Development Kit and AWS Construct Library | AWS Developer Tools Blog』より: “Pattern Constructs are packaged in their own module and have the patterns suffix, like aws-ecs-patterns.” 

  3. さらに L2 の解説 では、L2 を作るときは Resource クラスではなく Construct クラスを直接継承するよう解説されています (“Any class that inherits the Construct class is an L1, L2, or an L3 construct. L2 constructs extend this class directly”)。そうなると L2 と L3 はますます区別がつきにくくなります。 

  4. 「L2.5 は抽象化という点では L2 の一段上である」と述べられていますが、LambdaRestApiRestApi を Lambda プロキシ統合という具体的な目的にしぼってカスタマイズしたのですから、抽象度はむしろ下がっているのではないか?という気もします。 

  5. Solutions Constructs 公式ドキュメントより: “The aim of AWS Solutions Constructs is to reduce the complexity and glue logic required when integrating common well-architected patterns to achieve your solution goals on AWS.” 

  6. コンストラクト名には、リソースが明らかな場合はサービス名を使うという命名規則があります。