Railsのお勉強
古いがとりあえずこれから
ふわっと説明すると、レイヤアーキテクチャというのはアプリケーションを責務に応じたいくつかの層としてとらえる設計手法のことです。このとき上の層が下の層を一方的に利用するようにすることで、オブジェクト間の結合を疎に保ち、ドメインロジックの凝集度を高めることができます。詳しくは文献[2]の前半を参照してください。
とりあえず押さえるべき点は各オブジェクトはいずれかの層に属し、複数の層にまたがることはない
層の関係は一方通行であり、相互参照する関係は層をまたがない
ということです。
4つの層に分けられます。上から順に
レイヤ | 役割 |
---|---|
ユーザインタフェース層 | ユーザに情報を表示し、ユーザの入力を解釈する。 |
アプリケーション層 | ドメイン層のオブジェクトを協調させる。ビジネスに関する知識を持たず、作業を調整するだけ。 |
ドメイン層 | いわゆるビジネスロジックを表現する。一番重要な層。 |
インフラ層 | 上位レイヤを支える一般的な技術。MySQLとかRedisとか。 |
下の層は上の層のことを知っていてはいけない、というのはインフラ層を見れば納得感がありますね。MySQLが特定のアプリケーションのために設計されたものだったら困ってしまいます。
で、MVCをこの4層に当てはめると以下のように対応付けされます。
レイヤ | MVC |
---|---|
ユーザインタフェース層 | View |
アプリケーション層 | Controller |
ドメイン層 | Model |
モデルを表現するパターン
名前 | 役割 |
---|---|
エンティティ | 別の実装をまたいでも追跡されるような連続性と一意性を持ったもの。同一性を持つ。 |
値オブジェクト | 他の何かの状態を記述する属性であり、同一性を持たない。 |
サービス | オブジェクトより手続きとして表現したほうが明確なドメイン。エンティティや値オブジェクトに責務を押し付けないほうが適切なものがここにくる。 |
以上の3つのオブジェクトに対し、オブジェクトのライフサイクルを通じて整合性を維持し、管理が複雑だとしてもモデル自身はシンプルに保つ必要があります。そしてライフサイクルの変遷をカプセル化し、ドメインを綺麗に保つために以下の3つの役割を導入します。
名前 | 役割 |
---|---|
集約 | 明確な所有権と境界を定義する。整合性を保つ上で決定的に重要な役割を持つ。 |
ファクトリ | オブジェクトの生成・集約をカプセル化する。ライフサイクルの始まり。 |
リポジトリ | 永続化されたオブジェクトにアクセスする手段を提供する。 |
ActiveRecord
を使っているとこれらが全て一つのmodelの中に詰め込まれる事態が発生しがちです。が、それでなんとかなるのは小規模サイトまででしょう。
ユーザインタフェース層
views
言わずと知れたHTMLをレンダリングするためのやつです。
view_objects
これは参考文献[1]に出てくる同名のクラスとほぼ同じです。
下の層は上の層のことを知るべきではないので、modelは特定のviewと紐づくメソッドを持つべきではありません。かと言ってviewの中でごりごりロジックを書くのはヤバイので、その間の橋渡しをするのがview_objectsです。
例えばQiita:Teamのエクスプローラにはタグツリーが表示されます。ドメイン層で見ると複数のTag
が存在しているだけです。これを使いやすい形に変換してviewとmodelの汚染を防ぐのがview_objectsの役目です。
アプリケーション層
controllers
省略(だんだん書くのがだるくなってきたぞ...)
observers
Railsにはバージョン3.?までObserverという機能がcoreにありましたが、今ではrails-observers gemに切りだされました。詳しい経緯は追っていませんが、おそらくは後ででてくるcallbacksと役割が被るからというのがcoreから排除された主な理由なんじゃないかと想像します。
ではなぜそのcallbacksと合わせてobserversも使っているのかというと、observersはアプリケーション層にいる(という風に区別して使っている)からです。observersがいることでcallbacksがドメインロジックに専念することができます。
observersに書かれるものには例えば社内ツールへの通知とかキャッシュへの追加・削除みたいな、modelの特定のタイミングで実行したいがドメインロジックではないようなコードです。つまりobserversを全てoffにしてもアプリケーションとしての振る舞いには大きな支障をきたさないということです。
decorators
参考文献[1]にも同名のパターンが出てきますが、あれとだいたい同じです。違いは、特定条件下でのみ実行されるobserversの処理を切り出して軽くするためのものであって、callbacksをリファクタリングするものではない、たとえその処理が特定のcontrollerの特定のactionでのみ発生するものなのだとしても、それがビジネス的に重要であればそれはドメイン層に存在すべきだ、という事です。
parameters
参考文献[1]にformsオブジェクトパターンというのが出てきて、その利点として
Formオブジェクトを導入するメリットとして、ActiveRecord自身の中でvalidationを行なうという融通の効かない方法に代えて、validationロジックをコンテキストに応じた場所で定義できるというのがあります。
と言われていますが、アプリケーション層とドメイン層がformsの中で入り交じっていて嫌な感じがします。変わりに参考文献[5]で出てきたparametersと後述するfactoriesを使っています。
parametersはユーザが入力したデータをmodelsやfactoriesに都合がいい形に変換するラッパのような存在です。ちょうどview_objectsがやっていることの反対のことをするのがparametersです。
ドメイン層
models
超重要なやつです。なんでもかんでもこいつに突っ込むのは間違いだからどうにかしよう、というのはこの記事と参考文献[1]の主張するところです。
(書くの疲れたもうやめたい)
mailers
メール送るやつです。ドメイン層と捉えるべきかアプリケーション層と捉えるべきか悩ましい感じですが、callbacksの中から呼び出すことがあるので、ドメイン層に分類しています。
callbacks
modelのbefore_xxx
とかafter_yyy
とかを委譲するのがこのcallbacksです。前述のobserversとは違い、ドメインロジックを表す重要な役割があります。
このときItem
モデルに紐づくからItemCallback
, ItemObserver
だとやってしまいがちですが、そうするのは短絡的でよろしくない。初めのうちはそれでいいかも知れませんが、大きくなってきたら意味のあるまとまりに分割しましょう。
詳しくは参考文献[4]参照。
validators
validatorsはmodelsのバリデーション機能を切り出すものです。modelsがデータとその振る舞いをカプセル化し、validatorsはそのデータについての制約を記述する場所です。
詳しくは参考文献[4]参照。
policies
policiesという名前から参考文献[1]のやつと同じかと思われるかも知れませんが、実体は微妙に違います。やっている責務は同じですが。
ユーザの権限管理にcancan gemを使っています。DSLの可読性がいいです。cancanはabilityというオブジェクトを作ってそこにユーザの権限を書いていくのですが、管理対象が増えてくると肥大化して辛い感じになります。
policiesはabilityにあったロジックを関連するmodelsに持たせることを目的としたオブジェクトです。abilityの中でuserと対象オブジェクトのpolicyから個別の権限があるかないかを判断する、という構造です。なので、policiesはuserとmodelsの間の関係を記述するための場所、と言えます。
ちなみにcancanは開発が止まっているので、forkされたcancancanを使った方がいいです。
queries
これは上で書いたリポジトリに相当するものです。ActiveRecord
使うとmodelのクラスメソッドとして実装することが多いですがqueriesに切り出します。参考文献[1]のやつとだいたい同じ。
services
ドメインを形成する大事な処理だけど、modelやvalue_objectに持たせるのが不適当なものがここに来ます。といっても割となんでもありというか、concernを使う代わりにserviceに委譲するみたいな感じで、ドメイン層の泥臭い部分がここに押し付けられている、というのが実体に近いのかも知れません。
value_objects
value_objectsは読んで字のごとく前述した値オブジェクトのことです。同一性は持たないけれど比較ができる。例えばQiitaの投稿のタグ情報は、どのタグとそのバージョンの組で表されますが、これ自体には同一性は無いし、ライフサイクルもありません。
ActiveRecord
のcomposed_of
で紐付けるのもvalue_objectの1つです。
factories
これは複雑な集約を作るのに使うものです。一般的にはparameterを受け取ってmodelsを初期化します。
サマリー
foreign_keyがわからなくなったので整理する
まずは外部キーの定義から
外部キーとは、リレーショナルデータベース(RDB)で、テーブルのある列に、別のテーブルの特定の列に含まれる項目しか入力できないようにする制約。また、その際に指定する列。標準のSQLではFOREIGN KEY句を用いて設定できる。
Railsにおける:foreign_keyの使い方
この文脈においてはbelongs_toで参照する場合を示す。
基本はbelongs_toで使用する。
そして通常はマイグレーションファイル、もしくはDBのDDLに記述すれば事足りる
モデルで使用するのは次のようにカラム名が変更になるケース。
具体的には多対多の場合、class_nameやprymary_keyとセットで用いられる
基本的にはbelongs_toのカラム名が規約通りに設定されていればモデルで使用することはない。
上のように独自で設定する場合に既存の設定を上書きするために用いると考えれば良い