2012年10月1日月曜日

model の decorator の話

最近の sapporo.rb などでは ActiveDecorator など、model の Decorator の話で少し盛り上がっているようだ。view に関わるコードをどう整理するか?という話について、共通見解が定まってきたということだろう。

rails の MVC に沿ってコードを書くと、view 回りがだんだんごちゃっとしてくる。scaffold のように単純に model の property を表示するような view なら良いが、STI のタイプに応じて表示内容を変えるとか、新規登録の場合と更新の場合で表示を変える、ユーザの権限に応じてどこまで情報を見せるか制御するなど、model のデータやセッションのデータに基づく条件分岐と html の描画が絡み合うような場合にこれをどこに書くのか?というのが問題になる。

伝統的な rails のレイヤーわけだと候補としては view, model, helper のいずれかになるだろう。この中のどれが適切か?

view にロジックを書くな、ということは昔から言われている。view のようなUIに最も近いところにモデルのロジックが入り込んでしまうと仕様変更に弱くなる。またデザイナーと分業しているときにメンテがしづらくなる。だから view にはできるだけコードは書かない。

では model に書くのか?これもあまりうまくない。まずmodelには html を描画するための十分な能力が無い。helper を include してしまうとよくわからないメソッドが大量に流入してくる。また、view に関する雑多なメソッドが model に入り込むとすぐに fat な model が出来上がる。あるプロパティでも画面によって微妙に表示の仕方が異なる、ということはよく起きるが(たとえばメールと画面では微妙に違う)、そういう些細な違いのメソッドが沢山modelに生えるというのは好ましくない。

では helper に書くのか?helper に書くのは良いアイデアのように見える。view に直接ロジックを書かないようにするための仕組みがhelperだ。ここしかない。実際小規模のアプリでは十分うまく働く。しかし、規模が大きくなるにつれて問題が出てくる。原因はhelperがフラットなためだ。このフラットな空間に様々な view 用の メソッドが生えてくると結局管理できなくなる。ある helper メソッドがどの view で使われているのか、いちいち grep するか?また helper のデフォルトの分け方が controller 単位なのも管理がしづらい原因でもある。view のメソッドは view 単位か model 単位で分けられるのが自然だと思う。

そこで出てきたのがこれらの Decorator だ。上であげた3つのレイヤーいずれとも異なる新しいレイヤーだ。Decorator は model をラップすることで表示専用のメソッドを追加することができる。ラップしているだけなので元のモデルの挙動は確保される。また、必要なときに必要なロジックが追加できる。ラッパーから helper へのアクセスを用意しておけばhtmlの描画もそれほど苦ではない。

この Decorator をどういう単位で作成するか、というところについては検討の余地がある。選択肢としては、

  • 一つの View につき一つの Decorator
  • 一つの model につき一つの Decorator
  • View x Model の組み合わせにつき一つの Decorator
最初のものはいわゆる Presenter パターン。ActiveDecoratorは二つ目のパターン。最後のものは Exhibit パターンという名前がある。ここの理解は少し怪しい。最後の一番細かい粒度で設定できるのが使い勝手としては一番良いと思う。view か model のどちらか選べと言われれば view にすると思う。ActiveDecorator のような、model 単位で Decorator 一つ、というのは結局 helper と大差なくなる。


2012年9月26日水曜日

haml のうまい使い方

1年ほど前から業務アプリの開発に haml を使っている。

世の haml の紹介では html って閉じタグが無いからすっきり書けるよ!とか.Hogeって書くだけで<div class="Hoge"> になるよ!簡単!みたいなレベルで紹介されているが、haml の真価はそんなところにはないと思っている。haml が本当にすごいと感じるのは、view を作るための簡易 DSL を定義できるところにあると考えている。理想的には、html タグを一切かかない view、ということになる。(もちろんそのような原理主義的なやり方はうまくいくはずがない)。

世の中で紹介されている、haml すげー!という解説はこんな感じ。

 %div.SearchForm
    名前
    %input{:type=>:text, :name => :name}
    生年月日
    %input{:type=>:text, :name => :birthday}

