ShallowDive Serverless Stack

[Ateam Lifestyle Inc. Advent Calendar 2021](https://qiita.com/advent-calendar/2021/ateam-lifestyle)の25日目は、
@masatomasato1224が担当します。

`AWS CDK`を利用してサーバーレスアプリケーションを構築できるフレームワークである`Serverless Stack`を触ったところ、`Live Lambda Development`がとっても便利だったので紹介したいと思います。

## モチベーション

最近、業務でサーバーレスアプリケーションを構築する機会がありました。
その際は、`ServerlessFramework`を利用したのですが、インフラ関連の業務経験が少ないこともあり`CloudFormation`のテンプレートを利用したインフラ定義に苦戦しました。 
そこで、`AWS CDK`をラップして簡単にサーバーレスアプリケーションを構築できるフレームワークである`Serverless Stack`に興味を持ちチュートリアルを触ってみました。
 
## ServerlessStackについて

### 特徴

https://docs.serverless-stack.com/

https://github.com/serverless-stack/serverless-stack

+ インフラ定義がラクラク
	+ `AWS CDK`をラップした独自のコンストラクタが用意されているのでコードの記載量が少ない
	+ 単体のAWSリソースはもちろん、Reactの静的サイトをホスティングするリソースセットのコンストラクタが用意されていたりする
+ 開発とデバッグがラクラク
	+ Webpack、Babel、TypeScript、テストフレームワーク、リンターがデフォルトで入っているのですぐにコードを書き始めることができる
	+ `Live Lambda Development`によりLambda関数をローカルで実行できる
		+ Visual Studio Codeでのブレークポイントを設定してデバックができる
		+ Lambdaの変更のみであれば、デプロイなしで結果を確認することができる

## LiveLambdaDevelopmentについて

https://docs.serverless-stack.com/live-lambda-development

+ Lambda関数をローカルでデバッグ、テストできる
+ `sst start`コマンドで実行

### ざっくりとした仕組み

`sst start`コマンドを実行すると以下のことが行われる

1. WebSocketAPIを持つデバッグスタックをデプロイする
1. アプリケーションスタックをデプロイする
1. アプリケーションスタック内のLambdaをスタブに置き換える
3. スタブがWebSocket APIを呼び出しだすことでローカル版のLambdaを実行する

![](https://storage.googleapis.com/zenn-user-upload/2280db514a21-20211223.png)
※ イメージは[公式サイト](https://docs.serverless-stack.com/live-lambda-development#sst-start)より引用


## LiveLambdaDevelopmentを試してみる

:::note warn
Node.jsのインストール、AWSアカウント及びプロファイルの設定を事前に行って下さい
:::

### 新規プロジェクトの作成

```sh:新規作成コマンド
npx create-serverless-stack@latest\
    2021-advent-calendar\
    --language typescript --use-yarn
```

```sh:実行後のディレクトリ構造
├── README.md
├── node_modules
├── package.json
├── src
├── sst.json
├── stacks
├── test
├── tsconfig.json
└── yarn.lock
```

### VSCodeのデバッグ設定

デバック実行時に、AWSプロファイルを指定して`sst start`コマンドを実行できるようにする

```sh:設定ファイルの生成
mkdir .vscode && touch .vscode/launch.json
```

[公式サイト](https://docs.serverless-stack.com/debugging-with-vscode#launch-configurations)で公開されている設定をコピペ

```json:.vscode/launch.json 
{
   "version": "0.2.0",
   "configurations": [
     {
       "name": "Debug SST Start",
       "type": "node",
       "request": "launch",
       "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst",
       "runtimeArgs": ["start", "--increase-timeout"],
       "console": "integratedTerminal",
       "skipFiles": ["<node_internals>/**"]
     },
     {
       "name": "Debug SST Tests",
       "type": "node",
       "request": "launch",
       "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst",
       "args": ["test", "--runInBand", "--no-cache", "--watchAll=false"],
       "cwd": "${workspaceRoot}",
       "protocol": "inspector",
       "console": "integratedTerminal",
       "internalConsoleOptions": "neverOpen",
       "env": { "CI": "true" },
       "disableOptimisticBPs": true
     }
   ]
}
```

環境変数にAWSのプロファイルを追加

```diff_json:.vscode/launch.json
{
   "version": "0.2.0",
   "configurations": [
     {
       "name": "Debug SST Start",
       "type": "node",
       "request": "launch",
       "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst",
       "runtimeArgs": ["start", "--increase-timeout"],
       "console": "integratedTerminal",
       "skipFiles": ["<node_internals>/**"],
+      "env": { "AWS_PROFILE": "private-aws" }
     },
     {
       "name": "Debug SST Tests",
       "type": "node",
       "request": "launch",
       "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst",
       "args": ["test", "--runInBand", "--no-cache", "--watchAll=false"],
       "cwd": "${workspaceRoot}",
       "protocol": "inspector",
       "console": "integratedTerminal",
       "internalConsoleOptions": "neverOpen",
-      "env": { "CI": "true" },
+      "env": { "CI": "true", "AWS_PROFILE": "private-aws" }
       "disableOptimisticBPs": true
     }
   ]
}
```

### 実行

`Debug SST Start`が選択されているこを確認して、`F5`キーで実行

![](https://storage.googleapis.com/zenn-user-upload/9fc8128815ba-20211223.png)

初回実行のみスタックのプレフィックスを尋ねられるので入力
※ 今回はmasa10Devを指定

```:ログの抜粋
Look like you’re running sst for the first time in this directory. 
Please enter a stage name you’d like to use locally. 
Or hit enter to use the one based on your AWS credentials 
(xxxxx):masa10Dev
```
:::note warn
プレフィックスの命名は「Pascal」もしくは「Camel」で指定してください
｢-｣や｢_｣を使うと怒られます
:::

プレフィックスの入力が終わるとデプロイが始まるので、`Debug session started. Listening for requests...`が表示されるまで待つ

```sh:ログの抜粋
Preparing your SST app
=======================
 Deploying debug stack
=======================
･･･

 ✅  masa10Dev-2021-advent-calendar-debug-stack

Stack masa10Dev-2021-advent-calendar-debug-stack
  Status: deployed
  Outputs:
    BucketArn: arn:aws:s3:::masa10dev-2021-advent-calendar-deb-bucket83908e77-xxxxx
    BucketName: masa10dev-2021-advent-calendar-deb-bucket83908e77-xxxxx
    Endpoint: wss://xxxxx.execute-api.us-east-1.amazonaws.com/masa10Dev
    
･･･

===============
 Deploying app
===============

 ✅  masa10Dev-2021-advent-calendar-my-stack

Stack masa10Dev-2021-advent-calendar-my-stack
  Status: deployed
  Outputs:
    ApiEndpoint: https://xxxxx.execute-api.us-east-1.amazonaws.com
...

==========================
 Starting Live Lambda Dev
==========================

# これが出るまで待つ
Debug session started. Listening for requests...
```

### 動作確認

アプリケーションスタックのApiEndpointに対して、リクエストを実行

```
curl https://xxxxx.execute-api.us-east-1.amazonaws.com
```

```:実行結果
> af6dab28-94f2-4d51-9449-8c672d5dd927 REQUEST masa10Dev-2021-advent-calenda-ApiLambdaGETB1714EF3-1oAEhE3VA39G [src/lambda.handler] invoked by API GET /
Debugger attached.
Debugger attached.
af6dab28-94f2-4d51-9449-8c672d5dd927 RESPONSE 
{"statusCode":200,"headers":{"Content-Type":"text/plain"},"body":"Hello, World! Your request was received at 22/Dec/2021:21:25:51 +0000."}
```

### Lambdaの編集をして動作確認

```diff_javascript:src/lambda.ts
import { APIGatewayProxyHandlerV2 } from "aws-lambda";

export const handler: APIGatewayProxyHandlerV2 = async (event) => {

+ console.log('ローカルのLambdaが実行されよー!!!')

  return {
    statusCode: 200,
    headers: { "Content-Type": "text/plain" },
-   body: `Hello, World! Your request was received at ${event.requestContext.time}.`,
+   body: `Hello, Masa10! Your request was received at ${event.requestContext.time}.`,
  };
};
```

もう一回リクエストを飛ばす

```
curl https://xxxxx.execute-api.us-east-1.amazonaws.com
```

再デプロイは行われていないがログが追加されて、レスポンスのbodyが変わっていることが確認できる

```:実行結果
bcac3775-0fce-4632-b145-3bd858c0af2c REQUEST masa10Dev-2021-advent-calenda-ApiLambdaGETB1714EF3-1oAEhE3VA39G [src/lambda.handler] invoked by API GET /
Debugger attached.
Debugger attached.
ローカルのLambdaが実行されよー!!!
bcac3775-0fce-4632-b145-3bd858c0af2c RESPONSE {"statusCode":200,"headers":{"Content-Type":"text/plain"},"body":"Hello, Masa10! Your request was received at 22/Dec/2021:21:34:31 +0000."}
```

### ブレークポイント

`src/lambda.ts`の5行目にブレークポイントを設置

![](https://storage.googleapis.com/zenn-user-upload/b28bddf8ee79-20211223.png)

もう一回リクエストを飛ばす

```sh
curl https://xxxxx.execute-api.us-east-1.amazonaws.com
```

ブレークポイントで処理が止まり、ステップ実行や変数の中身などを確認することができる

![](https://storage.googleapis.com/zenn-user-upload/f4930296c14b-20211223.png)

## 最後に

AWS限定にはなりますが、簡単かつ楽しくサーバレス開発ができるフレームワークではないかなと思います! 

編集した結果をデプロイすることなく実行してフィードバックを得られ、ブレークポイントを活用することでエラー調査やデバッグを効率的に行えるのは嬉しいポイントではないでしょうか?

今回は紹介しきれませんでしたが、インフラの定義に関してもかなり触りやすかったです。
特に、リソースの参照やパーミッションの設定が`CloudFormation`よりも格段に分かりやすいと感じました。 
また、スタックの分割も簡単にできるので、リソース種別などのグループで分けておくとでデプロイ時間が短縮されるので待ち時間を減らすことができます。

2021/12/22時点の最新バージョンが0.54.4なので、1.00.0がリリースされたらプロダクション環境でも使ってみたいと思います。

最後まで読んでくださってありがとうございました!!
皆様良い年末をお過ごしください!!

## 余談

チュートリアルがとっても丁寧で、AWSの各種リソースやReactについても端折らずに説明してくれているのでおすすめです。 
※ インフラ初学者の自分にはとっても有り難かったです。

https://serverless-stack.com/#guide