DiscordアプリをAWS SAMで作った話(概要・システム構成編)

## はじめに

作成したのは、マップ上をボタンコンポーネントで移動し、宝石を探すゲームです。

![play.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3781648/f295ca1e-3c36-fd04-16a6-bdf8ab0d16ab.gif)

※ [ゲームインストールリンク](https://discord.com/oauth2/authorize?client_id=1229496306360123392)

最近[IEOなどで話題](https://gamebiz.jp/news/387484)になった[ブリリアントクリプト(Brilliantcrypto)](https://discord.gg/brilliantcrypto)というゲームのDiscordコミュニティに、発表当初から参加しています。

２０２３年末のβテスト後、リリースや各種情報解禁までの間、コミュニティが閑散とした時があったのですが、その時にネタ的に作り始めたファンゲームです。

Brilliantcryptoは"Play to Earn"の側面が注目されていますが、ゲーム自体もなかなか楽しめます。その中でもレーダーの反応から宝石の場所を探る要素が気に入っていたので、レーダーを使った宝探しゲームにしてみました。

開発も一区切りしていて、興味を持ってくださった方から質問をいただくこともあり、技術項目について記事にすることにしました。今回はその中でも概要的な内容になります。

なお、私はβテストに参加しましたが、Brilliantcrypto運営でも関係者でもありませんので、その点はご注意ください。（運営さんに迷惑がかかるといけないので）

また、一応書いておきますが、本前書き？はBrilliantcryptoに関連したNFTや仮想通貨の投資を推奨するためのものではありません。

## 主なリソース
主に３つのリソースから構成されています。
- １段目のLambda関数(関数URL有）
- ２段目のLambda関数
- DynamoDB

上記に伴い、暗黙的に作成されるリソースに加え
- １段目のLambdaが２段目のLambdaを呼び出すポリシー
- ２段目のLambdaがDynamoDBを操作するためのポリシー
も作成しています。

上記をソースコードと共に、AWS SAMを使って同一リポジトリで管理しています。
なお、開発言語はNode.jsで、Discord.jsなどの定番ライブラリは利用していません。

## 構成図
図を書くほどのものでもないですが、せっかくなのでChatGPTに作成してもらいました。
色々な種類の図を書いてもらったのですが、あまりしっくりするものが出せず、比較的わかりやすかったシーケンス図を掲載して起きます。

```mermaid
sequenceDiagram
    participant User as User
    participant Discord as Discord
    participant Lambda1 as Lambda1
    participant Lambda2 as Lambda2
    participant DynamoDB as DynamoDB

    User->>Discord: コマンド・ボタン操作
    Discord->>Lambda1: Invoke Lambda1
    Lambda1->>Discord: DEFERRED MESSAGE
    Lambda1->>Lambda2: Invoke Lambda2
    Lambda2->>DynamoDB: Read/Write Data
    DynamoDB-->>Lambda2: Return Data
    Lambda2->>Discord: UPDATE MESSAGE

```
## 処理概要
- Discord上のスラッシュコマンド、ボタンコンポーネント操作などをトリガーにAPIが呼び出されます。
- １段目のLambda関数は"INTERACTIONS ENDPOINT URL"になります。
- １段目のLambda関数はリクエスト内のシグネチャを検証し、正当なアクセスか検証を行います。
- 検証に成功すると２段目のLambda関数を呼び出し、Discord側には仮のメッセージを返します。
（DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE）
- ２段目のLambda関数は１段目のLambda関数から呼び出されます。
アプリケーションの本処理を受け持ち、処理が完了すると仮メッセージを更新します。

## 特徴
### サーバーレス構成、SAMを選んだ理由
個人開発のため固定費をかけたくないのが理由です。
本業の隙間時間になるので、開発時間を安定かつ継続的に確保できません。
無駄な費用を抑えるためにサーバーを休止させると、開発を再開する際の心理的なハードルが意外と高くなります。
気持ちの問題、熱意があれば苦にならない！と言われればそれまでですが、いつでも動かせるものがあり、思い立ったらすぐ作業できるというのは、モチベーションの維持に役立ちました。

また同様の観点で記憶の問題があります。
仕様や運用手順を忘れると、せっかく確保できた時間が調査に費やされます。
対策としてドキュメントを作成するのも面倒です。（続かない）
そこでSAMを使ってコードとリソースの管理を集約しました。
その他簡単なドキュメントはREADMEかコードにコメントで書いて済ませています。

そして、個人開発をしていると不要なリソースが溜まりがちです。
定期的に棚卸しをする際の調査も面倒ですが、SAMならプロジェクト単位で一括して削除できます。

## Discord.jsなど著名なライブラリを使わなかった理由
１つ目はDiscord公式のサンプルコードとチュートリアルを応用するだけで、大枠が作成できてしまった点です。
どんなプラットフォームを使うにしても、運用後のトラブルシューティングのことを考えると、できるだけ公式ドキュメント（一次資料）に馴染みたいと常々考えています。
チュートリアルはわかりやすいのですが、Discordの公式資料は自分には難解で、ライブラリのドキュメントと両方読み込んでいく気力がなくなってしまったのも正直理由としてはあります。

２つ目はライブラリのサンプルコードや、検索時に見つかる用例のほとんどが Gateway API(WebSocket)を使った実装ばかりだった点です。
（きちんとドキュメント読めば見つかるのでしょうが）
API Gatewayを使えば、WebSocketは使えますが、自分の作成するアプリでは不要なため見送りました。


### Lambda関数が２段階に分かれている理由
・Discord側は必ず３秒以内にレスポンスを返す制限を課しています。
さほど処理時間を要する実装はないのですが、Lambda関数のコールドスタートや、Discordサーバーの設置場所に起因するレイテンシ
（今回は@edgeまでは使っていないため）、Discord側API呼び出しなど（これが一番時間を消費する）考慮すると３秒を超えるケースが割と発生しました。安定稼働させるため２段階にしています。


### 関数URLを選んだ理由
APIの呼び出し元がDiscord限定かつ、APIとしての呼び出しのみなのでAPI Gatewayは省きました。

## その他
開発サイクル、デプロイ、バックアップ、負荷監視など記事にしたい内容はたくさんありますが、また別の機会に書きたいと思います。