上げられる利点は

  • 閉じタグ書かなくていいよ
  • クラスとかIDが簡単に設定できるよ
という感じ。上に書いたようにこれらはさほど重要ではないというか、こういう書き方だとデザイナーが入りづらくなっただけで私が良いと思う haml のコードは

  • 見た目上我々が認知するブロック構造と haml の構造が良く対応している
  • 全てのタグ(haml行)は単なる checkbox や div よりもビジネスロジック上やUI上リッチな意味を持つ(論理タグと呼ぶ)
  • ビジネスロジックやUI上意味を持たない余計な html タグ(物理タグと呼ぶ)があまり現れない

適当にでっちあげた例だが、例えば以下のようなものだ。

- search_form do
  - left do
    - search_field :name
    - search_field :email
  - right do
    - search_field :birthday
    - search_field :user_type_id
  - buttons do
    - clear_button
    - csv_download_button
    - submit_button

この例では検索フォームを記述するときに、フォームの左側と右側にそれぞれ何を書くか。サブミットボタンの部分に何を書くか、という設定をする。それ以外の枠組みは search_form というメソッドが書いてしまう。

あるいは、タブ形式の入力画面は以下のようになる。

- tabs do
  - tab "基本情報" do
    - input :name
    - input :kana
    - input :email
  - tab "住所" do
    - input :zip_code
    - input :address

ラジオボタンの選択結果によって入力できる場所が切り替わる(入力できない場所が disable されるようなもの)を書くときは

- exclusive do
  - choice "メールで連絡を希望" do
    - input :email
  - choice "電話で連絡を希望" do
    - input :tel

のようにする。

このようにすることで記述量を劇的に減らすことができるし、一行当たりの意味の濃度が圧倒的に高いので理解もしやすい。UIの要素ごとにきれいにコードの共通化が行われているため、品質の一定化が容易だし、変更にも非常に強い。

ただ、ほかでよく指摘されているように、デザイナーさんとの協業は相性が悪い。業務アプリのような、ページのコンポーネントが限られていて、かつデザイン上後から手を加えるようなことが少ない場合に使いやすい。私はもっぱら業務アプリに使っている。twitter bootstrap と組み合わせると非常に効果的だ。

また同様に、この DSL が動きだすまでの最初の準備にはかなり手間がかかる。そのため短期のプロジェクトではあまりペイしない。ここも twitter bootstrap でうまく組み合わせることでプロジェクトをまたがった再利用ができるようになると思う。

個人的にはさらに cucumber の用語とすり合わせることでテストが簡単に書けるようになるかなぁと考えている。

model を物理レイヤーと論理レイヤーに分割する。

エンタープライズrailsという書籍に、モデルをphysical レイヤーと logical レイヤーに分けろ、というアドバイスがあった。この分け方が絶対かといわれるとよくわからないが、私も基本的にはこの考え方には賛成だ。

モデルのレイヤーを分けて考えるということはrailsのような密結合のフレームワークにおいて長期にわたってメンテナンスするコードを書くのであれば必須の事項になると思う。

どのようなレイヤーが必要になるかはアプリの複雑度によると思うが、最低限必須なレイヤーが上で書かれているphysical(DB層) とlogical(ビジネスロジック層) の二つのレイヤーだ。モデルのメソッドを physical と logical に分離し、controller から呼び出すのは基本的に logical 層のメソッドに限定する(厳密に守る必要はないと思うが)こととする。

例えばブログの記事を投稿する、というビジネスロジックがあったとする。内部的にはこれは Post というモデルのインスタンスを save するということになる。しかしこの save という単語はビジネスロジックの単語ではない。ビジネスロジックでは post だ。ビジネスロジック上の「投稿」という処理を表すのが post であり、そのDBレイヤーでの実現方法としてsaveがある。

def post
  save
end

大事なことは、全ての post は save であるかもしれないが、全ての save は post ではないということだ。つまりこれらは異なる概念であり、混同すべきではない。controller から save を直接呼ぶというのはこれを混同していることになる。

