2013-12-21 02:50:08 +0900
Railsアプリが大きくなっていくにつれてうまく機能しなくなっていくパターンというものがあり、それに関してReverb.comが簡単に5つのアーキテクチャアンチパターンとしてブログにまとめていました。
Reverb.comはミュージシャンのためのマーケットプレイスサービスです。
紹介されている内容は、ドメインレイヤーのコードがRailsの想定する枠組みの外に配置されている構造を前提としいます。具体的には、ドメインレイヤーのクラスをActionControllerやActiveRecordといったRailsのアーキテクチャの範疇からは独立したものとして構築している形を指しているようです。
(1) 責務を抱えすぎたサービスオブジェクト (Service objects with many responsibilities)
- 名詞+Serviceのような命名は責務の肥大化を招く
UserService
ProductService
- 単一のことだけをやるユースケースクラスとして定義したほうがよい
Reverb::Accounts::ForgotPassword
Reverb::Orders::FinalizeCheckout
- 単一のユースケースからしか使われていないメソッドを探し出してクラスに移動させる
- あるユースケースのためだけのメソッドをActiveRecordモデルにどんどん追加していくのはやめる
参考:The Clean Architecture | 8th Light
(2) 名前空間に分けられていないドメインレイヤーのクラス (Un-namespaced classes in the domain layer)
- Reverb.comのドメインレイヤーは
app/reverb
以下に236個以上のクラスとして定義されている - 責務によって
app/reverb/checkout
やapp/reverb/offers
のように分類している - サービスクラス名と同じように名前空間もドメインコンセプトにしたがって命名している(Railsにおける慣習的な命名はしない)
(3) 機能を共有するためのモジュールインクルード (Including modules to share functionality)
- ミックスインによるコードの共有は役立つこともあるが常用すべきでない
- メソッドがどこで定義されているのかを見つけたり、クラス定義をざっと読んでパブリックなAPIを理解するのが難しくなってしまう
- パブリックメソッドを含んだミックスインはオブジェクトの表面を広げるので、そのクラスを利用するコードとの結合を強めてしまう
- ミックスインの代わりに、関連するオブジェクトへのデリゲートを明示的に行うようにする
(4) あらゆる種類の動的なメソッド呼び出し (Any kind of dynamic method invocation)
send
、特にメソッド名を動的に指定するような呼び出しはgrepもリファクタリングもしづらい- Reverbではアプリケーションコード内(だいたい25,000行)で、
method_missing
は1度も使っていない - こうしたテクニックはメンテナンス時の頭痛の元となる
- 凝ったDSLを作ろうとしない限りは不要だし、そういうものは外部のgemとして作るほうがいい
- これを徹底するため、コードベースに
send
の呼び出しがないことをテストするRSpec Matcherを使っている
(5) Railsのヘルパー (Rails Helpers)
- グローバルにアクセスできるメソッドを定義してしまうので使わない
button_to
とlink_to
を組み合わせて適切なCSSクラスを持ったボタンを定義するような用途はそれほど問題にはならないlink_to_user
といったようなドメイン固有の知識を含む使い方はよくない- Draperなどを使ってメソッドをデコレーター内に含めるようにする
コメント欄ではサービスクラスに対して「クラスよりモジュール関数にしたほうがよいのでは」、「サービスクラスのメソッド名はForgotPassword.forgot_password
とForgotPassword.execute
とどっちのスタイル?」といったやりとりもされているので、読んでおくと面白いかも。