🎃

ポリモーフィック関連とConcernの力を借りて、マイベストの記事を構成する24個のモデルを6個に整理した話

2024/05/09に公開

今年度マイベストに24新卒として入社したゆーだいです。
mybest BlogKaigi 2024の16日目を担当します。

この記事で書くこと

この記事では、ポリモーフィック関連・Concernを活用して、技術負債をどのように解決したか?を実際の例を示しながら説明します。

※本記事で具体例を示す際は、分かりやすくするため、無駄な情報を省いたり、名前を変更したりしています。

整理する前はどのような状態だったか?

いきなり裏側の話をされても理解しづらいと思うので、簡単にマイベストの記事がどのような構成になっているかを紹介し、どのような技術的問題が起こっていたのか?を説明をします。

マイベストの記事の構造

マイベストには大きく分けて3種類の記事が存在します。
そして、これらの記事は各セクションによって構成されています。

赤枠で囲われたセクションたちには、以下の図で示すようなパーツという概念が存在します。
(厳密にはセクションごとに、パーツの構造が若干異なりますが、今回は省略します。)
今回はこのパーツを整理するお話です。

イメージが湧きやすいようにするため実際の記事を用いて説明すると、日焼け止めの記事の選び方セクションの選び方1つ目の場合、以下のように中見出しパーツとそれに紐づくコメントパーツから構成されています。

どのような技術負債が発生していたのか?

一言で言うなら、各パーツのテーブルが紐づくセクションによって別々で用意されていることが負債になっていました。

以下の図を見てください。
例えば、中見出しパーツの場合、テーブルが7つ存在していました。(お役立ちコンテンツの情報セクションに紐づく中見出しパーツテーブル1。商材コンテンツの事前情報セクションに紐づく中見出しパーツテーブル2。...)
※商材コンテンツの事前情報セクションと事後情報セクションに関しては、同じモデルを継承している関係で紐づく中見出しパーツは同じになります。が、ややこしいし、本筋とは関係ないので気にしなくて大丈夫です。

詳細に説明していきます。
全部で理解しようとするとわかりづらいので、商材コンテンツ周りの中見出しパーツに絞って説明していきます。まずは、簡易ER図を見ていきます。

先ほど説明したように、商材コンテンツには選び方/検証/事前情報/事後情報セクションが存在します。
商材コンテンツと各セクションはポリモーフィック関連で紐づいています。
次に、各セクションと中見出しパーツはsection_idという外部キーによって紐づいています。
そして最後に中見出しパーツと小見出しパーツはcontent_idという外部キーによって紐づいています。
この画像では商材コンテンツに紐づいているものしか示していませんが、商品コンテンツ、お役立ちコンテンツも似たような状態になっています。そのため、中見出しパーツのテーブルが合計7つある状態と言うわけです。

そして、コードで見ると以下のようになっていました。
※ここでは、7つある中見出しパーツのうち、3つに省略しています。また、省略していますが、実際には各クラス間で重複したバリデーションやメソッドが存在します。

このように、ほとんど同じ役割、プロパティを持つものであるにも関わらず、紐づく元が異なるだけで別テーブル、別モデルとして扱われています。(実装当初は、同じ中見出しパーツでもセクションごとに役割やプロパティに差異があったり、その後仕様が異なっていくことが想定されていましたが、運用していくなかで似たような仕様になっていったという歴史があります。)
これにより、新規機能の追加や既存コードへの変更が行われるたびに、テストを含めてかかるコストが大きくなる問題が発生していました。

ちなみに、リンクパーツは以下のように紐づいており、商材コンテンツ/商品コンテンツ/お役立ちコンテンツごとにテーブルが用意されています。(ctaリンクパーツ/コメントパーツもリンクパーツと同様。図が線だらけになるので、リレーションは省略しています。)

どのように整理したのか?

タイトルにもあるように、ポリモーフィック関連Concernを用いて、整理しました。

ポリモーフィック関連で1パーツ1モデルに共通化する

整理後の簡易ER図を見てもらうのが一番早いと思います。

中見出しパーツの情報を持つテーブルsection_component_contentsを用意し、それをポリモーフィック関連によって、各セクションと関連づけました。
繰り返しにはなりますが、画像には商材コンテンツ周りのものしか載せていないため、省略されていますが、「商品コンテンツ/お役立ちコンテンツに紐づく各セクション」にも、section_component_contentsは関連づけされています。

コードで見ると、以下のようになります。
先ほど示した整理前のコードでは、7つのクラスが必要でしたが、1つのクラスにまとめることができました。

他のパーツに関しても同じようにポリモーフィック関連を用いることで、1パーツ1テーブルになるように共通化しています。

Concernで中見出しと小見出しから重複コードをなくす

これまでの説明を聞いていて、なんとなく思った方もいるかも知れませんが、中見出しパーツと小見出しパーツは、紐付き先が異なるだけでおおよそ同じような役割のものになります。

中見出しパーツと小見出しパーツを1つのパーツに共通化することも考えましたが、むしろ複雑になることなどから、悪手だと判断し、今回は見送りました。

そうして共通化された後のコードがこのようになります。

ここでも、リレーションのみを記載していますが、実際にはバリデーションやメソッドなどさまざまなコードが2つのモデル間で重複した状態で存在しています。

このような場面では、Concernを用いることで整理を行うことができます。
Concernを用いて整理すると、以下のようなコードになります。

ContentComponentableというモジュールを用意し、そこに中見出しパーツと小見出しパーツ間で重複する処理を書いていきます。
そして、中見出しパーツと小見出しパーツそれぞれで、ContentComponentableモジュールをincludeすることで、重複したコードをなくすことができます。

その結果

今回の移行pjtによって、91ファイル1688行のコードを削除することができました。
テーブルに関しては、24テーブルを6テーブルにまとめることができました。

たくさん削除できれば良い。というものではないのですが、余分なものをこれだけ消すことができました。
また、それぞれのパーツを共通化したことにより、新規機能の追加や変更にかかるコストとバグが発生する可能性を一気に下げることができました。

まとめ

今回は、ポリモーフィック関連とConcernを用いて技術負債を解決した話をしました。
「ポリモーフィック関連」と検索するとサジェストで「SQLアンチパターン」と出てくることもありますが、上手く活用することでテーブル・コード量を削除し、シンプルなコードに置き換えることができます。

私自身も、設計を行う前にポリモーフィック関連によって関連づけられたモデルを利用する際は、「ちょっとわかりにくいな〜」と感じていましたが、設計を行う側に立つことでその良さに気がつくことができました。

「このモデルほとんど同じことしてるのに、モデル分かれてて面倒なんだよな〜」みたいなものを見つけた際には、ぜひこの記事を参考に、ポリモーフィック関連を用いて整理してみてください!

ポリモーフィック関連を用いた設計に移行する際に問題になるのが、データ移行の問題だと思います。今回は、記事のボリュームの都合上省略していますが、どこかでデータ移行についても書きたいと思います。

Discussion