AWS CDKの'aws-lambda-nodejs'を使ってCDKとLambdaの間の壁を破壊する

# 動機
久々に[AWS CDKのリファレンス](https://docs.aws.amazon.com/cdk/api/latest/)を眺めていたら[こんなConstruct](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-nodejs-readme.html)を発見！！

> aws-lambda-nodejs module
>
> This library provides constructs for Node.js Lambda functions.

これはもしかして、CDKのソースとLambdaのソースを一気通貫で管理できるってことかい！？もう[こんな過去記事](https://qiita.com/tetsuya-zama/items/4335f639650d6ec1e00c)のようにCDKのソースとLambdaのソースを分けて管理したり別々のnode_modulesやtsconfig.jsonを持たなくても良いのかい！？なんて美しいんだ！！バチが当たるじゃないか！？

ということでやってみました。

# 作るもの： S3式FizzBuzz

じゃあ、検証ということで[FizzBuzz](https://ja.wikipedia.org/wiki/Fizz_Buzz)でも作りますかね。折角のAWS CDKでただただFizzBuzzをやるlambdaを作っても面白くないのでこんな感じでどうでしょう？

## 仕様

![s3-fizzbuzz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290471/d811862c-18b8-00b6-3347-d2bb3b445eca.png)


S3に置いた設定ファイルの設定値を元に[Fizz Buzz](https://ja.wikipedia.org/wiki/Fizz_Buzz)を実行した結果をS3に出力します。

### 設定ファイル例

```yaml:test.yaml
from: 1 #始点
to: 15 #終点
fizz: 3 #fizzに変換する値（任意）
buzz: 5 #buzzに変換する値（任意）
```

### 結果例

```text:test.20200223103211.log
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
```

# 作ってみた

## ソースの全体像について

GitHubに置いたので、全体像を把握したい方は[こちら](https://github.com/tetsuya-zama/s3-fizzbuzz)からご覧ください。

ポイントはやはり、今までのようにlambda専用にディレクトリを切って独自のpackage.jsonやtsconfig.jsonを置いて管理しているわけではなく、`./lib`ディレクトリの中にスタック定義と一緒にlambda定義も置いてあって、package.jsonやtsconfig.jsonはCDKのものと共有している点だと思います。いや、これだけでかなりスッキリしましたよね。感動。。

## 環境準備

まずは[公式チュートリアル](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html)を参考に、お使いのアカウントに対してAWS CDKからリソースをデプロイできるようにしておいてください。

特にこだわりがなければ、[AWS Cloud9](https://aws.amazon.com/jp/cloud9/)上に環境作るのが楽だと思います。ただ、CDKに限ったことではないですが、デフォルトのt2.microインスタンスでtypescriptを扱っているとビルドやユニットテストの際にメモリ不足で停止することがよくあるので、お財布と相談の上で１個上のt2.smallに上げることを個人的にはおすすめします。

以下、cdkコマンドは使えるようになった前提で話を進めます。

## i.プロジェクトの作成

```bash
$ mkdir s3-fizzbuzz
$ cd s3-fizzbuzz
$ cdk init --language=typescript
```
まあ、ほぼほぼチュートリアルの通りですね。


## ii.ドメイン層

『たかだかFizzBuzzでドメイン層って。。。』というツッコミが聞こえてきそうではありますが、まあまあ、ちょっとだけお付き合いください。
`cdk init`で作成された`lib`ディレクトリ直下に以下のようなソースを書きました。

```typescript:./lib/domain.ts
/**
 * FizzBuzzの設定
 **/
export interface FizzBuzzSetting{
    /**始点*/
    from: number
    /**終点*/
    to: number
    /**fizzに変換する数*/
    fizz?: number
    /**buzzに変換する数*/
    buzz?: number
}

/**
 * [[FizzBuzzSetting]]のtype guard
 * @param obj チェックしたいオブジェクト
 * @return objがFizzBuzzSettingであればtrue
 **/
export const isFizzBuzzSetting = (obj: any): obj is FizzBuzzSetting =>{
    return typeof obj.from === "number"
        && typeof obj.to === "number"
        && (!obj.fizz || typeof obj.fizz === "number")
        && (!obj.buzz || typeof obj.buzz === "number");
}

/**
 * 引数が正の整数かどうか
 * @param n チェックしたい数
 * @return nが正の整数であればtrue
 **/
const isPositiveInt = (n:number): boolean => n > 0 && Math.floor(n) === n;

/**
 * [[FizzBuzzSetting]]のバリデーション
 * @param from 始点（正の整数）
 * @param to 終点(正の整数)
 * @param fizz "fizz"に変換する数(正の整数/デフォルト3)
 * @param buzz "buzz"に変換する数(負の整数/デフォルト5)
 * @return デフォルト値割り当て済みのFizzBuzzSetting
 **/
const validate = ({from, to, fizz=3, buzz=5}: FizzBuzzSetting):Required<FizzBuzzSetting> => {
    if(!isPositiveInt(from)) throw new Error(`from:${from}は正の整数である必要があります`);
    if(!isPositiveInt(to)) throw new Error(`to:${to}は正の整数である必要があります`);
    if(to <= from) throw new Error(`to:${to}はfrom:${from}よりも大きい数値である必要があります`);
    if(!isPositiveInt(fizz)) throw new Error(`fizz:${fizz}は正の整数である必要があります`);
    if(!isPositiveInt(buzz)) throw new Error(`fizz:${buzz}は正の整数である必要があります`);
    if(fizz === buzz) throw new Error(`fizz:${fizz}とbuzz:${buzz}は異なる数値である必要があります`);
    
    return {from,to,fizz,buzz};
}

/**
 * fizzbuzzを実行する
 * @param setting 設定値
 * @return 結果が格納された配列
 **/
export const fizzbuzz = (setting: FizzBuzzSetting): string[] => {
    const {from, to, fizz, buzz} = validate(setting);
    
    return Array.from({length: (to - from + 1)}, (_,i) => i + from).map((n):string =>{
        if(n % (fizz * buzz) === 0) return "fizzbuzz";
        if(n % fizz === 0) return "fizz";
        if(n % buzz === 0) return "buzz";
        return n.toString();
    });
}

```

### 閑話休題：サーバレス/FaaSはベンダーロックインになる？

よく「サーバレス/FaaSはクラウドベンダーにロックインするよね」と言われますが、それに対する答えというか対策が「ドメイン層の分離」だと思っています。つまり、AWS Lambdaの文脈で言えば、

1. LambdaやS3、AWS SDKなどに依存せず、ピュアなjs/tsでアプリケーションの仕様を書き表した層（≒ドメイン層）
2. LambdaやS3、AWS SDKとドメイン層を接続する層(≒インタフェース層)

を明確に分離して2.の部分を薄く保つことによって、仮にAWSをやめたくなったとしても上記１.の部分に関しては、ExpressやApploServerに乗っけるなり何なりして使い回せるわけです。これは別に目新しい考え方でも何でもなくて、例えばMVCに於いて「Controller層を薄く作りましょう」と言っているのと同じ考え方なわけで、サーバレスであろうが何であろうが『アーキテクチャをクリーンに保つ』という考え方は変わらないわけです。

このあたりの理屈を詳しく勉強したい方は、アンクル・ボブ先生の[Clean Architecture](https://www.amazon.co.jp/dp/B07FSBHS2V/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)がおすすめです。

### 閑話休題：テスタビリティ

もう１つ、ドメイン層を分離する動機として、テスタビリティ（テストのしやすさ）の向上があります。
「FizzBuzzのアルゴリズムが正しく組めているか」「設定の内容を正しく読めているか」をテストしたいときに、「S3 Bucketへの書き込み/読み込みができるサービスロールが付いているか」みたいなゴリゴリAWSと密結合な関心事について1ミリ秒足りとも考えたくないのです。
　
ドメイン層を明確に分離したことによって、以下のような「ドメインの関心ごとに特化した」テストをシンプルに書くことができます。

```typescript:./test/domain.test.ts
import {fizzbuzz, isFizzBuzzSetting} from '../lib/domain';

/**
 * fizzbuzz関数に関するテスト
 **/
describe("fizzbuzz function",()=>{
    /**
     * 設定値に応じてfizzbuzzを実行して結果を返す
     **/
    it("returns result of fizzbuzz by setting",()=>{
        const result = fizzbuzz({
           from: 1,
           to: 15
        });
        
        expect(result).toEqual(["1","2","fizz","4","buzz","fizz","7","8","fizz","buzz","11","fizz","13","14","fizzbuzz"]);
    });
    
    /**
     * fizzに変換する値を書き換えることができる
     **/
    it("assumes overwriting 'fizz' value by setting",()=>{
        const result = fizzbuzz({
           from: 1,
           to: 10,
           fizz: 2
        });
        
        expect(result).toEqual(["1","fizz","3","fizz","buzz","fizz","7","fizz","9","fizzbuzz"]);
    });
    
    /**
     *  buzzに変換する値を書き換えることができる
     **/ 
    it("assumes overwriting 'buzz' value by setting",()=>{
        const result = fizzbuzz({
            from: 1,
            to: 10,
            fizz: 2,
            buzz: 3
        });
        
        expect(result).toEqual(["1","fizz","buzz","fizz","5","fizzbuzz","7","fizz","buzz","fizz"]);
    });
    
    /**
     * 設定値が不正な場合はエラーが発生する
     **/ 
    it("throws Error if setting is invalid",()=>{
        expect(() => fizzbuzz({from:-1,to:2})).toThrow();
        expect(() => fizzbuzz({from:1.1,to:2})).toThrow();
        expect(() => fizzbuzz({from:1,to:-2})).toThrow();
        expect(() => fizzbuzz({from:1,to:2.1})).toThrow();
        expect(() => fizzbuzz({from:2,to:1})).toThrow();
        expect(() => fizzbuzz({from:1,to:10, fizz:-1})).toThrow();
        expect(() => fizzbuzz({from:1,to:10, fizz:1.1})).toThrow();
        expect(() => fizzbuzz({from:1,to:10, buzz:-1})).toThrow();
        expect(() => fizzbuzz({from:1,to:10, buzz:1.1})).toThrow();
        expect(() => fizzbuzz({from:1,to:10, fizz:2,buzz:2})).toThrow();
    });
});

/**
 * isFizzBuzzSettingに関するテスト
 **/
describe("isFizzBuzzSetting",()=>{
    /**
     * objectがFizzBuzzSettingかどうかをチェックする 
     **/
    it("checks if the object is FizzBuzzSetting",()=>{
        expect(isFizzBuzzSetting({from:1, to:100})).toBe(true);
        expect(isFizzBuzzSetting({from:1, to:100, fizz:4})).toBe(true);
        expect(isFizzBuzzSetting({from:1, to:100, fizz:4, buzz:7})).toBe(true);
    });
    
    /**
     * 引数が不正であればfalseを返す
     **/ 
    it("returns false if argument is invalid",()=>{
        expect(isFizzBuzzSetting(1)).toBe(false);
        expect(isFizzBuzzSetting({})).toBe(false);
        expect(isFizzBuzzSetting({from:1})).toBe(false);
        expect(isFizzBuzzSetting({to:1})).toBe(false);
        expect(isFizzBuzzSetting({from:"a", to:3})).toBe(false);
        expect(isFizzBuzzSetting({from:1, to:"b"})).toBe(false);
        expect(isFizzBuzzSetting({from:1, to:3, fizz:"c"})).toBe(false);
        expect(isFizzBuzzSetting({from:1, to:3, fizz:2, buzz:"d"})).toBe(false);
    });
});
```

今回は`aws-lambda-nodejs`を使ってソースをEndToEndで管理しているので、`npm test`コマンドでCDKのテストとドメイン層のテストを一気通貫で実行できるのもまた魅力の１つだと思っています。

## iii. Lambda関数(≒インタフェース層)

ということで本題に戻って、Lambdaを書いて行きましょう。

[aws-lambda-nodejs](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-nodejs-readme.html)のOverviewによると、

> Define a NodejsFunction:
>
> `new lambda.NodejsFunction(this, 'my-handler');`
>
> By default, the construct will use the name of the defining file and the construct's id to look up the entry file:
>
> .
> ├── stack.ts # defines a 'NodejsFunction' with 'my-handler' as id
> ├── stack.my-handler.ts # exports a function named 'handler'

ということで命名規則がありそうなのでそれに従って行きましょう。
`cdk init`で`./lib/s3-fizzbuzz-stack.ts`というスタック定義用のソースが作られていて、関数名を`fizzbuzz-func`だとすると、命名規則的には`s3-fizzbuzz-stack.fizzbuzz-func.ts`を作ればいいはずです。

その前に関連パッケージをnpm経由でinstallします。`package.json`と`/node_modules`をCDKと共用できるので楽でいいですね！

```bash
$ npm install --save aws-sdk js-yaml moment
$ npm install --save-dev @types/aws-lambda 
```

ということで、Lambdaのhandlerを書いていきましょう。

```typescript:./lib/s3-fizzbuzz-stack.fizzbuzz-func.ts
import {S3Handler,S3Event, S3EventRecord} from 'aws-lambda';
import {S3} from 'aws-sdk';
import * as yaml from 'js-yaml';
import {Moment} from 'moment';
import {isFizzBuzzSetting, fizzbuzz} from './domain';


const s3 = new S3();

/**
 * 設定ファイルを読み込む
 * @param bucket 対象バケット
 * @param key 対象key
 * @return 読み込んだ結果
 **/
const loadSetting = async(bucket: string, key: string): Promise<Object> => {
    const response = await s3.getObject({
        Bucket: bucket,
        Key: key
    }).promise();
    
    if(response.Body){
        const body = response.Body.toString();
        if(key.endsWith(".json")){
            return JSON.parse(body);
        }else{
            return yaml.safeLoad(body);
        }
    }else{
        throw new Error(`Load setting file failure:${key} on ${bucket}`);
    }
}

/**
 * 実行結果を保存する
 * @param key 対象バケット
 * @param settingName 設定ファイル名
 * @param result 実行結果
 **/
const saveResult = async (bucket: string, settingName: string, result: string[]): Promise<void> => {
    const moment = require("moment");
    const now: Moment = moment().format("YYYYMMDDHHmmss");
    const resultKey = settingName.split(".").slice(0,-1).join(".") + `.${now}.log`;
    
    await s3.putObject({
        Bucket: bucket,
        Key: resultKey,
        Body: result.join("\n")
    }).promise();
}

/**
 * このLambdaの実行対象レコードかどうか
 * @param record チェックするrecord
 * @return 実行対象であればtrue
 **/
const isTargetEvent = (record: S3EventRecord): boolean => record.eventName.startsWith("ObjectCreated:") 
    && (record.s3.object.key.endsWith(".json") || record.s3.object.key.endsWith(".yaml") || record.s3.object.key.endsWith(".yml"));

/**
 * fizzbuzz-funcのhandler
 * @param event S3のイベント
 **/
export const handler:S3Handler = async(event:S3Event) => {
    for(const record of event.Records){
        if(isTargetEvent(record)){
            try{
                const bucketName = record.s3.bucket.name;
                const targetKey = record.s3.object.key;
                const setting = await loadSetting(bucketName, targetKey);
                
                if(isFizzBuzzSetting(setting)){
                    const result = fizzbuzz(setting);
                    await saveResult(bucketName, targetKey, result);
                }else{
                    throw new Error(`Invalid setting ${JSON.stringify(setting)}`);
                }
            }catch(e){
                console.error(e.message);
            }
            
        }
    }
}
```

まあ、大したことはやっていないですね。S3の`ObjectCreated:*`を拾って、S3から設定ファイル(YAMLもしくはjson)を読み込んで、その設定値を先ほど作ったドメイン層のfizzbuzz関数に渡して、結果をまたS3に保存しているだけです。

## iv. スタック定義

ということでいよいよ、`aws-lambda-nodejs`を利用してlambdaを作ってみましょう。

まずは関連ライブラリのinstallをします。

```bash
$ npm install --save-dev @aws-cdk/aws-lambda-nodejs @aws-cdk/aws-s3 @aws-cdk/aws-s3-notifications 
```

続いて、`./lib/s3-fizzbuzz-stack.ts`を編集します。

```typescript:./lib/s3-fizzbuzz-stack.ts
import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda-nodejs';
import * as s3 from '@aws-cdk/aws-s3';
import * as s3n from '@aws-cdk/aws-s3-notifications';

/**
 * S3式FizzBuzzのスタック
 **/
export class S3FizzbuzzStack extends cdk.Stack {
  /**
   * コンストラクタ
   * @param scope スコープ
   * @param id Constructのid
   * @param props 設定値
   */
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    /**
     * fizzbuzz-func関数
     * ${スタックのソース名}.${関数のID(第２引数)}.tsが
     * exportするhandler関数をLambdaのハンドラと見做してデプロイしてくれる
     **/
    const fizzbuzzFunc = new lambda.NodejsFunction(this, "fizzbuzz-func");
    
    /**
     * 設定ファイルと結果を格納するバケット
     **/
    const fizzbuzzBucket = new s3.Bucket(this, "fizzbuzz-bucket");
    
    /**
     * fizzbuzz-bucketへの読み込み/書き込み権限をfizzbuzz-funcに付与する
     **/
    fizzbuzzBucket.grantReadWrite(fizzbuzzFunc);
    
    /**
     * fizzbuzz-bucketへの.json/.yaml/.ymlファイルのOBJECT_CREATEDを
     * fizzbuzz-funcへ通知する
     **/
    fizzbuzzBucket.addEventNotification(
      s3.EventType.OBJECT_CREATED, 
      new s3n.LambdaDestination(fizzbuzzFunc), 
        {suffix: ".json"}
      );
    fizzbuzzBucket.addEventNotification(
      s3.EventType.OBJECT_CREATED, 
      new s3n.LambdaDestination(fizzbuzzFunc), 
        {suffix: ".yaml"}
      );
    fizzbuzzBucket.addEventNotification(
      s3.EventType.OBJECT_CREATED, 
      new s3n.LambdaDestination(fizzbuzzFunc), 
        {suffix: ".yml"}
      );
  }
}
```

いやーシンプルですね！

### 閑話休題：AWS CDKとガバナンス

AWS CDKを導入したい理由のほとんどがいわゆる"効率面"だとは思いますが、個人的には、実は"ガバナンス強化"がデカいんじゃないかと思っています。

特にこういったサーバレスの世界ではファイアウォールのようなネットワーク的境界があるわけではないので、IAMの管理がセキュリティ上の肝だと思います。ところが今回のlambdaを作るとき、コンソールから作るときでもCloudFormationのテンプレートを直書きするときでも、ついLambdaのサービスロールに対して、`AmazonS3FullAccess`のような雑なポリシーを当てがちではないでしょうか？まあ、単に「めんどくさい」というパターンと「そもそもIAMがよく分かっていない」というパターンがあるとは思いますが。何れにしても、こういったことがセキュリティリスクであることには変わりないわけです。

ところが、CDKのスタック定義を見てください。

```typescript
    /**
     * fizzbuzz-bucketへの読み込み/書き込み権限をfizzbuzz-funcに付与する
     **/
    fizzbuzzBucket.grantReadWrite(fizzbuzzFunc);
```

この1行だけで、「適切なバケットに対して」「適切なポリシーを持つ」ロールが作成されるのです。実際にこのソースから作成されたサービスロールのテンプレートを見てみましょう。

```yaml
  fizzbuzzfuncServiceRoleD547EF86:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - Fn::Join:
            - ""
            - - "arn:"
              - Ref: AWS::Partition
              - :iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
    Metadata:
      aws:cdk:path: S3FizzbuzzStack/fizzbuzz-func/ServiceRole/Resource
  fizzbuzzfuncServiceRoleDefaultPolicy9977BB4B:
    Type: AWS::IAM::Policy
    Properties:
      PolicyDocument:
        Statement:
          - Action:
              - s3:GetObject*
              - s3:GetBucket*
              - s3:List*
              - s3:DeleteObject*
              - s3:PutObject*
              - s3:Abort*
            Effect: Allow
            Resource:
              - Fn::GetAtt:
                  - fizzbuzzbucketD2245D8E
                  - Arn
              - Fn::Join:
                  - ""
                  - - Fn::GetAtt:
                        - fizzbuzzbucketD2245D8E
                        - Arn
                    - /*
        Version: "2012-10-17"
      PolicyName: fizzbuzzfuncServiceRoleDefaultPolicy9977BB4B
      Roles:
        - Ref: fizzbuzzfuncServiceRoleD547EF86
    Metadata:
      aws:cdk:path: S3FizzbuzzStack/fizzbuzz-func/ServiceRole/DefaultPolicy/Resource
```

自作のテンプレートでここまで作り込むには、相当CloudFormationやIAMに精通している必要があるのではないでしょうか？熟練のAWS職人がわんさかいるプロジェクトならともかく、「初めてAWSに挑戦します！」みたいな若手だらけのプロジェクトにここまでの技術を期待しますか？

そんな状態に陥るくらいなら、コンソールからのLambda作成やCloudFormationの直書きを禁止した上で、CDKを使って`Bucket#grantReadWriteメソッド`を使用すること（`.grantRead`や`.grantWrite`もあります）を強制した方がよっぽどガバナンスが守られるわけです。もちろん効率面でも相当効果が高いですし、まさに一石二鳥ですね！

## deploy

早速デプロイしてみましょう！

```bash
$ cdk deploy
```

しばらく待つとスタックが出来ると思います。

## 動作確認

```test.yaml
from: 1
to: 100
```

```test2.json
{
  "from":100,
  "to": 200,
  "fizz": 5,
  "buzz": 7
}
```

*結果*
![スクリーンショット 2020-02-23 14.27.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290471/fbd8995d-cb5f-04bc-0250-659efb3bfb60.png)

上手くいってそうですね!!

# 考察

## デプロイされたlambdaのパッケージの依存関係が気になる

これまでも散々、

> package.jsonやnode_modulesをCDKと共用できる！

と書いてきましたが、その結果、デプロイされたlambdaのnode_modulesはどうなってるんでしょう...?
まさか、aws-cdk本体とか@types/xxxとか、要らないものまでまとめてコピーされてるんじゃ。。。

ということで、コンソールからデプロイされたlambdaを見てみましょう。

![スクリーンショット 2020-02-23 15.03.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290471/97b6f529-27d5-c0a9-2bef-290b5d255368.png)

おや...?そもそも`/node_modules`が無い？しかもindex.jsの中に書いた覚えのないコードが沢山。。

実は[リファレンス](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-nodejs-readme.html)にも書いてあるのですが、内部で[Parcel](https://parceljs.org/)というパッケージングツールが動いているみたいです。

Parcelについての詳しい説明は先人の素晴らしい記事があるのでそちらを見て頂くとして、

https://qiita.com/bitrinjani/items/b08876e0a2618745f54a

https://qiita.com/soarflat/items/3e43368b2d767c730781


どうもwebpackのように、依存関係を解決して一つのファイルにまとめてくれるツールのようです。

なるほど！これで元のnode_modulesの中から必要な（lambdaが依存している）パッケージだけ引っ張ってきてindex.jsにまとめデプロイしてくれるというわけですね。こんな[過去記事](https://qiita.com/tetsuya-zama/items/17674f387443c618f700)のようなことをしなくていいわけだ。。

# 所感

## "アプリ"と"インフラ"がボーダレスに

我々は伝統時に、サーバやネットワークなどの"インフラ"とそこで動くプログラムとしての"アプリ"を分離して仕事していましたし、今ままでいくら「サーバレス」といっても、CloudFormationで定義する"インフラ"とlamdbdaの関数として実行される"アプリ"にはまだ壁があったように思います。

この`aws-lambda-nodejs`のおかげでようやく、"アプリ"と"インフラ"の間に本当に壁が無くなって、同じTypeScriptという言語で同じアセットの中で同じ設定値(package.jsonやtsconfig.json)を共有しながら書けるようになったというのは、私にとって『新感覚』な出来事でした。

"Infrastructure as Code"ならぬ"Whole system as Code"とか"Everything as Code"とか呼んでもいいんじゃないでしょうか。

## TypeScriptの適用範囲の広さよ..

今やReact,Vue,Angular(およびReact NativeやNativeScript)のようなモダンなクライアントサイドはjavascriptでなくTypeScriptで書くのが当たり前となっていますし、この`aws-lambda-nodejs`のおかげでサーバサイドをTypeScriptに統一する動機も増えたということで、ますますTypeScriptを勉強するモチベーションが上がりますね。

いやもちろん、「マルチ言語」はマイクロサービスのメリットでもあるので、必要な局面で必要な言語（例えば、機械学習やるなら明らかにpythonですよね）を使えばいいとは思うのですが、むしろこういた「マルチ言語」の時代だからこそ、いわば『公用語』としてTypeScriptを選択するのは悪くないんじゃないかと思っています。

## CloudFormationのテンプレートを直書きするのはもはやアンチパターンか

「閑話休題」でも少し触れましたが、CloudFormationのテンプレートを自作するのはもはや苦行な上にガバナンス的なデメリットもあって、良いこと無い気がしてきました。

* [AWS CDK](https://aws.amazon.com/jp/cdk/)
* [AWS Amplify](https://aws.amazon.com/jp/amplify/)
* [Former2](https://former2.com/)

のような素晴らしいツールがある中で、CloudFormationのテンプレート直書きはもはや積極的に避けるべきなのかも知れません。

一方でこれらのツールの挙動をちゃんと理解したりトラブルシュートをするためにはCloudFormationに関する知識は必須であるわけで。。この辺りの抽象化のバランスは難しいですね。とはいえ、例えば私のようにプログラマを名乗っていてもアセンブラを書けない人が大多数の時代も来たわけなので、そのうちtemplate.yamlが「低級言語」と言われる時代が来るのかも知れませんね。