Slack と AWS の電話番号登録アプリ – Bolt + CDK ソースコード解説

こんにちは、櫻井です。

 

今回は前に公開した「SlackとAWSを組み合わせた電話番号登録アプリの開発 – Boltフレームワーク+CDKで作ってみた」で利用したBoltフレームワークとCDKのソースコードを解説します。アプリのデプロイ方法については上記のリンクから確認できます。

 

ソースコードを抜粋しながら解説を行います。ソースコードの全文を確認したい場合はGit Hubで公開しているので、こちらから確認してください!

 

 

CDK

ここでは、CDKのスタック層(lib配下のファイル)とアプリ層(bin配下のファイル)についての解説を行います。

CDKではL2コンストラクタを使用して記述しています。コードを書くときはこちらを参考にしました。

 

SlackAppRouterStack

スタック層にある、slackAppRouterStack.tsは本アプリのメインとなる部分です。

このスタックで作成されるリソースは以下の4点です。

  • Boltフレームワークを使うためのlambda
  • lambdaにアタッチするロール
  • API GateWay
  • ユーザの権限を管理するためのDynamoDBテーブル

 

以下のコードは “SlackAppRouterStackProps”という名前で、”Stack Props”を拡張しています。

“envName”, “projectName” などは、後ほど解説するアプリ層から値を受け取ります。

この “SlackAppRouterStackProps” を スタックの “constructor” の “props”にわたすことで、スタック内から外部の値を参照することができるようになります。

 

“const SLACK_BOT_TOKEN” は 前回の記事でSSMパラメータストアに保存した 値を取得し、変数に割り当てています。

TypeScriptでは、(バッククオート)内で ${変数名} を使うと文字列の中に変数を埋め込むことができます。つまり、今回の場合はSSMパラメータストアから “/slackAppRouter/geekBlog/SLACK_BOT_TOKEN”の値を取得しています。

 

“boltAppRole” では boltApp(lambda)にアタッチするロールを作成しています。CDKではlambdaを作成する場合、ロールを定義してアタッチしなくても自動でロールを作成してくれますが、今回は明示的に定義しています。理由は後述します。

“boltApp”ではlambdaを作成しています。

 

ここでは先ほど作成した”boltAppRole”に権限を追加しています。

“authorizationManagementUserTable.grantReadWriteData(boltAppRole)” は どのリソースの、どのような権限を、どのリソースに対して付与するかという構成になっています。

以下のコードの場合だと、”authorizationManagementUserTable” の読み取りと書き込み権限を “boltAppRole”に対して付与している。というふうになります。

 

今回のlambdaは自分自身を呼び出す権限が必要なため、以下のコードでboltAppRoleにboltAppの実行権限を付与しています。

 

“boltAppRole”に”boltApp”の実行権限を与えたいだけであれば以下のように権限を追加すればいいのではないかと思いますが、以下のような書き方をすると、なぜか循環依存を起こしてしまいます。

今のところ上記の方法以外で循環依存を解決する方法を見つけられていないため、上記のような書き方をしました。他に循環依存を解決するいい方法があった場合はアップデートする予定です。

 

ManagePhoneNumbersStack

ManagePhoneNumberStackは、電話番号管理アプリで、電話番号の追加、削除、一覧表示を行ったときに必要なリソースを定義しています。

このスタックで作成されるリソースは以下の2点です。

  • 電話番号を管理するDynamoDBテーブル
  • SlackAppRouterStackで作成したlambdaからのリクエストで起動するStepFunctions

 

以下のコードの “public readonly managePhoneNumbersStateMachine: StateMachine;” の部分は、このスタック内のmanagePhoneNumbersStateMachineを外部から参照できるようにするために必要なコードです。今回この値は、アプリ層で受け取り “slackAppRouterStack” に渡しています。

 

次にStepFunctionsの部分のコードを確認します。

StepFunctionsのワークフローは以下の画像のようになります。このステートマシンはboltAppのlambdaからjsonを受け取り、json内の”selected_type”で分岐します。

以下はboltAppから受け取ったjsonの”selected_type” が “delete”だった場合の処理です。”DynamoDeleteItem”というクラスがあるのでそれを利用して削除処理を行います。

 

 

以下はboltAppから受け取ったjsonの”selected_type” が “summary”だった場合の処理です。先程の”selected_type” が “delete” だった場合は “DynamoDeleteItem”というクラスを使いました。しかし今回は “CallAwsService” というクラスを使っています。”DynamoScanItem”のようなクラスがあればよかったのですが、今の所無いらしく、このような書き方になっています。

 

以下はステートマシンを定義している部分です。”stateMachineType”が “EXPRESS”の場合、明示的にログを残すコードを書かないとログが出力されないので、”logs”の部分を記述しています。

 

以下は”public readonly managePhoneNumbersStateMachine: StateMachine;”に上記で作成したステートマシンを割り当てています。

slack_app_router.ts

slack_app_router.tsはアプリ層にあたります。アプリ層はスタック層で定義したスタック郡のAWSリソースをデプロイするために必要なコードを実行する部分です。

 

1行目は、cdk.jsonから環境変数のキーを取得しています。デプロイ時にこのメソッドに環境変数のキーを渡すことで、cdk.jsonのcontextから環境ごとの値を取得することができるようになります。

