Sooey

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/checkoutapp/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_tolink_toを組み合わせて適切なCSSクラスを持ったボタンを定義するような用途はそれほど問題にはならない
  • link_to_userといったようなドメイン固有の知識を含む使い方はよくない
  • Draperなどを使ってメソッドをデコレーター内に含めるようにする

コメント欄ではサービスクラスに対して「クラスよりモジュール関数にしたほうがよいのでは」、「サービスクラスのメソッド名はForgotPassword.forgot_passwordForgotPassword.executeとどっちのスタイル?」といったやりとりもされているので、読んでおくと面白いかも。