社内通貨のSlackアプリを作成しました
こんにちは。Japan Digital Design 株式会社(以下JDD)でインフラエンジニアをしている渡邉です。
JDDでは社内通貨として入社時やイベント時に JDD Coin というアクリルの素敵なコインが配布されていましたが、それをデジタル化し管理するためのSlackアプリ(JDD Bank アプリ)を作りましたので、今回はその開発にまつわる話を書こうと思います。
JDD Coin、JDD Bankについての詳細や経緯は下記を是非ご覧いただくとして、本記事では主にどのようなインフラ構成や仕組みとなっているかを主に記載します。
プラットフォームの検討
社内通貨として配布されていた JDD Coin をデジタル化しようという話が出た際に、まず案として出たのは通常のWebアプリでした。
実際、私が社内の20%ルール(※1)で一人で開発を始めた時もReactベースのWebアプリとして作成しており、一応それでも運用していけると認識していました。というのも、初めの開発動機としては JDD Coin の管理簿をスプレッドシートで記載しており大変、というお悩みを聞いたのが始まりだったからです。
しかし、実際に検討を進めるうちに、Webアプリではあまり活用されないのではないかという意見もあり、最終的に社員全員が毎日頻繁にチェックしているであろう、弊社のメインのコミュニケーションツールであるSlack上のアプリとすることにしました。
そしてただ管理するためのツールではなく、 JDD Coin の流通量を増やす=JDD Coin を通じてリモートでのコミュニケーションを活性化させる、という目的から社員間でも JDD Coin を送りあえる機能を実装することになり、「JDD Coin を管理・流通させるもの」として、JDD Bank と命名されアプリ作成が始まりました。
JDD Bank アプリの機能
Slackユーザー(弊社社員)は JDD Bank アプリで主に下記の操作ができます。
残高照会
取引履歴照会
他の人に JDD Coin を送る
リワード(Goods)と交換する
いわゆる一般的な金融機関の口座で出来ることと似ていますが、リワード(Goods)との交換ができるので、ネットショッピングに近い要素もあります。
交換するとは言っても、物理的なリワードの受け渡しは郵送ではなく、オフィスにある個人ロッカーに JDD Bank の管理者が配布するという形を取り、申請者は良きタイミングで出社して受け取るということになりました。
そのため、アプリ上で行うのはあくまで交換の依頼を出す、というところまでとなります。
インフラ構成
私自身、Slackアプリはちょっと触ってみたことがある程度だったので、どのように設計するか悩みましたが、リリース時には一旦下記のような構成となりました。
弊社は Azure Active Directry に社員情報があるため、それを定期的に取得しSlack上のユーザーとの紐づけを行うことによってJDD Bankの口座を管理します。
Slack APIは3秒以内にレスポンスを返答する必要があるため、Lambdaは2段構成として本処理はInvokeされる形とし、DBを扱う処理はAurora ServerlessのData API経由でデータを取得・更新します。本処理の結果については別プロセスにてSlack側に返答する(メッセージを返す)という仕組みです。
言語はNode.jsとしました。
DBに関してはDynamoDBにするかRDSにするか悩みましたが、今回通貨相当のものを扱うので一貫性担保のため、かつデータ用途的にもRDBの方がしっくりきたため、RDS(Aurora Serverless v1)としました。タイミング的にちょうどGAした Aurora Serverless v2 も検討しましたがコスト面、およびその時点(2022年5月)では DataAPI や CloudFormation, CDK に未対応だったことから今回は見送っています。
構成としては非常にシンプルですが、サーバレスの宿命でコールドスタート時は想定される時間内に返答できないなどのデメリットがあります。
特に Aurora Serverless はコールドスタートで1分近くかかってしまうため、平日日中は常に起動している状態を維持するよう定期的に起こすスケジュールを組みました。
今回は社内利用、かつ予算・開発期間を鑑みて上記の構成となりましたが、一旦はスモールスタートでリリースし、利用状況に応じて今後構成の見直しなどを進めていければと考えています。
IaC
今回は私一人での開発となるため、自分が好きなCDKで作成しました。
Lambdaコードもインフラコードも1つのリポジトリで管理しています。
また共通のコードおよびnpmパッケージは Lambda Layer に格納し、各 Lambda Functionから呼び出して利用しています。
DB設計
DBについて上記に記載した通りRDSとしましたので、いわゆる一般的な正規化をして下記のテーブル群となりました。
社員テーブル
口座テーブル
取引履歴テーブル
リワードテーブル
社員それぞれに口座が1つ紐づき、取引履歴にはリワード交換を含め全取引の記録を取るようにしています。
また、年間の発行上限数が決まっているため、その判定も取引履歴のデータから実施する形になっています。
なお、取引履歴テーブルを安直に transaction と命名してしまい、DBのトランザクション機能とややこしくなってしまったのはここだけの話…。
Slack API
Slackアプリでは、Slack APIを利用します。
公式のドキュメントも充実しており、基本的にはドキュメントに沿って構築すれば問題無いかと思います。
だたAPIではEventやShortCut、InteractivityやSlash Commandなど複数の機能が存在し、また、それぞれリクエストデータの形式やフィールド名、データ構造などがバラバラで、正直使いやすいとは言えず(ごめんなさい)、そのままAPIの処理を自分で書き始めるとなかなかカオスになってきます。
そこで今回は公式が推奨しているBoltフレームワークを利用することにしました。
こちらも詳しいドキュメントがあり、ほぼ迷うことなく利用することができます。
リクエストデータの構造がバラバラなのは変わりませんが、デコード等の前処理やSlackのリクエスト署名の検証を自動でやってくれるなど、かなり使いやすくなっています。
AWS Lambdaで利用する場合は専用に用意された AwsLambdaReceiver を利用する必要があります。
一つ注意点として、AwsLambdaReceiverを利用する場合は ack による即時応答が効かず、すべての処理が終わるまで待機してしまいます。(Node.js用ライブラリの場合)
Issueは出ているようですが、そういう仕様とのことで現状諦めるしかなさそうです。python版はLazy Listener機能があるようで、ちょっと後悔しました…。
そのため、リクエストを受け取ってから3秒以内にLambdaの処理を終わらせレスポンスを返す必要があります。
また、Slack側は3秒以内に応答がないと、問答無用でリトライしてきます。
初回の処理の結果にかかわらず重複して処理が実行されてしまうため、その対策も必要です。
今回は単純にLambdaを2段構成とし、レスポンス用Lambdaから本処理用Lambdaを非同期でInvokeすることで3秒以内に応答できるようにしました。
リクエスト数が多いなどでキュー管理を厳密にする場合はSQS等を挟む方がよいですが、今回は一旦見送っています。
また万が一リトライされた場合については、ヘッダーに X-Slack-Retry-Num や X-Slack-Retry-Reason というカラムが含まれており、それによって2回目以降の処理をスキップするようにしています。
セキュリティ対策
今回は一旦MVPの機能のみでのリリースとしたため、運用中にどうしてもDBメンテナンスが必要となるシーンが出てきます。(例えば、リワードの在庫調整や操作ミス等の払い戻しなど)
そのためDBへ直接アクセスしてクエリを実行できるロールが必要なのですが、今回は下記の3種のロールで運用することとしました。
Devロール:Lambda等アプリリソースのリリースが可能
Opsロール:RDSのDataAPIアクセスが可能
Auditロール:ログ閲覧のみ可能
OpsロールおよびAuditロールはAzureADでのSSOアクセスとし、SSO可能なのはアクセスパッケージにて承認されたユーザーのみ、としています。
DB操作の発見的統制として、もし指定されたLambda以外からのクエリアクセスがあった場合は即時検知し、セキュリティチームにアラートが送信されます。Aurora Servereless の Audit Log も出力するようにしており、Auditロールにて後からでも発行されたクエリを追えるようにしました。
また、DevロールについてはGitHub(GitHub Actions)からのスイッチロールに限定し、アプリのデプロイはCloudFormation(CDK)で実施、かつコードを改修するにはGitHub上でのレビュー&承認が必須となるため、アプリのLambda改変による不正操作を防いでいます。
開発を振り返って
今回はとにもかくにも1人での開発だったため、これで良いんだろうか?他に最適な方法があるのでは?という悩みが尽きず、これは自分の性格もあるのですが、やはり数名での開発の方が安心感があって精神的には楽だなと思いました。
まだまだバックログに積んである機能もたくさんあるので、運用しつつ引き続き改善フローを回していければと思っています。
エンジニア募集中
JDDでは現在、各種エンジニアを絶賛募集中です。
1人で開発してて可哀そうだな、助けたいなと思ってくれた心優しい方、なぜインフラエンジニアがバックエンド開発してるんだと思った方、もしくは自分ならもっと最適な構成で作成できるのに!というつよい人、是非ご応募お待ちしています!
(カジュアル面談もやってますのでお気軽にどうぞ)
この記事に関するお問い合わせはこちらへ
Technology & Development Division
Engineer
Yuki Watanabe(渡邉 有希)