Ruby on Rails(以下、Rails)はオープンソースのWebフレームワークであり、多くのWebエンジニアが利用しています。
しかし、Railsプロジェクトが大規模になると、コードの管理や拡張性が問題になることがあります。
この記事では、そんな問題を解決するためにRailsで採用できる主要なデザインパターンについて詳しく解説します。
目次
アーキテクチャとは?
まずはアーキテクチャの説明から行います。
「知ってるよ」って方は飛ばしてください。デザインパターン一覧はこちら
アーキテクチャはソフトウェアの全体的な設計に関わります。
これには、システムの主要なコンポーネントやそれらの相互作用も含まれます。
アーキテクチャの主な目的は、システムの全体像を理解し、その構造と動作を明確にすることです。
アーキテクチャはプロジェクト全体に影響を与え、後から変更することが難しい場合もあります。
Railsのアーキテクチャ
Railsのアーキテクチャは、MVC(Model-View-Controller)パターンに基づいています。
このアーキテクチャは、アプリケーションの構造と責任を明確に分けることを目的としています。
今回はデザインパターンの記事なので、軽く説明だけしておきます。
Model(モデル)
モデルは、データベースとのやり取りを担当します。データの検証、保存、更新など、ビジネスロジックの大部分がモデルに実装されます。
View(ビュー)
ビューは、ユーザーに表示されるインターフェース(通常はHTML)を生成します。
ビューは、モデルから受け取ったデータを元に、ユーザーが見るページを構築します。
Controller(コントローラ)
コントローラは、モデルとビューをつなぐ役割を果たします。
ユーザーからのリクエストを受け取り、必要なモデルを呼び出してデータを処理し、適切なビューを選んでレスポンスを返します。
デザインパターンとは?
デザインパターンとは、ソフトウェア開発においてよく使われるプログラムの設計方法です。
デザインパターンは、特定の問題を解決するための再利用可能なソリューションです。
アーキテクチャはシステムの大枠を定義し、小〜中規模の構造を定義するためのパターンになります。
Railsを使うならアーキテクチャは基本MVCになると思うので、デザインパターンを主に考えていくことになると思います。
デザインパターンを使う場面
まずは普通にModel View Controller にコードを書いてみて、ロジックがどのくらい大きいかを確かめて、
MVCの役割からはみ出ている部分は、デザインパターンを使って責務の切り分けを行うというふうに使うのがおすすめです。
デザインパターン一覧
前置きが長くなってしまいましたが、Railsで使用できそうなデザインパターンの一覧と、メリットデメリットを以下に書いていきます!
個人的な意見もあるので、あくまで参考までに…
Decoratorパターン
何を目的としたデザインパターンか
モデルに対するビューのロジックをカプセル化する
ModelとViewの中間にデータの加工の役割を担う
特徴
viewに表示したいメソッドを追加したけれど、モデルに書くと肥大化してしまうものをdecoratorファイルに記述して、責務の切り分けをする
メリット
ViewやModelの役割を保ちながら、装飾することができる
ViewやModelの肥大化を防ぐ
デメリット
モデルとDecoratorを1対1にすると、単一のDecoratorに多くのメソッドが集結しDecoratorが肥大化してしまう可能性がある
参考記事
https://note.com/kentarotawara/n/n0c1ff2060cf7
https://techracho.bpsinc.jp/hachi8833/2018_03_06/53535
Presenterパターン
何を目的としたデザインパターンか
RailsのView内で使用される複雑なロジックを分離するDecorator
とPresenter
の違いは単一のモデルを扱うか複数のモデルを扱うかどうか
特徴
- ビューに関連するロジックをまとめるレイヤー
- ビューロジックの分離と再利用性の向上
- ビューの責務をシンプルに保ち、テスト容易性と可読性を向上
メリット
- インスタンス変数やsetter系のメソッドが減り、controllerを薄くすることができる
- テスタビリティが高い
- POROでもはじめられるので参入障壁が低い
※ POROとはPlain Old Ruby Objectの略で、Active Recordなどを継承していないクラスを作成すること
デメリット
特に見つかりませんでした。
参考記事
https://qiita.com/d-haru/items/b1d4ee97b7dd5a98c697
Deliveryオブジェクト
何を目的としたデザインパターンか
メール送信や通知に関するロジックをカプセル化する責務を持つ
特徴
- メール送信や通知の具体的な実装のカプセル化
- RailsにはデフォルトでActionMailerというしくみがあるため、SlackやLINEなどのチャットツールに通知機能に使われることが多いのかも?
メリット
- コードの可読性と保守性の向上
- ビジネスロジックから具体的なデータ送信処理を分離することで、コードの意図が明確になる
デメリット
特に見つからなかった
参考URL
https://github.com/palkan/active_delivery
Formオブジェクト
何を目的としたデザインパターンか
ユーザーからの入力を整形・検証して永続化する
特徴
コントローラで行うフォームの処理類をFormオブジェクトで行う
メリット
- コントローラの肥大化を防ぐ
- FormオブジェクトはActiveRecordモデルの
#save
や#update
のような単純な命令以外のことをするフォームに有用
→ActiveRecordモデルを操作したり、複数の子レコードの処理 - フォームのふるまいに関するユニットテストが書きやすい
- フォームに関する記述が一箇所にまとまり読みやすい
デメリット
Formオブジェクトがもつべき責務を明確にしないと肥大化してしまう
Serviceオブジェクト
何を目的としたデザインパターンか
肥大化したActiveRecordモデルを分割して、コントローラをスリムかつ読みやすくする
特徴
自由度が高く、便利な開発パターン
分類が難しいものに使う
メリット
- ModelやControllerがシンプルになり、可読性が向上する
- 処理がservice層に切り出されている為、Modelのテストが書きやすい
- Webのインタフェースからビジネスロジックを切り離すことで、責任範囲が明確になる
デメリット
- 明確なルールが存在しない
- チームでの認識を合わせるのが難しく、運用が難しい
- 本質的にServiceオブジェクトパターンそのものには、コードベースを読みやすくする力も、メンテしやすくする能力も、concernをうまく分割する手腕もない
- メンテナンスする人にとっては再利用しにくく、自動テストもやりにくい
参考URL
https://qiita.com/ren0826jam/items/6d3fa4c5dbdf1625e441
https://techracho.bpsinc.jp/hachi8833/2022_03_17/46482
https://tech.speee.jp/entry/ieul-rails-design-pattern-kpt-2021
Interactorオブジェクト
何を目的としたデザインパターンか
ビジネスロジックをカプセル化するためのモデル層に属するクラス群
- 複数のActiveRecordモデルを操作する処理
- 外部APIとやりとりする処理
などInteractorオブジェクトに記載する
特徴
ひとつのInteractorオブジェクトはひとつの責務を持つこと
メリット
- 役割(インタフェースや責務に関するルールがある)が明確で、Gemによるルールがあるため設計の原則を守りやすい
- トランザクションの管理や、例外処理など、ビジネスロジックの側面以外の処理も扱うことができる
- コントローラの肥大化を防ぐ
デメリット
特に見つかりませんでした。
参考URL
https://applis.io/posts/rails-design-pattern-interactor-objects
https://takaokouji.github.io/output/interactor/
Policyオブジェクト
何を目的としたデザインパターンか
ビジネスルールをカプセル化する責務をもつ
例: ユーザーの役割に応じて処理を実行する権限をもつかどうかを判断したりします
特徴
- メソッド名の末尾は常に
?
にする - メソッドは
true
かfalse
のいずれかだけを返す - 渡された属性は変更しない
- コードはシンプルな読み出しロジックだけを行う: データベース呼び出しなどは行わない
メリット
コントローラやビューがルールに関するロジックであふれてしまうのを防ぐ
デメリット
見つかりませんでした。
参考URL
Queryオブジェクト
何を目的としたデザインパターンか
コントローラからクエリ操作の責務を分離し、ActiveRecordモデルと疎結合に保つ
特徴
ActiveRecord::Relationに対して結合や絞り込み、ソートなどの操作を行い、Relationを返す
スコープが複数のカラムとやり取りする場合や、他のテーブルとJOINする場合は、Query Objectへの移行を検討する
メリット
- Relationに対する複雑な操作や再利用性のある操作を分割することで、再利用ができる
- ActiveRecord::Base継承クラスのスコープ経由で呼び出すことで、依存関係が明確になり、また返却されるクラスも自明になる
- コードの再利用性が上がる
デメリット
見つかりませんでした。
Validatorオブジェクト
何を目的としたデザインパターンか
ActiveRecord::Base継承クラスのレコードを検証する責務をもつ
特徴
複数の場所で利用される検証ロジックを書くことができる。
メリット
- 検証のロジックを再利用できる
- モデルのコードがシンプルに保たれる
デメリット
(私的意見)複数のカスタムValidatorを使用する場合、その管理や理解が難しくなることがありそう
参考URL
https://guides.rubyonrails.org/active_record_validations.html#validates-with
Valueオブジェクト
何を目的としたデザインパターンか
ドメイン駆動設計における値オブジェクトをカプセル化する
特徴
ある値が抽象化できる概念であり、かつ複数のクラスから属性として参照されるケースで利用する
- 名前における姓名やミドルネーム、住所における国や県・市区町村など、複数の値を組み合わせてひとつの値を算出するもの
- 日付や通過、気温、あるいは商品のレビューにおける星の数など、値の表現や比較を行うもの
メリット
- 可読性の向上
- Valueオブジェクトを使用することで、コードの意図が明確になる。
- ドメイン概念を表現し、意味のある名前を持つことが多いため、可読性と理解しやすさが向上する。
- 再利用性の向上
- 数のモデルやコンテキストで再利用することができ、コードの重複を減らせる
デメリット
値オブジェクトとして切り出せるものはたくさんあるが、すべてをその都度Valueオブジェクトとして扱うと、かえってコードが煩雑なる可能性がある
View Componentオブジェクト
何を目的としたデザインパターンか
ビューをコンポーネント単位でカプセル化する責務をもつ
ビューのコードを再利用可能なコンポーネントとして抽象化し、ビューロジックを分離することができる
特徴
Rails 6.1 からデフォルトでサポートされるようになったgithub製のgem
再利用性のあるビューを、ビジネスロジックごとカプセル化できる
メリット
- 期待するデータを明示的に書ける
- 独立して、高速に動くテストを可能にする
- テストしやすい
- データフローを理解しやすい
- パーシャルよりもパフォーマンスも高い
デメリット
特に見つかりませんでした
参考URL
https://qiita.com/Daniel-81/items/1b7608791a012f4f504a
https://kenzoblog.vercel.app/posts/view_component
デザインパターンの注意点
デザインパターンを導入することの全体的なデメリットは、学習コストと導入の複雑さです。
理由としては、パターンを正しく適用するためには、開発者がパターンの理解と実践に時間を費やす必要があるからです。
チーム内で使う扱う場合も、使い所を決めないと、後々困ることも多いので導入は慎重に行うべきだと思いました!
まとめ
- アーキテクチャは「全体像」に関わり、システム全体の設計と構造に影響を与える
- デザインパターンは「部分的な解決策」に関わり、特定の設計上の問題を解決するためのテンプレートやガイドラインを提供する
Railsで利用可能なデザインパターンは多岐にわたります。
これらのパターンは、コードの再利用性やメンテナンス性を高めるための具体的なソリューションを提供します。
今回紹介した記事が、少しでも皆様の役に立てると幸いです!