Boltで実装したアプリをCDKでAPI GatewayとLambdaにデプロイしてみた
こんにちは。坂井です。
最近Slackアプリを調査する機会があり、色々と調査を進めている中で、Boltに触ってみたいなぁと思ったので、Bolt 入門ガイドを見ながら、アプリを作ってみました。
アプリを実装している段階ではローカルで起動して、動作確認を実施したのですが、どこかの環境にデプロイしたかったので、CDKを使って、サクッとデプロイできないかなぁと思って調べてみたのですが、シンプルなCDKのデプロイサンプルを見つけることができなかったので、実際にやってみました。
実装イメージ
開発環境
- Node.js
- 12.16.1
- Bolt:
- 2.4.1
- CDK
- 1.74.0
- TypeScript
- 3.9.7
Slackアプリの作成
まずはSlackアプリを作成します。こちらの入門ガイドの通り、Slackアプリを作成します。
主に設定した部分は以下となります。
OAuth & Permissions
で、Bot Token Scopes
にchat:write
を追加Event Subscription
でEnable Events
を有効
に設定Request URL
は、ローカル実行時はngrokでフォワードするURL、AWSで実行する場合は、API Gatewayのエンドポイントを設定する
Interactivity & Shortcuts
でInteractivity
を有効
に設定
Boltアプリを実装
続いてBoltでアプリのコードを以下のように実装します。このソースの中で、
- メッセージのリスニングと応答をする
- アクションの送信と応答
- アプリをローカル起動する
- Lambdaで動作するように
aws-serverless-express
を利用してプロキシする
といった処理を実装しています。
基本的には、Boltの入門ガイドと同等のコードとなりますが、Lambdaで動かすために、aws-serverless-express
を利用してプロキシする処理を追加しています。
import { App, ExpressReceiver } from "@slack/bolt"; import * as awsServerlessExpress from "aws-serverless-express"; import { APIGatewayProxyEvent, Context } from "aws-lambda"; const processBeforeResponse = true; const expressReceiver = new ExpressReceiver({ signingSecret: process.env.SLACK_SIGNING_SECRET ?? "", // Lambdaの環境変数から取得 processBeforeResponse, }); const app = new App({ token: process.env.SLACK_BOT_TOKEN, // Lambdaの環境変数から取得 receiver: expressReceiver, processBeforeResponse, }); const server = awsServerlessExpress.createServer(expressReceiver.app); export const handler = ( event: APIGatewayProxyEvent, context: Context ): void => { awsServerlessExpress.proxy(server, event, context); }; // メッセージに"hello"が含まれていたら実行する処理 app.message("hello", async ({ message, say }) => { await say({ blocks: [ { type: "section", text: { type: "mrkdwn", text: `Hey there <@${message.user}>!`, }, accessory: { type: "button", text: { type: "plain_text", text: "Click Me", }, action_id: "button_click", }, }, ], text: `Hey there <@${message.user}>!`, }); }); // action_idが"button_click"のアクションが実行された際に実行する処理 app.action("button_click", async ({ body, ack, say }) => { // Acknowledge the action await ack(); await say(`<@${body.user.id}> clicked the button`); }); // ローカル起動時に実行するコード if (process.env.IS_LOCAL === "true") { (async () => { // Start your app await app.start(process.env.PORT || 3000); console.log("⚡️ Bolt app is running!"); })(); }
・・・ "devDependencies": { "@aws-cdk/assert": "1.74.0", "@aws-cdk/aws-apigateway": "^1.74.0", "@aws-cdk/aws-lambda-nodejs": "^1.74.0", "@types/aws-lambda": "^8.10.64", "@types/aws-serverless-express": "^3.3.3", "@types/jest": "^26.0.10", "@types/node": "10.17.27", "aws-cdk": "1.74.0", "jest": "^26.4.2", "prettier": "^2.1.2", "ts-jest": "^26.2.0", "ts-node": "^8.1.0", "typescript": "~3.9.7" }, "dependencies": { "@aws-cdk/core": "1.74.0", "@slack/bolt": "^2.4.1", "aws-serverless-express": "^3.3.8", "source-map-support": "^0.5.16" } ・・・
デプロイ
Boltアプリはできたので、このアプリをLambdaにデプロイするためにCDKで必要なリソースを定義して、デプロイします。
リソース定義としては、最低限の必要なリソースである、上記で実装したBoltアプリのLambdaとAPI Gatewayのみとなります。
Lambdaの依存関係については、@aws-cdk/aws-lambda-nodejs
のNodejsFunction
を利用して、デプロイ時にバンドルされるように実装しています。
import * as cdk from "@aws-cdk/core"; import { NodejsFunction } from "@aws-cdk/aws-lambda-nodejs"; import * as apigateway from "@aws-cdk/aws-apigateway"; export class SlackBoltCdkSampleStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const appLambda = new NodejsFunction(this, "appLambda", { entry: "src/lambda/handlers/app.ts", handler: "handler", environment: { SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN || "", SLACK_SIGNING_SECRET: process.env.SLACK_SIGNING_SECRET || "", }, }); new apigateway.LambdaRestApi(this, "slackApi", { handler: appLambda, }); } }
・・・ "scripts": { "build": "tsc", "watch": "tsc -w", "test": "jest", "bootstrap": "cdk bootstrap", "deploy": "cdk deploy --require-approval never", "dev": "IS_LOCAL=true ts-node src/lambda/handlers/app.ts" }, ・・・
以下のコマンドを実行してデプロイします。
※初回デプロイ時は、cdk bootstrap
が必要な場合があります。
npm run deploy
なお、ローカルで起動する際は以下のコマンドを実行して起動します。(環境変数指定しているだけですが。。。)
npm run dev
動かしてみる
デプロイできたら、実際に動かしてみます。動かす前にSlackアプリのEvent Subscriptions
のRequest URL
とInteractivity & Shortcuts
のRequest URL
がデプロイしたエンドポイントに設定する必要があります。
例えば、API Gatewayのエンドポイントが、https://XXX.execute-api.ap-northeast-1.amazonaws.com/prod
の場合、https://XXX.execute-api.ap-northeast-1.amazonaws.com/prod/slack/events
を設定します。
※エンドポイントの末尾に/slack/events
を追加しています。
では、実際に動かしてみます。Slackアプリの設定でダイレクトメッセージをリッスンするようにSubscribe to bot events
にmessage.im
を追加してありますので、アプリにhello
というダイレクトメッセージを送ってみます。
hello
というメッセージに対して、Hey there @[ユーザー名]!
というメッセージとボタンが表示されました。ボタンをクリックすると、@[ユーザー名] clicked the button
というメッセージが表示されることを確認できました。
期待したとおりの動きとなりました。
さいごに
今回は、Bolt入門ガイドに沿ってシンプルなアプリをLambdaで動かしたくてやってみましたが、少し工夫すれば特に問題なくデプロイできることを確認できました。もう少し複雑なアプリになると実装上の工夫が必要になるかと思います。
まずはシンプルなBoltアプリをCDKでLambdaにデプロイしてみたいという方の参考になれば幸いです。
今回サンプルで作成したソース一式は以下に配置してありますがので、興味を持たれた方は実際に動かしてみてください。