2ヶ月のバイブコーディング振り返り — BEリードとしてAIサービスを作ってみた正直なレビュー
2026年3月〜4月、東京拠点のstudy community + AIキュレーションサービスをチームプロジェクトとして作った。
私はバックエンドエンジニアリードを担当した。AI(Claude Code)と一緒に「バイブコーディング」をやってみた、これはその反省文だ。
何を作ったか
study community + AIキュレーションサービス。主な機能:
- コミュニティ: 掲示板 / 記事 / コメント / いいね
- AIキュレーション: 毎日RSSフィードを収集 → Ollama(LLM)で韓国語・日本語の要約を生成
- AIボットコメント: ユーザーが記事を投稿するとAIボットが自動でコメント
- Daily Digest: 毎日10時(KST)に一日分のキュレーションをAIが一つのダイジェストにまとめ、コミュニティに投稿として公開
スタック: Kotlin + Spring Boot 3.4.5 / PostgreSQL 16 + Flyway / RabbitMQ / Ollama(ローカルLLM)/ React + Vite + TypeScript / AWS + Cloudflare / CI-CD GitHub Actions
タイムライン — やったこと
Phase 1: プロジェクトセットアップ(3月末)
- BEプロジェクト初期化(Kotlin + Spring Boot 3.4.5、Gradle、JPA、Swagger、Flyway、Logback)
- Google OAuth + JWT認証実装
- DBスキーマ初期設計(V1〜V16、Flyway移行戦略策定)
- CORS、リバースプロキシ(Cloudflare)越えのHTTPSスキーム認識修正
✅ 良かった点: 最初からFlyway + 「既存ファイル修正禁止」ルールを設けたおかげで、チーム全体がマイグレーション競合なしにDBを管理できた。
❌ 見落とした点: この時点でDBの設計をより丁寧にすべきだった。後から確認するとnotificationsテーブルはスキーマだけあってエンティティがなく、last_login_at、user_activitiesといった分析用カラムも最初から欠けていた。
Phase 2: AIボットコメント実装(4月初〜中旬)
-
OllamaClient実装(chat / generate API分離) - AIボット自動コメント機能(BE-55):記事保存時にAIボットコメントを自動生成
-
@TransactionalEventListener(AFTER_COMMIT)+@Async+ RabbitMQ非同期処理に移行
✅ 良かった点: トランザクションコミット後にイベントを発行するパターンを正しく適用し、「記事保存失敗時はコメントも発行されない」保証を得た。
❌ ハマったこと — Ollama 502半日デバッグ事件
OllamaClientを合計3回交換した。その過程は小さな災難だった。
最初はSpring標準に合わせてJDK HttpClientを選択した。ローカルではOllama APIの呼び出しが正常だった。しかし本番デプロイ後、突然502エラーが大量発生した。
[診断過程]
curlでOllamaを呼び出す → ✅ 正常
JavaでOllamaを呼び出す → ❌ 502
curlは通るのにJavaだけ通らない。コードの問題だと思ってretryロジック、診断ログ、タイムアウト設定を追加した。一日中格闘したが依然として502。深夜1時にようやく原因を突き止めた。
原因: JA3フィンガープリント
HTTPS通信開始時、クライアント(Javaアプリ)とサーバー(Cloudflare)の間で「TLSハンドシェイク」が行われる。このとき、クライアントはサーバーにこんなメッセージを送る:
「TLS 1.3が使えて、これらの暗号化方式をサポートして、これらの拡張機能を使う。」
このメッセージに含まれる値 — TLSバージョン、サポートする暗号化方式の一覧、拡張機能の一覧など — を一つのハッシュ値にしたものがJA3フィンガープリントだ。人間の指紋のように、HTTPクライアントごとに固有のJA3値が生成される。
curl → JA3: aaa... (通過)
Chromeブラウザ → JA3: bbb... (通過)
JDK HttpClient → JA3: ccc... (CloudflareにBotとして分類 → 502でブロック)
Apache HttpClient → JA3: ddd... (通過)
CloudflareのBot Fight Modeは既知のボットのJA3値をブロックする。JDK HttpClientは一般的なブラウザやcurlとは異なる独特なパターンのJA3を生成するためボットとして分類された。コードの問題ではなかった。TLSハンドシェイクの方式が問題だった。
だからcurlは常に通り、ローカルでも通った(ローカルはCloudflareを経由しないため)。本番でCloudflareを通る瞬間だけブロックされた。
// こう交換したら解決した
// Before (JDK、Cloudflareにブロック)
JdkClientHttpRequestFactory()
// After (Apache、通過)
HttpComponentsClientHttpRequestFactory()
問題はこれで終わりではなかった。AIボットコメント用クライアントは修正したが、RSSキュレーションパイプラインのOllamaクライアントでも同じ問題が発生した。同じ理由で同じ修正をまたやらなければならなかった。最初からOllamaConfigを共有Beanとして作っていれば、一箇所だけ直せばよかった。
- AIボットの設計を一度誤って決定した(chat → generate API混用)。wikiに設計を文書化していなかったので、自分でも忘れてClaude Codeに誤ったコンテキストを伝えてしまった。
Phase 3: RSS収集 + Ollamaキュレーションパイプライン(4月中旬、BE-37)
最も大きな機能だった。
-
RssFeedClient— RSS/Atomパース、UTC正規化 -
CollectedFeedItem— url_hash/title_hash重複排除 -
FeedItemPublisher— RabbitMQ発行(DLQ含む) -
FeedCollectionScheduler— 毎日07:00 KST自動収集 -
CurationService— Ollama generate → 韓/日要約同時生成 → curationsに保存 -
AdminFeedController— 手動トリガーAPI
✅ 良かった点:
- RabbitMQ DLQを最初から設計に組み込み、失敗アイテムの再処理経路を確保した。
- キュレーション要約を韓/日同時生成(API1回の呼び出し)として設計したので効率的だった。
❌ ハマったこと — Hotfix4連続事件
RSSパイプライン実装を終えてデプロイしたら、バグが3つ同時に発生した。結局4日以内にhotfix PRを4本連続で出した。
PR #142 (4/14) hotfix: application-prod.properties復元とCI/CD DBクレデンシャル注入
PR #146 (4/16) hotfix: Ollama 502 — retryロジック追加(原因はまだ不明)
PR #147 (4/17) hotfix: JDK HttpClient → Apache HttpClient(JA3ブロックが真の原因)
PR #148 (4/17) hotfix: RSSキュレーションパイプラインにも同様の交換を適用
PR #151 (4/18) fix: PROD RabbitMQホストをhost.docker.internalに修正
それぞれの原因:
-
application-prod.propertiesが.gitignoreに登録されていてCI/CDビルド時にファイルがなかった。ローカルにしかなかったファイルだった。 - Ollama 502 — JDK HttpClient JA3問題(半日デバッグ)
- RSSパイプラインも同じJA3問題
- RabbitMQはDockerコンテナ内部からlocalhostでアクセスするとコンテナ自身を指す。
host.docker.internalに変更してはじめてホストマシンのRabbitMQにアクセスできる。
3つのバグの共通点:すべてローカルでは再現しない。 本番環境(Cloudflare、Dockerネットワーク、CI/CD環境変数)がローカルと異なった。でもローカルで「動きます」を確認してすぐデプロイした。本番環境の前提条件をチェックリスト化しておけば、少なくとも1番と4番は防げた。
- 「デプロイすれば動く」という前提でローカルテストを十分にしなかった。本番で初めて発生したバグばかりだった。
Phase 4: Daily Digest + ダイジェストミラー投稿(4月末)
-
CurationTypeenum + V24 Flyway移行 -
DailyDigestService— 一日分のアーティクル → Ollama → DAILY_DIGEST保存 + ミラー投稿作成 -
DailyDigestScheduler— 毎日10:00 KST自動実行、直接aiBotService.generateComment()を呼び出し - 核心設計決定:ボットが作った投稿にボットがコメントするためにイベントガードをバイパス
✅ 良かった点: @ConditionalOnPropertyで機能フラグを付けて、本番デプロイ時に安全に無効化可能。
❌ 惜しかった点: curationsテーブルにtypeカラムを最初から入れなかったので、後からV24移行で追加した。最初のDB設計時に「キュレーションにタイプが生まれるかもしれない」という予測ができなかった。
バイブコーディング + AI活用振り返り
うまく使えた点
1. CLAUDE.md + wikiでコンテキスト管理
CLAUDE.mdにスタック、APIレスポンスフォーマット、Flywayルール、認証パターンを明示しておいたことが最大の資産だった。Claude Codeが新機能を実装するとき、既存パターンを自動的に踏襲した。反対に、wikiやCLAUDE.mdにない設計(例:OllamaClient API選択理由)は、Claude Codeが誤った方向で実装するケースが繰り返された。
教訓: AIに「コードを書け」と言う前に「なぜこう設計したか」を記録することが先だ。
2. ブランチ/worktree分離
worktreeで機能ごとに分離した後、Claude Codeに「このbranchだけで作業しろ」と指定したのは良かった。ただし、worktreeのパスを誤って設定するミスをした(プロジェクトルートではなくサブディレクトリでgit worktree add)。
3. サブエージェント活用
実装 → スペックレビュアー → コード品質レビュアー → 次の実装というパターンは、「成果物の品質」よりも「自分が見落とした部分の発見」に効果的だった。AIがAIのコードをレビューする構造は完璧ではないが、少なくとも明らかなスペック漏れは拾ってくれた。
うまく使えなかった点 / 反省
1. 設計なしに「とりあえず書いてみて」
「とりあえず書いてみて → 後で直す」の繰り返し
Ollamaクライアントを3回交換したこと、curationsテーブルのtypeカラムを後から追加したこと、notificationsテーブルがスキーマだけあってサービスがないこと、すべて「まず作ってみよう」の結果だ。
AIが素早くコードを生成してくれる分、設計を飛ばす誘惑がより強くなる。バイブコーディングの最大の落とし穴がここにある。
2. Revert一つが2ヶ月のコンテキスト管理を台無しにした
gitログを振り返るとこんなコミットがある:
2026-04-09 417b847 create claude skills and settings
2026-04-11 288f05f Revert "create claude skills and settings" ← 2日後に戻した
4月9日に/handover→/takeoverコマンド、ステータスバースクリプト、HANDOVER.md自動化を作った。セッションが切れてもClaude Codeが「どこまでやって次は何をすべきか」を30秒以内に把握できる構造だった。でも2日後にRevertした。
正確な理由は覚えていない。おそらく「今すぐ機能を実装する方が急ぎだ」だったと思う。
その結果、その後2ヶ月ずっとセッションが新しく始まるたびに:
- 自分がHANDOVER.mdを手動で更新しなければならなかった
- Claude Codeがすでに決定済みの事項(HttpClient選択理由、API設計方針)を知らずに再び尋ねたり、間違った方向で実装した
- 「すでに直したもの」をまた元に戻すことが繰り返された
そしてmemory/ディレクトリ — AIがセッション間で記憶を保持する自動メモリシステム — これはプロジェクト終了直前にようやく構築された。2ヶ月ずっとあった機能なのに使わなかった。
セッション管理をきちんとしていれば:
- OllamaClientを3回交換することの一部はなかっただろう(すでに決定した理由がメモリにあったから)
- AIボットAPI設計(chat vs generate)が混在することもなかっただろう
- 「これなんでこうなってるんですか?」と自分が聞き返すことが減っただろう
結論:ツールを作ったら使え。 Revertするな。
3. 自分が理解していないコード
素早く生成されたコードを十分に理解しないままマージした。後から「このコードなんでこうなってるんだろう?」という状況が繰り返された。特にAiBotPostListenerのトランザクションイベント動作方式、RabbitMQ DLQ再処理経路を自分で説明できない瞬間があった。
教訓: AIが書いたコードでも自分が口頭で説明できなければならない。できなければとりあえず止まって理解してから。
4. プロンプトの質 = コードの質
曖昧なリクエスト(「これちょっと直して」)は曖昧な結果を生んだ。「現在のOllamaClient.chat()はCloudflareで502が出るJDK HttpClientを使用している。Apache HttpClientに交換し、connection timeoutは30秒、read timeoutは60秒に設定、既存のretryロジックは維持」のように具体的に書くほど結果が良かった。
5. /insights類の機能をもっと使うべきだった
Claude Codeの探索機能を「コードを書く」直前にしか使わなかった。プロジェクト中盤に「全体の構造はどうなってるんだろう?」と定期的に把握するのに使っていれば、notificationsのような未完成フィーチャーをもっと早く発見できただろう。
構造的に惜しかった点
DBスキーマレビュー(BE/FE/PM観点)をプロジェクトの最後に回してみたら出てきた問題点:
| 問題 | 原因 |
|---|---|
notifications スキーマだけあって未完成 |
優先度が押し出され、誰も管理しなかった |
last_login_at、user_activitiesがない |
DAUなどの分析指標の必要性を最初に議論しなかった |
| soft deleteがない | 「後で追加すればいい」→ しなかった |
FKインデックス欠落(posts.author_idなど) |
パフォーマンステストをしなかったので気づかなかった |
isLikedフィールドがない |
FEが必要だと言うまでBEは認識していなかった |
| UUID vs BIGSERIAL混用 | 明確なID戦略の議論なしにその都度決定 |
共通の原因: 「MVPに集中」という名の下に設計議論をスキップしたこと。
次はこうする
- DBの設計レビューをSprint 0で行う。 BE/FE/PM観点で最低1時間は一緒に見る。
- 設計文書(wiki)をコードより先に書く。 AIにコーディングをさせる前に設計意図を文書に残す。
- AIが書いたコードは自分が説明できるまでマージしない。
- 分析指標用カラム(last_login_at、user_activities)はDay 1に設計に組み込む。 後から追加するとマイグレーションコストが大きい。
- プロンプトをきちんと書く。 「これ直して」の代わりに「現在の状況 → 欲しい結果 → 制約条件」の形式で。
Optional / 知っておくと良いこと
まだこの辺はどのように扱いすればいいのか分からにけど、、、
-
@ConditionalOnProperty: property値に応じてBeanの生成自体をオン・オフできるSpring機能。環境ごとに特定機能の有効化をプロパティ値で制御したいときに使える。
おわりに
2ヶ月間「AIと一緒に仕事をするとはどういうことか」を体感した。AIは確かに速かった。一人で作っていたら2ヶ月以内には作れなかった機能を作った。
しかしAIは私が間違った方向を指せばその方向へ素早く走る。バイブコーディングのスピードはミスのスピードも高める。
結局AI時代のエンジニアに必要なのは「コードをうまく書く能力」ではなく**「何をなぜ作るのかを明確に知る能力」**だということを学んだ。
作成日: 2026-04-28
プロジェクト: 東京拠点のスタディコミュニティ + AIキュレーションサービス(チームプロジェクト、約2ヶ月)