2020-12-09

AWS CDK: Construct とは何か

この記事は AWS その2 Advent Calendar 2020 の 9 日目の記事です。

AWS CDK を使っていれば、Construct というキーワードは目にしたことがあると思います。基本的なところですが、今回はこの Construct とは何者か? というところを整理したいと思います。

Constructs Programming Model とは

constructs という TypeScript のライブラリがあります。

これは何かというと、「設定ファイルを TypeScript で記述するための基本となるライブラリ」です。いろいろと便利な関数が実装されていますが、主に木構造を構成する Construct クラスを定義しています。

例えば以下のような感じで使うことができます。

import { Node, Construct } from 'constructs';

class MyConstruct extends Construct {
  onSynthesize() {
    console.log(Node.of(this).id);
  }
}

const c1 = new MyConstruct(undefined as any, 'C1');
const c2 = new MyConstruct(c1, 'C2');
new MyConstruct(c1, 'C3');
new MyConstruct(c2, 'C4');

Node.of(c1).synthesize({ outdir: '' });

Output:

C4
C2
C3
C1

(onSynthesize メソッドがツリーの Postorder 順で呼び出されています)

非常にかんたんですが、なんとなく CDK に近いものは感じられたでしょうか?

このように Construct は AWS や CDK に固有の概念ではなく、もともとは単に「ツリー構造のノード」であることがわかっていただけたかと思います。CDK の開発者は、Construct クラスを組み合わせて複雑な設定ファイルを構築していくことを Construct Programming Model と呼んでいます。

また、Construct クラス周りが constructs ライブラリとして切り出してあるおかげで、Construct Programming Model を用いて CloudFormation 以外の設定ファイルを出力するツールを作ることも容易になっています。利用例としては terraform-cdk が挙げられます。

L1 Construct/L2 Construct とは何か

ところで先程から「ツリー構造」と連呼していますが、何を表現するツリー構造なのでしょうか? CloudFormation の YAML の構造のことでしょうか? もちろんそうではありません。Construct のツリー構造は、必ずしも出力される設定ファイルの構造とは一致しません。では、一体何を実現するためのツリー構造なのでしょうか。

ところで constructs を使って設定ファイルを便利に記述できるようにするには、まず「設定ファイルを愚直にそのまま表した Construct」を揃えるのが正攻法でしょう。特定の用途にしか使わないツールならまだしも、CDK のように CloudFormation でできることはすべてできるようにしたいなら、CloudFormation のリソースをそのまま表現した Construct がまず無いと始まりません。

しかし一方で、実際に Construct を駆使して設定を記述する際にこれまでの設定ファイルと全く同じように書いていたのでは、本末転倒と言えます。誰も TypeScript の中で YAML を書き直したいとは思わないでしょう。

そこで登場するのが 「Construct をラップした Construct」 です。出力形式の設定ファイルに愚直な Construct を抽象化したりまとめたりして、汎用プログラミング言語でも使いやすくした Construct を新たに作ることで、そのユーザーはより効率的に、短い記述で設定を記述することができます。そのような「抽象的な Construct」を L2 Construct(または High Level Construct)と呼び、逆にもとの設定ファイルに愚直なものを L1 Construct(または Low Level Construct)と呼びます。

もう先程の問いの答えもお分かりかと思います。 Construct がツリー構造を成すのは L2 Construct が L1 Construct をまとめあげるからです。 ちなみにツリーのルートとなるのは CDK で言う Stack です。私達が普段 CDK を使うときも、Stack という抽象的な Construct を定義しているというわけです(この場合 L2 と言うより L3 Construct と言ったほうが正確でしょうか)。

これらには Construct という観点で見れば、技術的な違いはありません。便宜的に抽象的かそうでないかで L1/L2 と呼び分けているだけで、実際はどちらも単にツリーのノードです(CDK という観点で見れば自動生成されたかそうでないかという違いがありますが)。

尚 CDK では、L1 Construct には Cfn〜 というプリフィックスが付き、L2 Construct と区別できるようになっています。もし CDK に L1 Construct しかなかったら全く使い物にならなかっただろうことは容易に想像できますね!

まとめ

以上です。ここまで読んでいただきありがとうございました!

(そのうち L2 Construct を OSS として公開した話も書くかもしれません)