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とどっちのスタイル?」といったやりとりもされているので、読んでおくと面白いかも。

2013-12-07 15:49:25 +0900

rack-mini-profiler 0.9.0.preの話。

Railsアプリで使っているrack-mini-profilerの設定を変更して、

  • デフォルトでは無効
  • URLに?pp=enabledを付ければ以降のセッション中は有効

という挙動にするため、GitHub上にあるREADMEのConfiguration Optionsを参考にconfig/initalizers/mini_profiler.rb

# Have Rack::MiniProfiler start disabled - you can use query string option to re-enable later
Rack::MiniProfiler.config.enabled = false

と記述してみたところ、

NoMethodError: undefined method `enabled=' for #<Rack::MiniProfiler::Config:0x007ff12a1305b0>

と例外が発生してしまう。

ソースコードをよく見てみるとHEADのREADMEで言及されているオプションは2013年12月5日にリリースされた0.9.0.preから利用可能になったもののようで、今のところのstable releaseである0.1.31では使えないみたい。

なので、Gemfileを

gem 'rack-mini-profiler', '0.9.0.pre'

と書き換えて最新版を使うようにしたことで、上記の設定が使えるようになった。

2013-12-04 22:41:43 +0900

Bundlerでgemに固有のビルドオプションを指定する方法。

libv8を利用しているRailsアプリケーションで、Mavericksにしてから改めてgemのビルド&インストールを行おうとすると(Mountain Lion時代にビルドしたgemが使えている限りはおそらく問題に遭遇しない)、libv8のビルドがこんな感じで失敗する。

Installing libv8 (3.11.8.13)
Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension.

    /Users/juno/.rbenv/versions/1.9.3-p484/bin/ruby extconf.rb
creating Makefile
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Unable to find a compiler officially supported by v8.
It is recommended to use GCC v4.4 or higher
Using compiler: g++

(snip)

Gem files will remain installed in /Users/juno/src/railsapp/vendor/bundle/ruby/1.9.1/gems/libv8-3.11.8.13 for inspection.
Results logged to /Users/juno/src/railsapp/vendor/bundle/ruby/1.9.1/gems/libv8-3.11.8.13/ext/libv8/gem_make.out

Mavericks上でlibv8のビルドを成功させるには、

$ gem install libv8 -v '3.11.8.17' -- --with-system-v8

というように--with-system-v8オプションを指定すればいいのだけど、Railsアプリでbundle installした時のインストール先をbundle install --path vendor/bundleのようにプロジェクトローカルなパスに指定している場合、普通にgem installしたgemは参照することができない。

そこで、bundle installした時にもgemに固有のビルドオプションを指定する方法を調べていたところ、あらかじめbundle configコマンドで設定しておけばよいことがわかった。

最終的に、Mavericksでlibv8をbundle installで正しくビルド&インストールするには以下のようにすればOKだった。

$ bundle config build.libv8 --with-system-v8
$ bundle install --path vendor/bundle