以前 before_save の弊害について書いたことがあるが、その問題の具体例がこれにあたる.。たとえば、記事の投稿をするときに記事データからタグを除去するなど正規化の処理をかけたいという要件があったとしよう。これを before_save で実現するというのは post と save を混同しているということに他ならない。全ての save が post 処理ではないのに、post 時のフックがすべての save 時に発動してしまう。このような予期しないフックが見えないところで動いていると予想しづらいバグを生む。バグが起きたのでフックをはずそうということになったはいいが、一度発動した状態で運用が続いていたものに対して、そのフックを本当に外しても大丈夫か、というのを考えるのは骨が折れる。

また別の面でも問題がある。一般的に、仕様変更はビジネスロジックの概念の単位で行われることが多い。「post時の処理にxxxを追加」という要求は出てきても、「save時の処理にxxxを追加」という要求はあまり出てこない。ビジネス上の要件はビジネス上の言葉で表現されるからだ。だからcontroller と model のインターフェース部分にビジネスロジックのレイヤーを設けることには変更に強くなるという点で非常に意味がある。




2012年9月23日日曜日

to_sql のうまい使い方(2)

前回の記事で、通常の ActiveRecord のクエリではパフォーマンスがよくないという場合に、うまくARELのよさを生かしつつ find_by_sql で高速なクエリを実行するための方法を紹介しました。
to_sql と find_by_sql でクエリを高速化する方法としてもうひとつ適用例があります。

本題に入る前に ActiveRecord が持っているあるパフォーマンス上の問題を紹介します。もしかすると最近の rails では改善されているのかもしれませんが、確認せずに書いてしまいます。その問題とは、joins を使ったテーブルに対しては eager loading が行われない、というものです。たとえば

users = User.joins(:emails).where("emails.address like '%@gmail.com'").includes(:emails)

のようにして、gmail のアカウントを持つユーザをとってきたとします。このクエリでは includes によって emails を eager loading するよう指示していることに注意してください。

さて、先頭のユーザが softbank のアドレスも持っているかどうかを調べたいとしましょう。

users.first.emails.any?{|e| e.address =~ /@softbank.ne.jp\Z/}

この時、users.first.emails にアクセスしたときにどうなるでしょうか?includes(:emails) を指定しているのだから eager loading された結果の emails  がすでに存在するはずで、ここではクエリは発生しないはずです。ですが残念ながらそうはなりません。ここで select * from emails where user_id = xxx というクエリが発生してしまいます。いわゆる1+n問題が発生してしまうのです。しかもこれを回避するうまい方法というのもありません。preload_association がまだ生きていたころに association を強制注射してみましたが、eager loading のクエリは実行されるものの、モデルはセットされずに結局 lazy load が発生しました。

この問題に対してはいつも以下のように対処しています。

まず、前回の記事で説明したような方法を使って、select + to_sql でモデルの id のみを取得します。

ids = User.connection.select_rows( User.joins(:emails).where(...).select("users.id").to_sql )

このように、connection.select_rows を使うと id の配列が得られます。(正確には配列の配列だが)。前回は find_by_sql を使いましたが、モデルが不要な場合、select_rows などの下位APIを使って配列など生データのみ取得したほうがはるかに高速です。

そしてその後idだけをつかって

User.where(:id => ids).includes(:emails)

とするときれいなモデルが得られます。

前の記事で、ビジネスロジック上意味のある絞り込み条件(activatedなど)を定義することで再利用性が高まるということを書きましたが、これはここでも適用されます。そのような scope は最初の id を引っ張るところにうまく使い回しできて、実際に必要なデータ(カラムや関連)を取ってくるところは
二つ目のSQLで調整する、という役割分担になります。


2012年9月21日金曜日

resque と delayed_job

resque と delayed_job の違いについての質問に対する回答がなかなかよかった。

http://qa.atmarkit.co.jp/q/2406

個人的には、resque って長いトランザクションの中からキューを突っ込んだ場合、トランザクションが完了するまえに resque のワーカーが動き出してしまい、そのタイミングではまだコミットされてないからトランザクションの中で加えられた変更が見えてなくておかしな挙動になる、という問題が結構深刻で使いづらいんだけど、何か対策あるのかな?

