深く根付いた2つのマスタテーブルを統合した話

こんにちは、YOUTRUST Webエンジニアの寺井(YOUTRUST/X)です。

YOUTRUSTではユーザーや募集において職種を設定することができます。

しかし、実はユーザーで設定できる職種(ユーザー職種)と募集で設定できる職種(募集職種)では、これまで全く別のマスタテーブルを参照していました。

別々のマスタテーブルを参照しているユーザー職種と募集職種

そのため、それぞれで設定できる職種が微妙に異なっていたり、ユーザー職種をもとにして同じ職種の募集をレコメンドするなどができない状態となっていました。

今回は、Webアプリとネイテイブアプリの両方でダウンタイムを発生させずに、2つのマスタテーブルを統合した話を書きたいと思います。

全体の流れ

  1. 新職種マスタの作成 [Webバックエンド]
  2. ユーザーテーブルと募集テーブルに新職種カラムを追加 [Webバックエンド]
  3. 職種変換器の作成 [Webバックエンド]
  4. 作成・更新APIで旧職種と新職種の両方をDBに保存するように変更 [Webバックエンド]
  5. 旧職種を元にして求めた新職種を保存するrakeタスクの実行 [Webバックエンド]
  6. 新職種カラムにNOT NULL制約をつける [Webバックエンド]
  7. 参照APIで旧職種と新職種の両方を返すように変更 [Webバックエンド]
  8. 既存の職種一覧APIを参照している箇所を新職種一覧APIを参照するように変更 [Webフロントエンド・ネイティブアプリ]
  9. 参照しているAPIレスポンスの旧職種を新職種に変更 [Webフロントエンド・ネイティブアプリ]
  10. 旧職種関連のコードやカラム、マスタテーブルを削除 [Webバックエンド]

1. 新職種マスタの作成 [Webバックエンド]

今回、職種マスタを統合するにあたって、既存の職種名を変更したり新たな職種の追加も行うことになりました。

そこで、まずは既存のユーザー職種マスタと募集職種マスタの和集合をベースにして、新たな新職種マスタを作成しました。

ユーザー職種マスタと募集職種マスタから作成した新職種マスタ

2. ユーザーテーブルと募集テーブルに新職種カラムを追加 [Webバックエンド]

もしWebアプリだけであれば、クライアントからのリクエスト部(フロントエンド)とサーバの処理部(バックエンド)で同時に新職種マスタを参照するように変更すれば、それでマスタテーブルの移行は完了となりますが、今回はネイティブアプリにも影響があります。

YOUTRUSTでは可能な限りネイティブアプリの強制アップデートは避けているので、今回も強制アップデートなしでマスタテーブルの統合を行うことにしました。

強制アプデをしないということは、古いバージョンのネイティブアプリからのリクエストと、新しいバージョンのネイティブアプリからのリクエストが混在することになります。

GETリクエストの場合、古いバージョンのネイティブアプリでは旧職種(既存のユーザー職種 + 既存の募集職種)のみしか解釈できないので、レスポンスに旧職種と新職種の両方を含めるように拡張する必要がありました。

古いバージョンのネイティブアプリでは旧職種のみしか解釈できない

それを実現するために、ユーザーテーブルと募集テーブルの旧職種カラムは残したまま、新職種カラムを追加しました。

※正確にはユーザーテーブルではなく、ユーザーと職種の紐付けを管理するテーブルです。

ユーザー職種(user_job_categories)テーブル
+---------------------+------+
| Field               | Null |
+---------------------+------+
| id                  | NO   |
| user_id             | NO   |
| category_type       | NO   | # 旧職種カラム
| job_category_number | YES  | # 新職種カラムを追加
| created_at          | NO   |
+---------------------+------+
募集(recruitment_posts)テーブル
+--------------------------------------+------+
| Field                                | Null |
+--------------------------------------+------+
| id                                   | NO   |
| user_id                              | NO   |
| ~~                                   | NO   | # 省略
| job_category_number                  | YES  | # 新職種カラムを追加
| recruitment_post_job_category_number | NO   | # 旧職種カラム
| created_at                           | NO   |
| updated_at                           | NO   |
+--------------------------------------+------+

なお、このタイミングでは新職種カラムには値は入っていないのでNOT NULL制約は付けていません。

3. 職種変換器の作成 [Webバックエンド]

POSTリクエストの場合、旧職種と新職種のどちらが飛んできても対応できるようにAPIを拡張する必要があります。

入力に職種が含まれるAPIは数多く存在していたので、それらのAPIを拡張する際に共通で必要となる職種変換処理を作成しました。

# v2が旧職種、v3が新職種
class UserJobCategoryConverter # ユーザー職種変換器
  def self.convert_job_category_from_v2_to_v3(v2_job_category_key:)
    ~省略~
  end

  def self.convert_job_category_from_v3_to_v2(v3_job_category_key:)
    ~省略~
  end
end
# v2が旧職種、v3が新職種
class RecruitmentPostJobCategoryConverter # 募集職種変換器
  def self.convert_job_category_from_v2_to_v3(v2_job_category_key:)
    ~省略~
  end

  def self.convert_job_category_from_v3_to_v2(v3_job_category_key:)
    ~省略~
  end
