はじめに
前回はアーキテクチャの基礎である3層アーキテクチャを解説し、RailsのMVCとの関係性、そこからユースケース層の追加による4層アーキテクチャへの拡張の話をしました。
今回からはRailsでよく使われてきた有名なデザインパターンについて解説していきます。デザインパターンとは特定の設計問題を解決するための手段をパターン化したものです。あくまで手段です。そのことを意識しつつ、何を解決するためのデザインパターンなのか、そしてデザインパターンが担うのはどの層なのかを俯瞰してみていきましょう。単に便利な設計方法のように捉えるのではなく、層の概念と合わせて捉えることで、Railsのアーキテクチャに関して体系的に理解していくことができると思っています。
これから紹介するのはこちらで紹介されている7つのデザインパターンです。上から順番に説明していきます。
- Value Object
- Service Object
- Form Object
- Query Object
- View Object
- Policy Object
- Decorator
本当に使われているものなのかと疑問に思う方がいるかもしれませんが、自分が今まで出会ってきたRailsのプロダクトでは実際にこれらのパターンが使われている場面に何度も遭遇してきました。またこれらのデザインパターンで検索すると色々な方が書かれた記事も出てきますので安心してください。
Value Object
今回紹介するのはValue Objectと呼ばれるデザインパターンです。
どの層を担う?
Value Objectが担うのはドメイン層です。根幹となるビジネスロジックの部分を担うことになります。
Value Objectはモデルの属性に関するロジックをまとめてくれる役割を果たします。
![](https://taharuku.com/wp-content/uploads/2024/07/Untitled-Diagram-Page-7-5-1024x632.png)
どういうときに使う?
モデルの特定の属性に対して
- 同じロジックが散らばってしまっている
- 特定の属性に対するロジックだけでモデルが肥大化している
というような場合に有効です。抽象的でピンとこないこともあると思うので具体例でみていきます。
具体例
例として次のような開発をしているとしましょう
- ECサイトを作っている
- 出品する側のユーザーを表すCustomerというモデルがある
- 購入側のユーザーを表すMemberというモデルがある
- それぞれのモデルに電話番号を格納するためのphone_numberという属性がある
- phone_numberに関するロジックは共通
- 出品側であろうと、購入側であろうと電話番号の扱いは変わらないというのはある程度直観的でもわかると思います。出品側は必須だけど、購入側の方は必須ではないとか実際は異なるケースもあるかもしれないですが、ここでは完全にロジックは共通としましょう。
このようなケースの時、各モデルの実装は次のような感じになりそうです。例として携帯用の番号かどうかを判定ロジックと、バリデーションロジックがあるとしました。
これを見たとき、電話番号に関するものをまとめたいというモチベーションが出てきそうです。またこのままだと、電話番号に関するロジックを変更したいとなったときに、2つのモデルを修正しないといけないことになります。この例は簡単なので変更に対するコストはあまりなく楽な作業になりますが、もっと複雑でもっと他のモデルにも使われているようなアプリケーションの場合は変更も大変になりそうで変更の抜け漏れの心配も出てくるのは想像できますね。
そこで、アプリケーションで扱う電話番号は、MemberであってもCustomerであっても同じ概念なのでこれをPhoneNumberクラスにまとめて切り出してやりましょう。
実装例
PhoneNumberクラスは以下のような実装になります(一例です)
これがValue Objectです。
モデルの実装は以下のようにすることができます。
使い方も簡単です。
実装ポイント
実装のポイントとしては2つです。
- Value Objectには等値比較できるように自分でメソッドを書く
- PhoneNumberクラスのままだと、プリミティブの値のときのように等値比較ができないので、==やeql?といったメソッドを定義して、等値比較ができるようにしています。この辺はRubyの実装の話なので細かい説明は割愛します。
- モデルでは composed_of メソッドを使って普通の属性のように使えるようにする
- composed_ofは実はRailsにもともと存在する機能です。Value Objectを使うために用意されています。
- こちらもどういう仕組みかなどの詳細は割愛します。
解決される問題
PhoneNumberクラスというValue Objectの導入によって、電話番号に関するロジックが1つに集約され、非常に明快になりました。バリデーションやメソッドの変更時もこのクラスの修正だけで済むようにもなりましたね。
まとめ
- 今回はValue Objectというデザインパターンを解説しました。
- Value Objectは4層のうちドメイン層を担います。
- モデルの特定の属性に対するロジックは1つにまとめてくれる役割を持ちます
次回はService Objectを扱います。
コメント