いまさらSlackとLambdaとDynamoを連携させてみた

いまさらですが、LambdaとDynamoを触ってみたのでまとめておきたいと思います。
APIGateway/Lambda/Dynamoなど触ったことのない人の助けになれば幸いです。

今回やってみたことは以下です
![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/9dc00dfa-cf71-da91-4903-8555f830003e.png)
1. SlackのAppにメッセージを送信
2. Slackのevent apiでAPI Gatewayのエンドポイントにリクエストを送る
3. Lambdaで、飛んできたリクエスト内容をDynamoDBに保存
4. Lambdaで登録した旨をSlackに返信

## 作成手順
以下の順序で作成を行います

1. Lambda関数の作成
2. API Gatewayでエンドポイントを作成
3. Slackのevent APIの設定と、メッセージの送信
4. CloudWatchでログを確認する
4. Lambdaでのメッセージ処理
6. LambdaからDynamoにデータを登録

## 1. Lambda関数の作成
### Lambdaとは
https://aws.amazon.com/jp/lambda/
>AWS Lambda はサーバーレスコンピューティングサービスで、サーバーのプロビジョニングや管理、ワークロード対応のクラスタースケーリングロジックの作成、イベント統合の維持、ランタイムの管理を行わずにコードを実行できます。Lambda を使用すれば、実質どのようなタイプのアプリケーションやバックエンドサービスでも管理を必要とせずに実行できます。