end

4. 作成・更新APIで旧職種と新職種の両方をDBに保存するように変更 [Webバックエンド]

ユーザー作成APIや募集更新APIなどの職種に関わるすべての作成・更新APIで、

  • リクエストに旧職種が含まれていたら、変換器を使って新職種を求め、旧職種と新職種を保存する
  • リクエストに新職種が含まれていたら、変換器を使って旧職種を求め、旧職種と新職種を保存する

ように拡張していきました。

# 以下のような分岐を各APIに追加していく
# v2職種のとき
if RecruitmentPostJobCategoryConverter.v2_job_category?(job_category_key: category_type)
  set_job_category_number_by_v2_job_category(category_type: category_type)
# v3職種のとき
elsif RecruitmentPostJobCategoryConverter.v3_job_category?(job_category_key: category_type)
  set_job_category_number_by_v3_job_category(category_type: category_type)
else
  raise 'recruitment_post_job_category_type must be included in v2 or v3 job category master'
end

5. 旧職種を元にして求めた新職種を保存するrakeタスクの実行 [Webバックエンド]

ステップ4が終わった段階では、新たに作成・更新されたレコードには必ず旧職種と新職種の両方が保存されているが、既存のレコードには新職種が保存されていない状態になっています。

そこで、旧職種を元にして求めた新職種を保存するrakeタスクを作成し、実行しました。

6. 新職種カラムにNOT NULL制約をつける [Webバックエンド]

ステップ5によって、これから作成されるレコードも既存のレコードも新職種カラムに値が入るようになったので、NOT NULL制約をつけます。

ユーザー職種(user_job_categories)テーブル
+---------------------+------+
| Field               | Null |
+---------------------+------+
| id                  | NO   |
| user_id             | NO   |
| category_type       | NO   | # 旧職種カラム
| job_category_number | NO   | # 新職種カラムをNOT NULLに変更
| created_at          | NO   |
+---------------------+------+
募集(recruitment_posts)テーブル
+--------------------------------------+------+
| Field                                | Null |
+--------------------------------------+------+
| id                                   | NO   |
| user_id                              | NO   |
| ~~                                   | NO   | # 省略
| job_category_number                  | NO   | # 新職種カラムをNOT NULLに変更
| recruitment_post_job_category_number | NO   | # 旧職種カラム
| created_at                           | NO   |
| updated_at                           | NO   |
+--------------------------------------+------+

7. 参照APIで旧職種と新職種の両方を返すように変更 [Webバックエンド]

すべてのユーザーテーブルのレコードや募集テーブルのレコードが旧職種と新職種の両方を必ず持つようになったので、既存の参照APIのレスポンスに新職種の情報を追加して、職種に関するすべての参照APIで旧職種と新職種の両方を返すようにしました。

すべての参照APIで旧職種と新職種の両方を返す

8. 既存の職種一覧APIを参照している箇所を新職種一覧APIを参照するように変更 [Webフロントエンド・ネイティブアプリ]

ユーザーや募集の職種を選択する際に使っている職種一覧APIを、新職種一覧APIを参照するように変更しました。

ユーザー職種一覧と募集職種一覧

9. 参照しているAPIレスポンスの旧職種を新職種に変更 [Webフロントエンド・ネイティブアプリ]

最後に、参照API・作成API・更新APIのレスポンスに含まれる旧職種を参照している箇所を、同レスポンスに含まれる新職種に変更しました。

10. 旧職種関連のコードやカラム、マスタテーブルを削除 [Webバックエンド]

ネイティブアプリの最低サポートバージョンでも旧職種が使われなくなると、APIで旧職種と新職種の両方を受け付ける必要がなくなるため、旧職種関連のコードや旧職種カラム、旧職種マスタテーブルを削除します。

こちらはまだ実施しておらず、今後行う予定です。

まとめ

今回は、Webアプリとネイテイブアプリの両方でダウンタイムを発生させずに、2つのマスタテーブルを統合した話を書きました。

ネイティブアプリの旧バージョンが入ってくると時系列的な観点でも考慮するべきことが発生し、移行手順は少し膨らみましたが、当初の目的通り2つの職種マスタテーブルを無事統合することができました。

同じ様に、ネイティブアプリの運用も行っている中でマスタテーブルを統合する際の参考になれば幸いです。

宣伝

最後に2つ宣伝させてください。

1つ目は、コラボイベントの募集です!

YOUTRUSTではOPEN CODEというイベントを毎月行っており、コラボして一緒に開催してくださる企業を常に募集しています!

「OPEN CODEは厳しいけど他の形式でならコラボしてみたい」という企業も大歓迎ですので、こちらもご興味がある方はぜひDMでお待ちしております!

(下はmybestさんとのコラボ回です!) youtrust.connpass.com

2つ目は、一緒に働く仲間の募集です!

YOUTRUSTでは現在ほぼすべてのエンジニア職を募集しています。

ご興味がある方はぜひカジュアル面談の申し込みをお待ちしております!

herp.careers