こんにちは、 traP 代表のたけのひとです!
いきなり突拍子もないことをいいますが、僕の traP の代表の任期が 12/31 で終わります。
いやー、1年間早かったですね。なんか最後になんかやりたいなー…って思って色々考えたのですが…。
そうだ、アドベントカレンダーを乗っ取ろう。
ということでやります、"traP代表によるひとりブログリレー"!ちょうど昨日アドベントカレンダーが終わりましたし、期間は今日から代表の任期が終わる 12/31 までにします。毎日出すことよりは記事の質を優先したいので、6日間で6本出るとは限らないです!
どの記事も色々と振り返る話を予定してます。
ということで本編です、どうぞ!
突然ですが、Bot を作りました。
今日はこの Bot の話をしようと思います!
traPの代表を1年間務めていて、面倒だった仕事のうちの一つに「役員会の議決」があります。traPの役員会はオンラインで議決を取ることがしばしばあるのですが、オンラインでの議決を取る際のルールとして「24時間後」に「反対票がない」ときに可決するというものがあります。慣例的に、最後は代表が条件を満たしているか確認して可決とするかを宣言しています。
何が面倒かというと、僕、24時間経ったら忘れるんですよね。これを忘れるなって話なら申し訳ないのですが、同時並行で他の話も進んでいたりするので大体忘れます。
で、議案を出してる人は早く次に進みたいので僕に議決結果を宣言してほしいという催促する。でも、僕もすぐに反応できる訳でもないし、それにメンションされると忘れててごめんって気持ちになってちょっとつらいです。
あとは僕以外の人が独自に判断して宣言することがあるんですけど、これを常習的にやると決議の判断基準がブレるとか、意思決定のフローが分散する可能性があるとか、ちょっと怖い事態に繋がりかねません。…これは僕が忘れずに真面目に宣言すれば起きないんですけど。
まぁとにかく面倒です。
でも、この宣言ってルールに沿ってやってるだけなので人間がやる必要ないですよね。ってことで自動化しちゃいましょう。自動化したものがこちらになります。(traQ用にカスタマイズしてるので、皆さんはそのままでは使えません…!しかもソースコードそこそこ汚いです…。)
自動化するにあたって、将来的にやりたいことを含めて色々要件がありました。
- できるだけ楽に投票を開始できる
- 条件にそって、一定期間後に議決の成立可否を宣言する
- 再起動に耐える必要がある(データの永続化をする)
- [将来的に] 議決をフォームで入力できるようにしたい!
- [将来的に] 議決以外も日程調整とか手軽にできる申請フォームとか便利な自動化機構を盛り込みたい!
こうやってリストアップしてみると、Slack のワークフローみたいなものって感じがしますね。traQ の機能に組み込んでもいいのですが、デザインとか色々考えると流石にコストが重すぎるので一旦もっと簡単に実装できる BOT 形式でやります。
ちょっと面倒くさかったところが、「再起動に耐える必要がある(データの永続化をする)」の部分。24時間後に実行されてほしいけど、ただ24時間 sleep してるだけだと、途中でアプリケーションが再起動されたときに困る(補足:これをホスティングしてる NeoShowcase の性質上要求される要件なのです)。
これは、scheduled_tasks
テーブルを用意して、新しくイベントを積むときはレコードを挿入、1分ごとにテーブルを確認して時間を過ぎてるタスクがあったら取り出して消化、という実装で解決させました。テーブルスキーマはこんな感じ。
type ScheduledTask struct {
Id string
Command CommandName // string
Arg string
ScheduledAt time.Time
CreatedAt time.Time
ExecutedAt sql.NullTime
}
投票開始時にこのテーブルに新しいレコードを突っ込んで、テーブルから取り出したタイミングでレコードを消化する、という方法で無事に24時間後の判定が実装できました。
さーもん開発 RTA
ここからは僕の開発の様子を振り返っていきます。
というのも、僕がどうやって開発してるか、みたいなのも記録に残ってると後々の人が参考になりやすいかな、と思って。今回はブログに書くのを想定して、普段より自分の times に色々メモを残しています。
そんなに重い BOT を考えてなかったので、楽に素早く実装できるということを念頭にぱぱっと開発します。
19:30 開発環境構築
よーい、スタート。すでに個人用のBOTとその開発環境用のBOTを作っていたので、開発環境用のBOTを流用していきます。開発用に一旦チャンネルを購読させます。
え、なんか違うBOTも来た。
そういえば、購読メッセージの判定はめちゃくちゃ雑でした。本番環境にこれが push されてるので、BOTを開発するときに困る…。
if m.PlainText == "@BOT_no_hito きて" || m.PlainText == "@BOT_no_hito_local きて" {
b.joinChannel(m)
return
}
うーん。
-if m.PlainText == "@BOT_no_hito きて" || m.PlainText == "@BOT_no_hito_local きて" {
+if m.PlainText == "@BOT_no_hito きて" || m.PlainText == "@BOT_no_hito_local きて2" {
b.joinChannel(m)
return
}
一旦カスみたいなワークアラウンドで回避。これがこのサークルの代表のコード力です。
そのあとリポジトリセットアップして、docker-compose / air でホットリロード環境とDBにつなげる環境を用意して開発準備完了!ここまで約30分。何度もセットアップしてると流石に慣れてきますね。
20:00 フロント側も環境作ろう
フロントの方も環境を整えていきます。今回は Vite / React を採用。フロントはちょっと経験値がまだ足りてないのですが、この書き方で問題なく伝わってる…?
UIライブラリはめんどいので MUI でも使おうと思ったのですが、ドキュメント読んでてなんか結局めんどくなってきたので一旦後回しに。
21:00 BOT の命名案
命名、さーもん。
僕の変わりに召喚される BOT で、summon = salmon → さーもん。
というくだりを2ヶ月くらい前に同期がしたんです。決して僕の案じゃないです。信じてください。
ここからまずはパッケージ構成を考えていきます。
今回は BOT, cron, API と3パターンのイベント発火があることを頭に起きながら、 handler - model の2層構造を採用。3種類のイベントを集約させる層があっても良いとも考えたのですが、別種のエンドポイント(例えば BOT と cron)から共通して起動されるイベントが実装される見込みはないので、集約させても冗長なコードが増えるだけになりそうなのでやめました。
ファイル配置はこんな感じ。 `bot` は traQ への接続とかを担ってもらっています。絶対もっと洗練できるけど、まぁ一旦…。
21:40 メッセージを投稿するBOT
とりあえず起動したらメッセージが出て、スタンプが押されるBOTができた!
その後30分くらいで、コマンドで実行できるようにもします。コマンドのパーサーを適当に自前で実装しちゃってて苦労してるので、早くどうにかしたい。
23:20 指定時間後に結果が表示されるように / ジャッジできるように
おー。時間がたったらちゃんと結果が来ます!
更に場合分けもできることを確認。デバッグ用に票数も出すように。
文章も出して、僕のtimesに見に来た人が適当にスタンプを押してくれるのでデバッグもしつつ、日付が回るころには初号機が完成です。
23:50 本番用 BOT のセットアップをしよう
やることは 2 つ。
- traQ 上で新しく BOT アカウントを作成する
- NeoShowcase にリポジトリを登録して、必要な環境変数を登録して起動する
1つめの traQ はこういう感じで BOT の新規登録ができます。楽で助かる〜
NeoShowcase の登録もボタンをぽちぽちするだけ!部内サービスなので料金のことを気にしなくてもいいので、本当に体験が良いです。
しかも Buildpack のおかげで、言語も指定せずにいい感じにビルドしてデプロイしてくれます。すげー!
ということでデプロイのセットアップにかかった時間は30分!早い。
しかも NeoShowcase は push するとWebhook経由で更新されたのを検知して自動でデプロイが走ります。最強かよ。
皆さんもぜひ使ってください!
完
ということで開発を思い立ってから実際に実装してデプロイするまで4時間くらいで終わりました!開発で特に詰まることはなかったので助かりましたね。
その後
時間をかけてBOTの土台を作ったおかげで、後日の新機能の開発が1-2時間くらいでできてめちゃくちゃ助かりました!
機能としては、チャンネルで議論が混戦したときに適切にサブチャンネルに誘導できるというもの。 /new topic
で空いているチャンネルに誘導されて、議論が終わったら /close
で閉じるだけ!
結構シンプルな機能を思いついたときにすぐに実装できる環境があると、面倒な仕事を機械に押し付けられて助かります!
Next...?
今後は、例えば「まだ投票していない人がいたら適宜リマインドする」とか、「日程調整して、前日・当日20分前にリマインドしてくれる」とか、もっといえば Slack のワークフローみたく、そういう簡単なタスク設定を僕以外の人でもUI操作だけでできるように(つまりノーコードで作れるように)したいですね!お仕事の全自動化の道は遠い……。
代表ひとりブログリレー、次の担当は @Takeno_hito です!ある大会の話をします!