AIと実践するVibe-Driven Development:第3回「自作キューよ、さようなら」
こんにちは!株式会社ネクスタのエンジニア、bikです。
▼ 前回までのあらすじ
敵『状態消失』を殲滅。次なる使徒『意図せぬ続行』に対し、エンジニアは『キャンセル』という名の槍を手に取る。
そして、サーバーの死をも乗り越える『ゾンビタスク』対策。
完璧なはずの、心の壁。
だが、その先には、まだ誰も知らない、致命的な欠陥があった。
AIパートナー「Gemini 2.5 Pro」との対話を通じて、バックグラウンド処理機能にDB永続化、キャンセル機能、さらにはサーバークラッシュ対策まで実装した僕。もう怖いものはない、そう思っていました。しかし、最後の壁は、これまでの議論の中に静かに潜んでいました。
最後の問い:「それ、サーバー2台でも動きますか?」
僕(心の声): 「…うん、よくできている。キャンセルはメモリ上のBackgroundTaskManager
で、ゾンビタスクは起動時のStaleTaskCleanupService
で掃除する。…待てよ。」
「このBackgroundTaskManager
、static変数だよな。これって、サーバーのプロセスごとに一つ、ということだ。もし、負荷分散でサーバーを2台に増やしたらどうなる…?」
僕(心の声): 「…サーバーAで開始したタスクのキャンセル命令スイッチは、サーバーAのメモリにしかない。キャンセルリクエストがサーバーBに飛んだら、スイッチが見つからずに失敗するじゃないか!」
これまで築き上げてきた堅牢なシステムが、スケールアウトというごく当たり前の要件の前にもろくも崩れ去る未来が見えました。
僕: 「Gemini、今の僕らの設計、サーバーをスケールアウトしたらキャンセル機能が破綻すると思うんだけど、この問題を解決する一般的な方法って何がある?」
Gemini: 「その通りです。それは分散システムにおける状態管理の典型的な課題です。解決策としてRedisのPub/Sub機能を使う方法があります。(…中略…)」
Geminiは、そのアーキテクチャを実現するための具体的なコードを提示してくれました。
複雑さの先に見えた光
僕は、Geminiが提示した実装手順を元に、キーボードを叩いていた。サービスを登録し、メソッドを呼び出す…手順自体は明確だ。しかし、そのコード全体を見渡した時、ふと手が止まった。
僕(心の声): 「なるほど…。実装の手順は理解できた。でも、これはかなり大変そうだ。スケーラビリティのためとはいえ、メッセージングの仕組みやサービスの購読管理まで自前で実装するのは、今回の機能のスコープを考えると少しやりすぎかもしれない…。」
僕: 「サーバーAがクラッシュしても処理を継続する方法ってあるんでしょうか?」
Gemini: 「はい、あります。そのためには『キューを利用したワーカーサービスモデル』という設計パターンが必要です。そして、その仕組みを.NETで非常に簡単に実装できるライブラリがあります。あなたが今直面している『バックグラウンド処理の信頼性とスケーラビリティ担保の複雑さ』を、丸ごと解決するために作られたフレームワークです。**『Hangfire』**です。」
プロの道具、Hangfireとの出会い
**Hangfire。**それは、.NETで信頼性の高いバックグラウンド処理を実装するための、オープンソースのフレームワークです。
Geminiが教えてくれたHangfireの機能リストを見て、僕は言葉を失いました。
- タスクの永続化とキューイング: SQL Server等を共有ストレージとして利用。
- スケーラビリティ: 最初から複数サーバー構成をサポート。
- クラッシュ耐性: 処理中にワーカーがクラッシュしても、自動でリトライ。
- キャンセル機能: 標準でサポート。
- ゾンビタスクの自動検知&クリーンアップ: 標準でサポート。
僕(心の声): 「…なんだ、これ。僕らがこの数時間で悩み、苦労して実装してきたこと…そのすべてが、このHangfireというフレームワークに全部入っているのか。」
さようなら、僕たちの自作コード。こんにちは、本物の信頼性。
Hangfireの導入は、驚くほど簡単でした。
これまでバックグラウンド処理を起動していた自作のコードが、
▼ 変更前(自作)
var cts = new CancellationTokenSource();
BackgroundTaskManager.RunningTasks.TryAdd(newTask.Id, cts);
_ = _taskService.ExecuteTaskAsync(newTask.Id, cts.Token);
たった一行のコードに置き換わりました。
▼ 変更後(Hangfire)
var hangfireJobId = BackgroundJob.Enqueue<HeavyTaskService>(service =>
service.ExecuteTaskAsync(newTask.Id, JobCancellationToken.Null)
);
BackgroundJob.Enqueue
を呼び出すだけで、HangfireがDBへの永続化、キューイング、スケーラブルな実行、そしてエラーハンドリングまで、すべてをよしなにやってくれます。
もちろん、自作したコードは決して無駄ではありませんでした。自分たちの手で実装し、様々な問題と格闘したからこそ、Hangfireが裏側で何をしてくれているのか、そのありがたみを骨の髄まで理解できたのです。
今回の開発譚:8時間の軌跡をデータで振り返る
この壮大な(?)冒険を終え、ふとGeminiとのやり取りのログを振り返ってみた。そこには、私たちの格闘と成長の軌跡が、驚くべき数字として刻まれていました。
-
対話の密度
- 総やり取り回数: 約150往復
- 総文字数: あなた: 約15,000字 / AI: 約80,000字
-
思考の進化(あなたの質問の傾向)
- フェーズ1(実装方法の模索): 「どうやって実装する?」(How-to型)
- フェーズ2(エラー解決): 「このエラーの原因は?」(デバッグ型)
-
フェーズ3(仕組みの理解): 「
async/await
って何?」(Why型) - フェーズ4(アーキテクチャ評価): 「サーバーがクラッシュしたら?」(What-if型)
-
格闘の軌跡
- 解決したエラーの種類: 7種類以上
- アーキテクチャの方向転換: 3回 (SignalR → ポーリング → DB永続化 → Hangfire)
-
共創の証
- AIが提示したコードスニペット: 約40個
- AIが使った「例え話」の数: 5つ以上 (レストランの厨房, 車の部品, ドアベル etc.)
Vibeから始まった旅の終わり、そして…
「非同期処理を深く学んでみたい」
そんな個人的な学習目標から始まった僕の開発の旅は、最終的にHangfireという信頼性の高いフレームワークを採用し、スケーラブルで堅牢なバックグラウンドジョブ基盤を完成させるという形で、ついに幕を閉じました。
ふと、僕は時計を見ました。
そして、信じられない事実に気づきます。
この濃密なエラーとの格闘、アーキテクチャの議論、そして全面的な実装の刷新…そのすべてが、まだ始まってから8時間しか経っていなかったのです。
AIは、ただコードを生成するだけの機械ではありません。対話を通じて課題を明確にし、思考を拡張させ、そして時には自分たちの知識の外にある「プロの道具」の存在を教えてくれる、まさに「戦略的パートナー」でした。
Discussion