delayed_job はキューが mysql なのでその心配はない。ということで delayed_job を使っている。


しっかし↑このサイト stack overflow のパクリ丸出しだなー。


2012年9月19日水曜日

DCI (data context interaction)


DCI アーキテクチャという設計についての考え方がある。数年前から scala 界隈で盛り上がっていた記憶があるが、最近は ruby/rails 界隈でも盛り上がっている模様。

先日の札幌 ruby 会議で角谷氏が発表を行っている。
rubykaigi  http://sapporo.rubykaigi.org/2012/ja/schedule/details/79.html
スライド    http://kakutani.com/20120916.html#p01

そこからたどって以下のような資料があるのも発見した。
objects on rails (書籍の無料公開)
http://objectsonrails.com/

Clean Ruby
http://clean-ruby.com/
書籍サイト。ベータ版書籍が購入可能。$42なので電子書籍にしてはかなり高い。

DCIの講演 togetter
http://togetter.com/li/3335
色々disられていて面白い


私の意見も混ぜてざっと紹介してみます。

まず大元の問題意識としては、rails のアーキテクチャ(MVC)のレイヤ分割だけでうまくいくのはとても小規模なアプリのみということ。rails のレールに沿って素直にコードを書いていくといつのまにか super fat model. super fat helper が出来上がる。一つのクラスに大量のメソッドがある状態。これはC言語のように function が flat になっている状態と差がなくなるということだから非常に良くない。

従って、コードを責務などによって適切にレイヤー分割、クラス分割しないといけない。そしてそのレイヤー分割の判断根拠なるのはまずは「ビジネスロジック」や「ユーザエクスペリエンス」になる。
これらの「ドメイン」の境界に沿うようにプログラムの境界が設定されるのが自然なプログラム。
ビジネスロジックを理解せずにクラスやレイヤーの分割をするというのは大局では良い結果にならない。

   そういう意味で、例えばデザインパターンのような技術者だけの道具が設計の初期に出てくるのはおかしい。技術者はえてしてそういう技術話が好きなものだが、ラムダとかデザインパターンとかmix-inとか、ビジネスロジックの出てこない道具は「syntax上」の道具でしかなく、これはつまり局所最適化にしかなりえない。それを全体レベルの最適化に使ってもうまくいくはずがない。こういう道具は局所戦でしか使ってはいけない。

 まずはビジネスロジックやユーザエクスペリエンスに基づいて大枠の設計がなされ、局所的な最適化に各種ツールが活用される。その優先度が逆転してはいけない。(xxパターンに合致させるためにビジネスロジック上歪なクラス構成になってしまうなど。)ビジネスロジックに沿った境界の中を局所的なツールを使ってさらに分割するということは許されるが、プログラマの玩具によってビジネスロジックの境界が捻じ曲げられることがあってはいけない。

そしてビジネスロジックやユーザエクスぺリエンスに基づいた設計を行うために何を学ぶべきか?
というのがこのDCIや以前紹介した(かな?)DDD(Domein Driven Design)だ、と考えている。

大事なことはコードが「適切に」分割されることであって、分割されたコードをいかにruby 的に楽にmix-inするか?みたいな技術話(こういうのが局所最適化)は、コードを気持ち良く書くという短期的な幸せにとって大事ではあるが、長期的な幸せ(メンテナンス)にとっては影響度が相対的に低い。

色々大口たたいたけど自分も勉強中。。。

(追記)
そもそものDCIについて何もかいてなかったな。。。まぁ最近は紹介がたくさん転がっているのでいまさらですが、簡単に説明してみます。

DCI は上に書いた super fat model を解決するための方法、として紹介されていることが多いと思います。rails で thin controller を突き詰めると model がfatになる。大きなアプリになればなるほどビジネスロジック上の中核のモデルに大量のメソッドが生えてしまう。一番変更が激しいモデルが一番fatになっていく。そういう問題が起きます。