2行目は取得した環境変数のキーを利用して、実際の環境変数の値を取得しています。

 

以下は”ManagePhoneNumbersStack”のリソースをデプロイするためのコードです。”envName”, “projectName”,  “authorityManagementChannelId”を cdk.json から取得して、propsに渡しています。

 

以下は”SlackAppRouterStack”のリソースをデプロイするためのコードです。”ManagePhoneNumbersStack”と違う点は propsで”managePhoneNumbersStateMachine” を定義し”ManagePhoneNumbersStack” で作成されたステートマシンの情報を渡しているところです。

 

Slack Bolt (lambda)

次に、Boltフレームワークを使ったソースコードについて解説していきます。

BoltはSlackワークスペースで発生するイベントを受け取り、そのイベントに対応する処理をすることができます。

Boltに関する基本的な概念はこちらにまとまっているので参考にしてみてください。

 

@app.event(“app_home_opened”)はアプリのホーム画面が開かれたときに動く処理です。

ここでは、ホームを開いたユーザがどのアプリの権限を持っているかを確認し、権限を持っているアプリのボタンだけを表示するような処理を行っています。

 

“アプリ権限管理”の権限しか持っていない場合は以下のように表示されます。

 

“アプリ権限管理” と “電話番号管理” の権限を持っている場合は以下のように表示されます。

“boltApp.py”はSlackワークスペースからのイベントに応じて処理を行う関数を登録するためのコードが含まれています。

“boltApp.py”ファイル自体には、具体的な処理の実装は含まれておらず、処理の実装は別のファイルに分割しています。

 

たとえば、以下は”アプリ権限管理” で “Submit”ボタンをクリックしたときに呼び出されれる”boltApp.py”の処理です。

“アプリ権限管理” で “Submit”ボタンをクリックしたときの具体的な処理は”authority_register.py” の “handle_request_authority_register_modal_view_events”に書かれています。
また、以下のコードは “アプリ権限管理” で入力された情報をイベントから取得して “selected_option”の値で分岐し、登録及び削除の処理と、誰が、誰に対して、どのアプリの権限を与えたかというメッセージを指定したチャンネルに送信する処理をしています。

 

 

以下はアプリのホーム画面で “電話番号管理” ボタンをクリックしたときの処理です。slack api はリクエストがあった時3秒以内にレスポンスを返さないといけないといけないという決まりがあるため、”ack”の部分でslackに対して先に空のレスポンスを返して、”lazy”で後続の処理を行います。

 

アプリのホーム画面で”電話番号管理” ボタンをクリックしたという情報は “common_view.py”にある “manage_phone_number_block” の “action_id”から取得しています。
“type”が “actions” の ブロックに”action_id” を設定し、上記のようにapp.actionの引数にそのaction_idを渡すことで、そのブロックに対してユーザが何かしらのアクションを行った場合の処理を設定することができるようになります。

 

 

 

次は “電話番号管理” のモーダルで “Next” をクリックしたときの処理です。

 

以下は上記の画面を描画するためのviewのコードです。

モーダルビューでは “callback_id”というプロパティを設定できます。callback_idは、文字列で指定された一意の識別子で、アプリケーションがモーダルの送信時に特定のモーダルを識別することができます。以下のコードでは、callback_idに “request_manage_phone_numbers_type_select_modal” が設定されています。

このモーダルが “submit” されると、Boltはこのcallback_idを使用して送信されたモーダルを特定し、適切な処理を行います。

 

以下は、”request_manage_phone_numbers_type_select_modal” を持つviewで “submit” されたときの処理です。

先程と同様に “boltApp.py” では具体的な処理は書かれていません。具体的な処理が書かれている別ファイルにルーティングするだけです。

 

具体的な処理は “manage_phone_numbers.py” に書かれています。

この関数では “request_manage_phone_numbers_modal_view” の “blocks”プロパティ内の “options” プロパティにある “value” から選択したタイプを判別して、それぞれの処理に分岐させています。

選択したタイプが “registration” だった場合は、電話番号登録処理に必要な情報を入力してもらうための新しい”manage_phone_numbers_registration_view” というview にモーダルをアップデートします。

選択したタイプが “summary” だった場合はStepFunctionsを起動して、DynamoDBに登録されているレコードをスキャンし、レスポンスをviewに格納してモーダルをアップデートします。

 

 

以下は選択したタイプが “registration” だった場合のモーダルです。

 

以下は選択したタイプが “sumarry” だった場合のモーダルです。

 

 

まとめ

今回はCDKとBoltフレームワークを使ったlambdaのソースコードの解説を行いました。

今回説明したこと以外でも、AWSとSlackを連携させることで、いろいろなことを簡単にできるようになると思うのでぜひ使ってみてください!

 

 

 

この記事が気に入ったら
いいね ! しよう

Twitter で
The following two tabs change content below.
櫻井
櫻井
2022年3月にギークフィードに入社。 エンジニア完全未経験からSAP・SAAを三週間で取得することが出来ました。そのためAWSに関することを中心に記事を作成する予定です。 自分が初心者だからこそわかる、エンジニア未経験の方や、エンジニアを始めたばかりの方の躓きポイントをうまく説明できるように頑張ります。

【採用情報】一緒に働く仲間を募集しています

採用情報
ページトップへ