YOUTRUSTのソースコードに残る大量の旧テーブル依存を撲滅する

こんにちは!YOUTRUSTでWebエンジニアをしている春日(YOUTRUST / X)です。

気づいたら2025年も師走なので、ソースコードの大掃除の話をしたいと思います。

今回は、YOUTRUSTのサービス初期から存在し、長年にわたって蓄積してきた「旧・所属テーブル」への依存を、コードベースから完全に削除するまでの取り組みを紹介します。

以前の記事「キャリアSNS YOUTRUSTの所属データの構造を抜本的に変えた話」では、新しいデータ構造を設計し導入するまでのプロセスを説明しました。しかし、テーブルを新しく作っただけでは終わりません。

問題は、コードベース全体に旧テーブルへの依存が大量に残っていたことです。

今回の記事では、その依存をどのように発見し、どう安全に削除していったのかを、実践的な観点でまとめています。


背景:なぜ旧テーブルを撲滅するのか

前回の記事でも触れましたが、旧テーブルには構造上の制約が多く、長年の運用によって技術的負債が蓄積していました。

旧構造の問題点

旧構造は、ユーザーと企業の紐づきを「所属」「職歴」というそれぞれ独立したテーブルで管理する仕組みでした。

ユーザー ─┬─ 所属テーブル(旧)
         │
         ├─ 職歴テーブル
         └─ 学歴テーブル

問題点は以下の通りです:

  • 二重登録が必要:職歴と所属で同じ情報を入力する必要があった
  • 不整合が生まれやすい:職歴を直しても所属情報は自動更新されない
  • 表現力の限界:「本業/副業/学生」などの概念を表現しきれない

新構造の特徴

そこで新しい構造では、「現在のポジション」を表すテーブルが職歴・学歴テーブルを参照する形へ刷新しました。

ユーザー ─── 現在のポジション ─┬─ 職歴テーブル
                           └─ 学歴テーブル

職歴・学歴テーブルがSSoT(Single Source of Truth) となり、整合性と柔軟性が大きく向上しました。

旧テーブルへの依存が残り続ける問題

新しいテーブル構造を導入しても、旧テーブルへの依存がコードベースに残り続けると、以下のような保守・運用上の問題が生じます:

  • 認知負荷の増大: 新旧両方のテーブル構造を理解する必要があり、新メンバーのオンボーディングコストが上がる
  • バグの温床: 新テーブルを更新したつもりが旧テーブルを参照していた、といった不整合が起きやすい
  • 開発速度の低下: 機能追加や修正のたびに「どちらのテーブルを使うべきか」を判断する必要がある

そのため、旧テーブルへの依存を完全に削除することが必要でした。


課題:数千箇所に散らばる旧テーブル依存

ざっと洗い出ししただけでも、数千箇所の旧テーブル依存が見つかる状況でした。

依存が存在していた領域は幅広く、以下は一部です:

  • モデル・アソシエーション
  • has_many :through を経由した参照ロジック
  • Command / UseCase 層のビジネスロジック ※
  • Query 層のデータ取得ロジック ※
  • API レスポンス
  • OpenAPI 仕様書
  • テストコード(Factory含む)
  • バッチ/通知処理
  • 複数のフロントエンドアプリケーションでの参照

※ YOUTRUSTでは CQS アーキテクチャを採用しています。詳しくはYOUTRUSTのSRE 寺井さんの発表資料「Fat Modelを解消するためのCQRSアーキテクチャ」をご覧ください。

これを一気に削除することはとても困難です。 サービスに影響を与えず安全に進めるため、段階的な移行戦略を取る必要がありました。


移行アプローチ:段階的に依存を消していく

前提: 新旧両方のテーブルへの同期で整合性を保証

段階的な移行を可能にするため、新旧両方のテーブルへ同期する仕組みを導入しました。

[ユーザー操作]
     ↓
[新テーブルを書き換え]
     ↓
[同期コマンド]
     ↓
[旧テーブルにも書き換え](暫定)

新テーブルへの書き込み時に旧テーブルへも同期することで、新旧どちらを参照しても同じデータが返る状態を維持しました。

この前提があるからこそ、依存箇所を1つずつ安全に新テーブルへ差し替えていくアプローチが可能になります。

Phase 1: 依存箇所の洗い出しと分類

まずは旧テーブルへの依存がどこにあるのかを把握するため、洗い出しから始めました。

ただし、いきなり修正箇所の完璧なリストを作るのは無謀なので、

主要な依存を発見 → 修正 → 新たな依存を再調査

という段階的なアプローチを採用しました。

洗い出した依存箇所は Markdown で一覧化し、ファイル名・修正方針などを整理してドキュメント化しました。これが後々大きな効果を発揮します(詳しくは後述)。

Phase 2: 段階的な依存削除

Step 1: APIレスポンスの移行(Expand → Migrate → Contract)

最初に API レイヤーから着手しました。

クライアント側は Web フロントエンドだけでなくモバイルアプリからも利用されているため、まずフロントエンド側の旧フィールドへの依存を解消し、修正範囲をバックエンドに閉じ込めることを優先しました。

採用したのは Expand-Contract パターンです。

  1. Expand: 新旧フィールドを併存させて返す
  2. Migrate: フロントエンド・アプリを新フィールド参照に切り替える
  3. Contract: 旧フィールドを削除する
{
  "user": {
    "user_companies": [...], // 旧フィールド
    "affiliations": [...]    // 新フィールド
  }
}

これにより、クライアント側の依存を先に解消し、残りの修正をバックエンド内で完結できる状態を作りました。

Step 2: 内部ロジックの差し替え

API層の移行が済んだら、バックエンドの内部ロジックを段階的に置き換えていきました。

  • Command / UseCase 層
  • Query 層のデータ取得ロジック
  • バッチ処理
  • 通知処理

など、ビジネスロジックのコア部分を中心に移行しています。


工夫したポイント

依存箇所リストを Markdown で構造化したのには、もう一つ大きな狙いがありました。

AI ツール(Claude Code など)に大量修正を任せるための設計書にすることです。

(簡易的な例)

| ファイル | 修正方針 |
|----------|----------|
| XXX_query.rb | user.user_companies -> user.affiliations に置換 |

このように整理しておくことで、

  • 修正意図
  • 修正方法
  • 対象箇所

を AI が正確に理解でき、数百以上の PR を AI 主導で生成できました。

「人が方針を決め、AI が実装する」流れを作れたので、効率的に進めることができました。(実際のところ、方針の案出しも生成AIがやってくれるので、人間はほぼレビュワーの役割を果たすだけでした)

現在の進捗と今後

現時点で、旧テーブルへの依存は 約 70% を削除できています。
残りもユーザー影響を抑えながら慎重に進めており、最終的には旧テーブルの DROP に到達する予定です。

チームが開発しやすい状態に近づけるため、引き続き改善を進めます。


YOUTRUSTでは、一緒にプロダクトを進化させたい方を募集しています!

👉 採用ページはこちら: https://lp.youtrust.jp/recruit