凄く雑な説明をすれば、Lambda上にスクリプトを置いておくと、
こちらが指定したトリガー(HTTPリクエストなど)のタイミングでそのスクリプトを実行してくれます。
スクリプトを置いておくサーバーは意識する必要はなく、課金は実行回数によって発生します。
[月100万リクエストまで無料](https://aws.amazon.com/jp/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&all-free-tier.q=Lambda&all-free-tier.q_operator=AND&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all)なので、遊びで使う分には(多分)お金は発生しません。

### 関数の作成
AWSのマネジメントコンソールからLambdaを検索します。
「関数の作成」というボタンがあるはずなので、クリックして作成しましょう。
![スクリーンショット 2021-02-27 15.24.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/4022bd53-7f54-ffc5-e20f-27e22e6c1fb8.png)
こういう画面になるので、`関数名`と`ランタイム`の欄を入れて関数の作成をしましょう。
他に詳細設定などがありますが、今回は大枠を理解するだけなので、パスします。

作成後の画面に`関数コード`という画面があればOKです。
![スクリーンショット 2021-02-27 15.32.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/8f59ce62-1285-93a6-f0a2-7a175d9ff6b6.png)

### 関数のテスト
`Test`というボタンがあるので、`イベント名`に適当な名前を入れて作成
再度`Test`ボタンを教えてみましょう。
画面に以下のようなテスト結果が出ればOKです！
![スクリーンショット 2021-02-27 15.38.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/e36bb000-d77e-53de-9c9a-0ffd83ab77dd.png)

## 2. API Gatewayでエンドポイントを作成
### API Gatewayの設定

非常に簡素ですが、Lambdaの関数は作成できました。
次はLambda関数の実行を外からできるように、API Gatewayを設定します。

https://aws.amazon.com/jp/api-gateway/

Lambdaの関数の画面から、`デザイナ>トリガーを追加` をクリック
![スクリーンショット 2021-02-27 15.40.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/44511e47-7c6c-ac69-7672-c0e36c4420f5.png)

すると、トリガーを追加という画面がでてくるので、以下のように設定してください。
![スクリーンショット 2021-02-27 15.43.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/4d750857-d8e0-b339-f457-42473e3da7d8.png)

:warning: 大枠を理解するため、また後で消すのでオープンにしていますが、そうでない場合はきちんと設定してください。
作成後に`API Gateway`の項目にslack_sample_APIができていればOKです。



### エンドポイントの作成
API Gatewayのリンクをクリックし、設定画面に行きます。
![スクリーンショット 2021-02-27 15.49.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/b0142477-c418-4abe-0e44-8bc3c84c6b96.png)
アクションからメソッドの作成を行います。
POSTを選択した後、以下のように選択して、保存します
![スクリーンショット 2021-02-27 15.53.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/ef21b367-f0a3-f409-6c5e-ad0734bf1027.png)

作成後、再度`アクション`ボタンから`APIのデプロイ`を実行します。
デプロイされるステージを`default`を選択肢、デプロイします。
![スクリーンショット_2021-02-27_15_59_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/9eb250d2-e34b-c743-1a05-836150afd7a5.png)

このようなURLが表示されていればエンドポイントの作成は完了です。
とりあえず、テストでcurlを叩いてみましょう！
以下のように返ってきたら成功です！！

```
curl -X POST https://それぞれのURL/default/slack_sample
=> {"statusCode":200,"body":"\"Hello from Lambda!\""}%
```

## 3. Slackのevent APIの設定と、メッセージの送信
### Slackのアプリ作成
https://api.slack.com/apps

こちらからAppの作成
名前を適当に入れ、workspaceを設定します。
### request urlと、Lambada関数の修正
サイドバーの`Basic Information`から
`Add features and functionality>Event Subscriptions`を選択してください。
requestを送るURLを求められるので、先程作成したエンドポイントのURLを入れましょう。

すると、以下のように怒られます。
どうやら`challenge`というパラメータを返す必要があるようです
![スクリーンショット 2021-02-27 16.15.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/1fe6f063-ab93-bc16-6927-a9acdcc31f38.png)

Lambda関数のメソッドを、以下のように修正した後、`Deploy`ボタンを押しましょう。

```
def lambda_handler(event:, context:)
  # event["challenge"]がどこから来たかは、後述します
  { statusCode: 200, body: JSON.generate('Hello from Lambda!'), challenge: event["challenge"] }
end
```

デプロイ後にSlack画面の`retry`ボタンを押すと、Request URL Verified と表示されるはずです

### Subscribe to bot events
botの権限を設定します。
今回はBotへのメンションをトリガーにrequestを送信。Botぽくそれに返信をしたいので
![スクリーンショット 2021-02-27 17.29.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/28b2d214-8c30-84e4-bd7b-22f97e787079.png)

`app_mentions:read`と`chat:write`を入れておきます

### workspaceへのインストール
再び、`Basic Information`に戻り、
今度は`Install your app`からworkspaceにアプリをインストールします

### Slackでメッセージを送る
Bot的に使いたいので、適当なchannelにbotを入れて、
メンションをつけてメッセージを送ってみましょう

## 4. CloudWatchでログを確認する
実はLambda関数を作成したタイミングで

https://aws.amazon.com/jp/cloudwatch/

のロググループが出来ています。
AWSでCloudWatchを検索し、
サイドバーの`ログ>ロググループ`
から`/aws/lambda/slack_sample`を見てみましょう。

ログストリームの項目に、先程メンションを送った時間のものが増えていれば、
SlackからAPI Gatewayを介してLambda関数が実行出来ています！！

## 5. Lambdaでのメッセージ処理
Botぽく反応させたいのでSlackのスレッドに返信するようにしましょう。
とりあえず、メンション付きで「hoge」と送ったら、
「hogeと受信したよ！」とでも返すようにします。

### Slackからのeventを見る
slackからのメッセージをLambda上で処理したいので、requestがどんな形か知りたいです。
lambda関数を以下のように修正して

```
def lambda_handler(event:, context:)
    puts event
    { statusCode: 200, body: JSON.generate('Hello from Lambda!'), challenge: event["challenge"] }
end
```
デプロイしたあとに、
再度、slackでbotにメンション付きでメッセージを送りましょう。
CloudWatchのログに以下のようなものが出ていればOKです。

```
{
  "token"=>"hoge", 
  "team_id"=>"foo", 
  "api_app_id"=>"fuga", 
  "event"=>{
    "client_msg_id"=>"1234", 
    "type"=>"app_mention", 
    "text"=>"<@id> hoge", 
    "ts"=>"1614412451.000800",
  ~~~~~略~~~~
} 
```

### スレッドに返信する
#### Token
返信するためにSlackのTokenが必要です。
Slackアプリ画面のサイバーの
`Features>OAuth&Permissions`から`Bot User OAuth Token`をメモしておきます。

#### コードの修正
Lambda関数を

https://api.slack.com/methods/chat.postMessage

と、先程のCloudWatchのログを参考に、以下のように書き換えます。

```
# サンプルなので、ここに書いていますが本当は適切に暗号化する必要があります
SLACK_TOKEN = "token"
def lambda_handler(event:, context:)
  event_data = event["event"]
  # メッセージにメンションがついてしまうため、送信されたテキストからIDを削除
  text = event_data.dig("text").delete("<@id>")
  
  channel = event_data.dig("channel")
  params = 
    {
      token: SLACK_TOKEN,
      channel: channel,
      as_user: true,
      text: "#{text}と受信したよ！",
      thread_ts: event_data["ts"]
    }
  
  uri = URI.parse("https://slack.com/api/chat.postMessage")
  Net::HTTP.post_form(uri, params)
  { statusCode: 200, body: "test", challenge: event["challenge"] }
end
```

これで、Slack API → API Gateway → Lambda →Slackの流れができました！

## 7. LambdaからDynamoにデータを登録
最後に、なんとなくDynamoにSlackのメッセージを入れてみましょう！
### Dynamoとは

https://aws.amazon.com/jp/dynamodb/

KeyValue型のNoSQLです。集計などには向きませんが、1件のデータの登録、抽出に優れています。

### テーブルの作成
Dynamoの画面から`テーブルの作成`をクリック
テーブル名とプライマリーキーを入れます。
他の設定はデフォルトでOKです。

テーブルが作成できたら、`項目の作成`から増やしてみます。
今回はtextを増やしてみました。
![スクリーンショット 2021-02-27 18.56.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/1efdaeda-e5f1-9ff0-1bc2-13904247c18d.png)


### Lambdaの設定変更
このままだとLambdaからDynamoにまだアクセスできないのでポリシーを追加します
Lambda関数の画面の`アクセス権限`のタブから`実行ロール`のロール名をクリック
IAMの画面からポリシーをアタッチします。
![スクリーンショット 2021-02-27 18.30.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/0ceccb96-6319-22ca-6b4d-1e2d197b79ce.png)

:warning: 自分しか使わないアカウントなのでFullAccessですが、本当は良くないです。

### Lambda関数の修正
後は、

https://docs.aws.amazon.com/sdk-for-ruby/v2/api/Aws/DynamoDB/Client.html

を参考に、
Lambda関数を以下のように修正します

```
require 'json'
require "net/http"
require 'aws-sdk-dynamodb'

SLACK_TOKEN = "token"
def lambda_handler(event:, context:)
  event_data = event["event"]
  # メッセージにメンションがついてしまうため、送信されたテキストからIDを削除
  text = event_data.dig("text").delete("<@id>")

  # Dynamoへのinput
  dynamodb = Aws::DynamoDB::Client.new(region: 'ap-northeast-1')
  item = {
    text: text,
    sender_id: event_data["user"],
  }
  params = {
    table_name: 'dynamo_table_name',
    item: item
  }
  dynamodb.put_item(params)
  
  # slackへの送信
  channel = event_data.dig("channel")
  params = 
    {
      token: SLACK_TOKEN,
      channel: channel,
      as_user: true,
      text: "#{text}と受信したよ！",
      thread_ts: event_data["ts"]
    }
  
  uri = URI.parse("https://slack.com/api/chat.postMessage")
  Net::HTTP.post_form(uri, params)
  { statusCode: 200, body: "test", challenge: event["challenge"] }
end
```

ここまでうまく行っていれば、このように
Dynamoにレコードが登録され、
![スクリーンショット_2021-02-27_18_39_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/80880a31-7caa-bd10-abb1-d85a92e454f8.png)

Slack上でも返信が来ていると思います。
![スクリーンショット_2021-02-27_18_36_58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/874795/167a70d9-b5e5-d98f-6f09-4bcb28908155.png)

とりあえずしたかったことは出来たので、完了です。
## 最後に
やっぱり、インフラが分かって無くても、AWSがいい感じにしてくれるのはすごいですね。
工程も簡単で、この記事を書きながらしても3時間ぐらいで終わりました。
多分、作業だけすれば1時間ぐらいで終わると思います。

大枠を理解するために雑な紹介でしたが、
IAMの設定や、API Gatewayのアクセス制限、Lambda関数が失敗したときの処理など
本当はしないといけないことが、実はまだまだあります。
今回の記事で興味を持ってくれた人がいたら、ぜひやってみください。
「LambdaやDynamo全然わからないけど、なんか触ってみたい！」という人に役立てばば幸いです。