モデルが fat になると何がまずいか?コードの構造が読み取りづらくなり、他のメンバーが初めてコードを読んだ時に辛い思いをする。似たようなメソッドがたくさんできる。似たようなメソッドがたくさんできた時に、最初に作ったメソッドには active のようなシンプルで一般性の高い名前をつけてしまって困る。似たようなメソッドがたくさんあってよく分からなくなって、同じ名前のメソッドが定義されたり、同じ意味のメソッドが定義されたりする。などなど。

で、DCIではこれをどう解決しようとするのか。基本的な方針としては、モデルが使われるコンテキストごとに必要なメソッドを分類してモジュールに切り出して、必要なときに必要なモジュールを include しよう、ということになります。例えば入力ページを書くときだけに必要なメソッド、というのはよくあります。保存処理をするときだけ必要なメソッドなどもよくありますね。そういうものを models/user/input_role.rb や models/user/save_role.rb などのように別ファイルのモジュールに切り出して必要なときに include しましょう。とういことです。

えー、いちいち include するの面倒だよ、という人のために、DCI ではコンテキストという「場」を設けて、コンテキスト内に model が入ってきた時にそのコンテキスト用の role が自動で include されるような仕組みを作ろう、と言っています。ここについては上で少し局所最適化だ、とか吠えたように、私はそれほど重要ではないと思います。逆にこの仕組みがDCIだ、仕組みをどうやって作ろうか、みたいなところに話のフォーカスが写ってしまうとDCIの本質を見失った議論になると思います。

さて、ではmodelとroleを分けるとして、何を role に追い出すべきなのか?何が model に残っても良い、modelにとって本質的なものなのか?本来はここが議論されるべきポイントのはずです。この方針が決まる前に module が自動で include される仕組みが作られるというのは順序が逆だと思います。まぁ、ここはこれからじっくり時間をかけて合意が取られていくので。今の時点で明らかなものとしては

role に追い出すべきもの

  • 特定の場面で一回のみ使うようなメソッド。例えば退会処理を実行するメソッドなど。
  • view でのpresentationに関わるメソッド。
  • 一時的に association の動作を変えたい場合などに上書きする
model に残すべきもの
  • よく使うビジネスロジックを表現した scope。例えば、現在有効な会員データはregistered_on is not null and left_on is null だ、など。
  • model の状態を判定するようなメソッド。上のscopeをメソッドで表現したようなもの。active? とか left? など。
  • association
など。validation をどちらに置くべきかが議論の余地があると思う。save するときには必ず validation が走って正しいデータが保たれなければいけない、という場合は model に置くのが良いと思うし、業務アプリなどで、どうしても外部からの取り込みデータなどvalidationに通らないようなデータを格納しておかないといけない場合、常に validation が走ると batch 処理などがうまく走らなくなる。save :validate => false を使えば良いのだが、そういう validation を必要としない save がたくさんある場合は難しい。


最初の方で吠えていた局所最適化とかの話は↓でも似たようなことを書いてます。

コードの重複を排除するのもほどほどに!



2012年9月16日日曜日

ActiveRecord の changed? のバグ


ActiveRecord にはちょっとひどいバグ?がある。

ActiveRecord は save するときに実際に変更のあったカラムのみ
sql の update 文に書き出すようになっている。blob を持つようなモデルやカラム数がやたらと多いモデルの場合には sql が短くて済むので効果がある。

例えば

 u = User.find(1)
 u.name = "山田"
  u.name_changed? #=> true
 u.save
とすると、高速化のために name というカラムだけを更新するような sql が出力される。


同様に、
 u = User.find(1)
 u.save
としても何もカラムが変更されていないため、この場合そもそも sql が発行されない


これは良い仕組みだが、カラムが変更されたかどうかの判定に問題がある。
具体的には、代入が起き、かつ異なる値がセットされたときに変更があったとみなされるため、
gsub! を使った場合変更されたとみなされない。
 u = User.find(1)
 u.name.gsub!("山", "川")
 u.name_changed? #=> false
 u.save #=> なにもおきない

通常フォームからわたってくるデータを突っ込んで保存、という流れを想定しているので
あまり問題にはならないのだと思うが、バッチなどでこのようなコードを書いてしまう可能性はある。