Serverless Framework + TypeSciprt + Jest で保守しやすい Lambda のコードを書く

# はじめに

Lambda のコード、つい適当に書いてしまいたくなります。

* ごちゃごちゃした手作業でデプロイしていたり ...
* Node.js (非 TypeScript) で書いて直しにくくなったり ...
* 1 ファイルに全部の処理を書いてしまったり ...
* Unit テストしていなかったり ...

修正不要なちょっとしたツールならいいんですが、そうでなければいつか後悔することになります。

これらの問題を Serverless Framework + TypeScript + Jest で解決します。

# Serverless Framework でデプロイを楽にする

Lamda のコードを書いて手でアップロードするのはめちゃくちゃ非効率です。

この問題は、Serverless Framework を使えば一発で解決します。[^1]

Serverless Framework では、デプロイが以下の 1 コマンドで完了します。

```console
$ sls deploy
```

これだけで使いたくなります。

インストールも簡単です。以下のコマンドを実行するだけです。

```
$ npm i -g serverless
$ sls version
1.41.1
```

# TypeScript で書く

## セットアップ

「TypeScript 興味あるけど導入大変そうだなー」と考えて Node.js (非 TypeScript) で書き始めると、プログラムを修正したくなったときに非常に辛い目に遭います。実際辛かったです。

Serverless Framework は TypeScript に対応しており、プロジェクトのテンプレートを 1 コマンドで生成できます。

```console
$ sls create -t aws-nodejs-typescript -p aws-serverless-typescript-sample
```

`-p` で指定した名前のディレクトリが作成され、中に各種テンプレートが生成されています。[^2]

```console
$ cd aws-serverless-typescript-sample
$ ls
handler.ts        package.json      serverless.yml    tsconfig.json     webpack.config.js
```

開発を進めるため、生成された package.json に書かれている依存関係をインストールしましょう。

```console
$ npm i
```

## Hello World

`sls create` では、Hello World のような関数も生成されています。

`sls invoke local` コマンドで、早速ローカルで実行することができます。

```console
$ sls invoke local -f hello
    :
    :
    :
{
    "statusCode": 200,
    "body": "{\n  \"message\": \"Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!\",\n  \"input\": \"\"\n}"
}
```

AWS 上へのデプロイも一発です。[^3]

```
$ sls deploy
```

# serverless-webpack で複数ファイルを 1 関数にまとめる

Serverless Framework で TypeScript を選択した場合、serverless-webpack が自動的に導入されています。

serverless-webpack により、`sls deploy` を実行した際に Webpack のバンドルが実行され、複数ファイルにまたがったコードを 1 つのファイルにまとめてくれます。

Lambda のコードを書く際におろそかになりがちだった、処理の共通化や通常の Web アプリケーションのようなレイヤ化が可能になります。

## レイヤ化の例

Lambda のコードを書く際、ついつい 1 ファイルに全ての処理を書いてしまいたくなりますが、しっかりレイヤを分離したコードを書くメリットは大きいです。

例えば、比較的シンプルな例として、以下のようなディレクトリ構成が考えられます。

```
src/
├── application ... アプリケーションサービスを配置
├── domain ... ドメインモデルを配置
├── handler ... Lambda に依存せざるを得ない handler を配置
└── infrastructure ... AWS サービス (DynamoDBなど) の呼び出しの実装を配置
```

上記のようにレイヤ化すれば、Lambda や AWS に依存する箇所からドメイン層とアプリケーション層を隔離することが可能になります。

つまり、Lambda であってもヘキサゴナルアーキテクチャやクリーンアーキテクチャ、DDD の戦術的設計などが採用可能になります。

# Jest で Unit テストする

最後に、Unit テストのために Jest をセットアップします。

## Jest のセットアップ

以下のコマンドで Jest をインストールします。

```console
$ npm i -D jest ts-jest @types/jest
```

プロジェクトのルートディレクトリに jest.config.js を配置します。

```jest.config.js
module.exports = {
  "roots": [
    "<rootDir>"
  ],
  "testMatch": ["<rootDir>/__tests__/**/*.test.ts"],
  "transform": {
    "^.+\\.tsx?$": "ts-jest"
  },
  "moduleFileExtensions": [
    "ts",
    "tsx",
    "js",
    "jsx",
    "json",
    "node"
  ],
}
```

## 実装

テスト対象のコードを書きます。

```src/domain/hello/hello.ts
export default class Hello {

  public greet(): string {
    return 'Hello World!';
  }

}
```

テストコードを書きます。

```__tests_/domain/hello/hello.test.ts
import Hello from '../../../src/domain/hello/hello'

test('hello', async () => {
  const hello = new Hello();
  expect(hello.greet()).toBe('Hello World!');
});
```

テストを実行すると ...

```console
$ npx jest
 PASS  __tests__/domain/hello/hello.test.ts
  ✓ hello (4ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.582s, estimated 2s
Ran all test suites.
```

無事 PASS しました。

# おわりに

結構簡単なセットアップで、Lambda のコードをしっかり書く環境を整えられます。

Lambda を本格的に使うのであれば Lambda Layers の利用なども入ってくるかもしれませんが、最初の一歩としてはこのくらいがいいかもしれません。

ソースコードは [こちら](https://github.com/os1ma/aws-serverless-typescript-template) です。

# おまけ

## セットアップのまとめ

セットアップは 1 つ 1 つ説明すると長かったですが、実はめちゃくちゃ簡単です。

### 1. プロジェクトの初期化と Jest インストール

```console
$ npm init
$ npm i
$ npm i -D jest ts-jest @types/jest
```

### 2. Jest の設定ファイル作成

```jest.config.js
module.exports = {
  "roots": [
    "<rootDir>"
  ],
  "testMatch": ["<rootDir>/__tests__/**/*.test.ts"],
  "transform": {
    "^.+\\.tsx?$": "ts-jest"
  },
  "moduleFileExtensions": [
    "ts",
    "tsx",
    "js",
    "jsx",
    "json",
    "node"
  ],
}
```

以上です。

あとは適宜コーディングしつつ、`sls invoke local -f <関数名>` でローカルで実行し、 `sls deploy` でデプロイします。

※ handler の追加・変更の際は serverless.yml の書き換えが必要です

[^1]: もちろん、SAM や Apex などでも OK です
[^2]: Serverless Framework のバージョンによって生成されるものは異なるようです
[^3]: AWS の credentials の設定が完了している前提です