宿題と日直を忘れることが多かったので、Webサービスを開発しました。

要件
- 課題+日直を管理できること
- ちゃんとしたアカウントシステムを設置すること
- 高速であること
- トラフィックを効率的に捌けること
技術スタック
| カテゴリ | 技術要素 |
|---|---|
| フロントエンド・クライアント | Astro, React, Shadcn/ui, Storybook |
| バックエンド | Go (Fiber), GraphQL, gRPC, Node.js, Python, Shell Script, Jest |
| ミドルウェア | MySQL, Redis |
| インフラ | Linux, XServer Static |
| CloudFlare | DNS, Pages, R2, Email Routing, (Email)Workers |
| 技術系その他 | GitHub, Docker, Apache AirFlow, Gemini API, Sendgrid |
| モニタリング | Sentry, Datadog |
| コミュニケーション・ドキュメント | Notion, Discord, Scalar, PlantUML |
| デザイン | Figma |
GitHub Student Developer PackでSentryやDatadogが無料で使えたため、初めて導入しました。GitHub ProやAzureの無料枠なども利用できるので、対象の方は導入してみることをおすすめします。
バックエンドのサーバーにはみらいサーバーを使用しています。高専生であれば無料で利用できます。
インフラ構成
マイクロサービスアーキテクチャ(風)に設計しています。実際にはAPIサーバー、MCPサーバー、バッチ処理系でRDBを共有しており、厳密なマイクロサービスとは言えません。
データのACID特性を保証するために補償トランザクションを定義する必要があり、個人開発レベルでは、サービスとしてのデータの整合性の確保が難しいためです。
とはいえ、MSAの原則に反するので、機会があればDBを分散できたらと思っています。
独自のアプローチ
CI/CDの高速化
このサービスでは、すべてのCI/CDをGitHub Actionsで管理しています。APIエンドポイントを制御するサービスはGoで書かれており、Dockerコンテナとしてデプロイする必要がありますが、この処理に当初20分以上がかかっていました。
セルフホステッドランナーへの切り替えを行い、ワークフローによっては実行時間が半分ほどに減少したものの、このワークフローは依然として20分以上を要していました。
この問題はマルチアーキビルド(クロスコンパイル)をやめさせることで2分台で完了するようになりました。
一般的な公開バイナリイメージと同様にlinux/amd64,linux/arm64のどちらもビルドしていましたが、ランナーに合わせる形でビルドを単一アーキテクチャ用にしました。QEMUエミュレーションがかなり大きな負荷となる、といういい例でした。
セルフホステッドランナーはNishikawa1208から借りた自宅サーバーです。GitHubのランナーと比較して、コンピューティングリソースとしては潤沢ですが、特に可用性の保証などはないことから、ランナーのフォールバック処理を実装しています。
jimmygchen/runner-fallback-actionを用いると、次のようにして簡単にジョブとして実装できます。このジョブ自体はGitHubのランナーを使用します。
jobs:
select-runner:
runs-on: ubuntu-latest
outputs:
label: ${{ steps.pick.outputs.runner }}
steps:
- id: def
run: echo "runner=ubuntu-latest" >> "$GITHUB_OUTPUT"
- id: fbk
name: Select self-hosted with fallback
uses: jimmygchen/runner-fallback-action@v1
continue-on-error: true
with:
github-token: ${{ secrets.RUNNER_FALLBACK_TOKEN || secrets.GITHUB_TOKEN }}
primary-runner: self-hosted
fallback-runner: ubuntu-latest
- id: pick
run: echo "runner=${{ steps.fbk.outputs.runner || steps.def.outputs.runner }}" >> "$GITHUB_OUTPUT"
2026/07/11追記: 現在jimmygchen/runner-fallback-actionはPublic Archiveになっているので、以下を使用することが推奨されています。
https://github.com/mikehardy/runner-fallback-action
バッチ処理の管理
ちょうど最近Kaigi on Railsに参加してきたので、5年間のFintech × Rails実践に学ぶ - 基本に忠実な運用で築く高信頼性システム / 5 Years Fintech Rails Retrospectiveを参考に、バッチドキュメンテーションの必須化とAirFlowの導入を行いました。
結果はDiscordに通知するようにしました。
監視
今までPrometheus+Grafanaを使っていましたが、Sentry, Datadogを初めて導入しました。
Datadog AgentだけでもAmazon CloudWatchのようにサーバーのメトリクスを監視でき便利です。
非構造化データの構造化
私のクラスでは、日直業務が正しく履行されていない場合に、クラス担任から画像のような構造化されていないメッセージが送信されます。
同じメールアドレスから日直に関連しないメッセージも送信されるため、Gemini APIを用いて判定を行っています。
Geminiが誤判定(偽陽性/偽陰性)を出力するケースも考慮して、Discordへの通知と管理画面からの変更も可能にしています。

不適切な投稿の検出
投稿をGeminiに検証してもらい、誹謗中傷などの不適切な課題の投稿がないか評価しています。
不適切な投稿が見つかった場合はDiscordに通知し、当該投稿を非公開にします。この際、投稿は削除されず保持されます。開示請求などの対応を考えた設計です。
内容が深刻である場合は、管理者ロールのついたユーザーにメールでも通知されます。
ログイン認証
Tasklyはユーザーの意志に基づく任意の水準でのセキュリティを達成するために、複雑なログイン認証機構を持ちます。開発中に混乱することを防ぐために、PlantUMLを用いて状態遷移図のように図式化しています。
Teamsの識別子の記録
Tasklyでは課題に関する変更をメッセージにリプライする形でTeamsに通知しています。Teamsではメッセージにリプライするのにmessage_idとteams_converstion_idが必要です。(実際にはチャンネルを一意に決定できる情報も必要ですが、このサービスでは複数のチャンネルにメッセージが分散することはないので、定数としています。)
変更があった時にTeamsへ伝えるための方法として、識別子を
- サービス側のDBに記録
- Power Automate側でExcelシートなどを作成し記録
という二つのアプローチが主に利用できます。
今回はPower Automate側にできるだけ機能を持たせたくなったので、サービス側のDBに記録する方式を取りました。
ただし、Power AutomateではMicrosoftのGraph APIにfetchする場合を除いて、有料のアクションを利用する必要があることから、
- JSON形式のメールでCloudFlareで管理しているメールボックスに転送
- Email Workersを実行
- パースしてサービス側にREST APIとして投げる
という形の(大きく迂回した)アプローチを採用しました。
ただし、現在の実装ではTeamsの識別子がサービス側に到達するまでの間に発生した編集がTeams側に反映されないため、今後のアップデートで対象の変更情報をキャッシュするなどして、識別子到達後に通知を行う処理を追加したいと考えています。
まとめ
複数のWebサービスを作ってきましたが、今回は個人開発なので、触ってみたいと思っていた(けどビジネス的にはスケールに見合わない)さまざまな技術を活用しました。特に、
- 可観測性
- バッチ処理
まわりがこのサービスでこだわった点です。
ユーザー体験の面で言えば、リリース前に1ヶ月ほど自分自身で使ってみましたが、課題の管理が飛躍的に楽になっています。一定期間の経過後にアンケートを取ってみて、課題提出率や負担軽減に有意な効果があるのかを評価しようと思います。









