本エントリは、バックエンドにAWS Lambdaを用いたSlack Botを開発するときのTipsを雑多に垂れ流していくものです。普段の記事と異なり、エントリとしての主張のまとまりに関してはほとんど考慮していません。
- Slackで使えるインタフェースを把握しよう
- Slack Appの設定はコード化しておこう
- レスポンスは3秒以内にSlackに届けよう
- Slackの再送による多重実行を回避しよう
- AppからSlackにメッセージを投稿する場合、エラーハンドリングの方針を考えよう
- リンク集
Slackで使えるインタフェースを把握しよう
Slack Appを作るとき、我々はSlackの仕様に縛られることになります。Slack側が用意してくれているインタフェースを使ってユーザはAppとやりとりすることになります。
ただ、Slack Appでは思ったよりもリッチなアプリケーションを作れます。作りたいものが決まって余裕があるなら、Slackが提供しているユーザインタフェースのうちどれを使うと良い体験になるかを考えてみると良いでしょう。(そもそも、こんなUIが実現できるんだという存在を認識するところがスタートです。)
- メッセージ
- スラッシュコマンド
- モーダル(アクション)
- ホームタブ
- ショートカット
- ワークフロー
もちろん、UIがリッチになっていくほど実装は大変になります。作り始めから特定のUIに依存しない形でサービスロジックのコーディングができていると、他のUI経由にしたいときも実装を変えやすいです。
Slack Appの設定はコード化しておこう
複数人で開発すると、どうしてもSlack Appの設定(エンドポイントURLや必要な権限など)の情報を各人で共有することが難しくなります。開発環境と本番環境など、同じような設定を複数のSlack Appにしたいならば尚更です。
インフラ構成をIaC (Infrastructure as Code) で管理したくなるのと同様に、Slack Appの設定はmanifestファイルとして管理することができます。
個人でお遊びのものを作る分には良いのですが、業務で使うようなAppの開発は引き継ぎできるかも大事なポイントの1つです。設定をコード化して属人化を防ぎましょう。
レスポンスは3秒以内にSlackに届けよう
スラッシュコマンド実行時やModalでの送信時に、SlackからSlack Appに対してリクエストが送られます。API Gateway経由で起動したLambdaでそのリクエストを処理してレスポンスをSlack側に返してあげる必要があります。
ここで、リクエスト送信からレスポンス到着までの時間が3秒以上かかってしまうと、Slack上では「何らかのエラーが発生しました」と表示されてしまいます。ある程度時間がかかってもレスポンスさえ返せばSlack上での画面変化が正常に行われるのですが、エラーと表示されてしまうとユーザとしては不安ですよね。
アルゴリズムの改善や実行に必要なファイル群のサイズを減らすなどによって実行時間を減らすことができますが、これらの細かい工夫だけで解決ができるかは保証できません。何より、他のAPIにアクセスして結果をもらうという典型的な処理においては、そのアクセスのレイテンシも乗っかってくるので、自分たちがいくら努力しようともどうしようもないことがあるでしょう。
以下の記事で紹介されている通り、レスポンスを受け取ったらすぐに空の200を返してしまい、その後で黙々と処理をするのが良さそうです。
また、金銭的余裕があるなら、Lambdaのコールドスタートをできる限り回避するためにプロビジョニング済み同時実行を使うなどの工夫も考えられます。(それだとLambdaの良さ台無しでは?というところではありますが。)
なお、Slack Appレベルの規模の場合、ランタイムの選択によって3秒問題を解決できるほど大きく実行時間が変わるということはないと考えています。JavaScriptでもGoでも好きな言語を選ぶと良さそうです。(ただしJavaは微妙かも。)
Slackの再送による多重実行を回避しよう
「レスポンスは3秒以内にSlackに届けよう」にも関連するのですが、SlackがSlack Appのエンドポイントにリクエストを送った結果エラーとなった場合、Slackは自動でリクエストを再送します。これは、3秒以内にレスポンスが届かなかったとき(http_timeout)にも起こるので、上の問題と絡み合って不具合の原因特定と解消が困難になってしまいがちです。
Slackのお節介な仕様が必要でなければ、再送された場合のリクエストに対しては処理せず200を返すだけにしてしまいましょう。概ね、以下の記事の「パターン3, そもそも再送を無視する方法」に紹介されている通り、ヘッダを見て再送しているリクエストかどうかを判別するのが良さそうです。
AppからSlackにメッセージを投稿する場合、エラーハンドリングの方針を考えよう
Slackを通じて何らかのアクションを行う場合、きっとSlackのメッセージで結果を返したくなるでしょう。もし、バックエンドのメインロジックの処理が成功したのに、その後でSlackのエラーが発生してレスポンスがエラーになると、一見処理は正しくできているのにSlack上では「エラーが発生しました」と表示されてしまいます。
で、Slackのエラー(もっと具体的に言うと、LambdaからSlackのAPIを叩くときのクライアントエラー)なんて起こるのかというと、意外と起こる可能性があります。例えば、SlackのAPIは短い時間でたくさん呼ぶとthrottlingエラーを返します。1回のアクションに対して複数回postするような場合には注意が必要です。
補助的にSlackのメッセージを送りたいのであれば、あえて、Slackの投稿に失敗してもLambdaの実行失敗としないようにエラーを隠してしまうのも一つの手だと思います。
逆にSlackにメッセージを送るのがMustな要件の場合は、キューサービス(AWS SQSなど)を挟んで投稿専用Lambdaを作るとよいでしょう。投稿用Lambdaは投稿に失敗したら異常終了するようにし、Queueに再送することでthrottlingなどによる投稿失敗を防ぐことができます。何回も失敗する場合には何かがおかしいので、Dead Letter Queueに入れて元のキューが詰まらないようにし、DLQのメッセージ数をモニタリングしてアラートを出すと良さそうです。