serverlessでslackbotローカル開発環境構築 - serverless-offline編 -

#何を作ろうとしている
slack apiフレームワークのboltをAWS Lambda上で動かして、さらにはDynamoDBとかも連携したSlackbotを作ろうとしている。

#目指す開発環境

####ここから
マジで何も分からないところからどうにかこうにかserverlessを使ってAWSに簡単なlambda関数をデプロイ出来るようになりました
でも結構速いPCでもデプロイに一回1分以上かかる

####こうなった！

serverless-offlineを使って、コードの編集が5秒ほどで自動で反映されてすぐ試せるローカルサーバーを立ち上げた。それをngrokでWEB上にURL公開、Slackからのエンドポイントに指定してローカルの方にリクエストを投げて逐次試しながら開発できる。

**この記事はserverless-offlineでローカル開発環境をserverlessの設定から立ち上げるところまでの備忘録です。**


#serverless-offlineとは

https://github.com/dherault/serverless-offline

AWS LambdaとAPI Gatewayのエミュレーターです。
指定したローカルホストのポート番号+AWSにリクエストを投げる時と同じpathにエンドポイントが生成され、curlなりPostmanでリクエストを投げればAWSと同じように処理されレスポンスが返ってきます。

なおDynamoDBと連携しているLambda関数の場合はこのserverless-offline pluginだけではダメで、DB用のプラグインを併用する必要があるようです。
その辺りも今後必要になってくるので、いずれ記事にまとめたいと思います。

#導入方法

とりあえずインストールからの

```zsh
npm install serverless-offline --save-dev
```
からの.ymlファイルにプラグイン読み込みましょう。

```yml:serverless.yml
plugins:
  - serverless-offline
```

## credentialファイル、ドキュメントに書いてないけど必要

credentialファイルとは、要するにAWSへのログイン情報が書いてあるローカルに置いたファイルで、基本的にserverlessを導入する過程でもう用意されてる場合が多いと思うんですが、今回私はserverlessがweb上のコンソールから提供しているAWSとの連携機能を使っており、serverless自体はcredentialファイルを使わずに済んでいたため、offline用に用意する必要がありました。

ちなみに無しで実行しようとするとこんな感じのエラーが出ます。

```zsh
 CredentialsError: Missing credentials in config, if using AWS_CONFIG_FILE, set AWS_SDK_LOAD_CONFIG=1
```

###credentialファイルの作り方
1. まずaws cliをインストールします。しなくても多分作れますが。
2. グローバルにインストールされていることを確認したら`aws config`コマンドを使って指定項目を設定します。
3. 完了するとルートフォルダの.awsフォルダにcredentialとconfigというファイルが生成されています。

ちなみに設定する`aws_access_key_id`と`aws_secret_access_key`ですが、
IAMコンソールでLambdaを動かす権限を持ったユーザーのページに移行、「認証情報」タブ内の「アクセスキーの追加」ボタンを押すと両方確認することが出来ます。


## Lambda.invokeを使う方法

Lambda.invokeとは、lambda関数内で別のlambda関数を呼び出すメソッドです。
これはデフォルトの設定では使用できないようになっています。

使用方法が[公式ページに項目を用意して説明されている](https://github.com/dherault/serverless-offline#usage-with-invoke)のですが、どうにも上手くいかなくて、他のissueを見て解決した自分の設定を書いておきます。

```yaml:serverless.yml
custom:
  serverless-offline:
    # httpPort: 3000 //defaultが3000なので指定しなくてもOK
    # lambdaPort: 3002 //Default: 3002なのでこれも不要
    endpoint: http://localhost:3002
```
ymlファイルの方にはプラグインの設定としてエンドポイントだけを設定しておきます。
デフォルトではないポートを使いたい人は適宜コメントアウトしている部分を書き換えて上書きしてください。
ここでコメントアウトされてるものの大事なのが`lambdaPort`なる設定です。

`--lambdaPort                Lambda http port to listen on. Default: 3002`

このリファレンスを見ても正直よく理解できていないのですが、
実際にリクエストを投げる`httpPort：3000`とは別に`lambdaPort: 3002` なるものを設定して、それをendpointパラメータに与えたLambdaクラスを初期化すればofflineでもinvokeが使えるようになるらしい...

嬉しいのはこの設定をすると、公式で説明されてるようなnew Lambda()側に引数を渡すといった処理がいらないんですよね。

**参考：[Class: AWS.Lambda](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html)**

> **endpoint (String|AWS.Endpoint)** — The endpoint URI to send requests to. The default endpoint is built from the configured region. The endpoint should be a string like 'https://{service}.{region}.amazonaws.com' or an Endpoint object.

endpintパラメーターはデフォルトだとservice名とregionから生成されるURLなので、offlineの時だけ`lambdaPort`を指定してくれてるって感じなのかしら。

ちなみにserverless-offline起動中のlambda.endpointの中身を見てみると、ローカルホストのポートで動いてるのが分かりますね

```ts
console.log(JSON.stringify(lambda.endpoint))
```

```bash
{"protocol":"http:","host":"localhost:3002","port":3002,"hostname":"localhost","pathname":"/","path":"/","href":"http://localhost:3002/"}
```



##invokeに渡すパラメーター

[serverless+typescriptの記事の型定義ファイルの入れ方](https://qiita.com/herishiro/items/b005bfef55313c677b89#%E5%9E%8B%E5%AE%9A%E7%BE%A9%E3%82%92%E4%BB%98%E3%81%91%E3%81%9F%E5%80%A4%E3%81%ABserverlessyml%E3%81%AE%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%82%92%E5%85%A5%E3%82%8C%E3%82%8B%E6%99%82%E3%81%AE%E6%B3%A8%E6%84%8F)でも説明していますが、
invokeする関数のFunctionNameは、serverless.ymlで定義している「サービス名-ステージ名-functionsで定義した関数名」で生成されるので、serverless.ymlでファイル内参照しながら書く方が楽なんですよね。こんな感じで。
`${self:service}-${self:provider.stage}-<functions内で定義した関数名>`

というわけでこんな感じでパラメーターは指定しています。

```ts:handler.ts
  const params: AWS.Lambda.InvocationRequest = {
    FunctionName: process.env.INVOKE_FUNCTION!,
    InvocationType: "RequestResponse",
    Payload: JSON.stringify({ msg: "Hello" }),
  };
```

```yaml:serverless.yml
provider:
  environment:
    INVOKE_FUNCTION: "${self:service}-${self:provider.stage}-backend"
```

##ローカルと本番で処理を分岐したい時のIS_OFFLINE変数

serverless-offineを実行中、`process.env.IS_OFFLINE`がtrueになります。
これはこちらから環境変数になにか設定したりする必要はないです。

諸々、ローカルの時しか使わない設定や関数の条件分岐に使えます。

---
これで単純にエンドポイントにcurlでクエリを投げれば動く関数ならローカルで開発できる環境が整いました。
slack bot (bolt)の場合はリクエストがslackからのイベントになるため、そのリクエストURLはWEB上に公開されている必要があります。
それをngrok（エングロックって読むらしいですね）を使って実現する手順は別の記事でまとめたいと思います。

以上です。