やはり俺のMVCは間違えている in Backbone.js

昨日のエントリの続き。こっちのほうが有益な情報になってると思うんだけど多分昨日ほどはのびない。

さて、昨日のエントリーでは「Backbone.jsのViewはControllerってことなのか〜それは俺が間違えてたわ〜、えっじゃあ Marionette.js 使う場合はどうなの」という感じになったのだけれど、そのあといろいろ考えて以下のような感じに落ち着いた。

Marionette.jsを使っていたとしても結局考え方はBackbone.jsのときとかわらない。

つまり、Marionette.js の View も C である。ViewControllerと言うべきかもしれないので以下ではViewControllerと書く。

ViewControllerの責務は、以下の通りである。

  • Model(あるいはCollection)をひとつ保持し、View(HTML片のことである)をひとつ保持する
  • Modelの変更イベントに応じてV(HTML片)をレンダリングする
  • HTML上のイベント(クリックされたとかそういうやつ)を監視して、それに応じてモデルを操作する。

さて、ここまではよい。しかし、ここで Model の種類がたくさんある場合にどうすればよいかという問題が出てくる。昨日の例で言うと、「アーティスト一覧の中からアーティストをクリックするとレコード一覧を表示する」みたいなロジックをどうやって組んでいくか、という問題である。しかし、実はこれはきちんとモデルが設計されていれば問題にならない。

実際に書いてみた

で、口だけマンには死んでもなりたくないというプライドがあるので、実際に動作するサンプルコードを書いた。小規模なのに無駄に Marionette.js on Rails である。

https://github.com/Shinpeim/marionettejs_sample

Rails が担当してるのはAPIの部分。rake routes の結果は以下の通りで、

        Prefix Verb URI Pattern                           Controller#Action
artist_records GET /artists/:artist_id/records(.:format) records#index
       artists GET /artists(.:format)                    artists#index
          root GET /                                     welcome#index

で、

  • GET /artists でArtistの一覧をJSONで返す
  • GET /artists/:artist_id/records でそのアーティストのレコード一覧をJSONで返す
  • GET / はHTMLページをレンダリングする

となっている。

というのを前提として、実際に書いたクライアントサイドのコードは以下の通りである。

まずは、アーティストを表すArtistモデルと、その集合を表すArtistsコレクションが存在する。また、レコードを表すRecordモデルと、その集合を表すRecordsコレクションが存在する。そして、Artistモデルは自分のレコード一覧を表すRecordsコレクションをfetchするfetchRecordsというメソッドを持っている。

さて、話はViewControllerのほうに移る。Artsits コレクションを保持し、その情報をHTML上にレンダリングする責務を持っている ViewController が、ArtistsViewである。また、ArtsitsViewはHTML上のアーティストのどれかがクリックされたときに、そのイベントを拾ってArtistsコレクションの操作メソッドを呼ぶ責務も持っている。今回ならばまずは「実際にArtistsコレクションの中からArtistを選択する」という操作に責務を持っている。

実際にその操作をしているのはArtistsView#artistSelectedメソッドで、その中で@collection.selectOne(artistModel)しているのが見て取れるだろう。

さて、アーティストを選択したのはよいが、今度はそのアーティストのレコード一覧を表示するという仕事をしなければいけない。しかし、ArtistsViewRecordsモデルを保持していない。じゃあどうするのか。ここでArtist#fetchRecordsの出番である。Model同士の関係をきちんとModelの世界で定義しておいてあげるのが大事で、これをCでやろうとかしたりするといわゆる「FatController問題」にハマりこんでゆく。

さて、首尾よくArtistからRecordsが取得できたので、あとはこの引っ張ってきたコレクションを表示するための ViewController を ArtistsView が保持しておけばよい。つまり、ViewControllerは下位の ViewController を持ちうるのである。

このように、ある ViewController が受け取ったイベントが二つ以上の異なった種類のモデルに関係する場合があるが、それらの異なったモデルは、なんらかの関係を持っているはずである。でなければおかしい。そういう、「ModelAをこうするとModelBがああなる」みたいな部分、これこそが「ビジネスロジック」であって、それを操作するのが Model の責務である。なので、ViewController はあくまでそのViewController が保持している Model の操作メソッドを呼び、「これがこうなったときはあれがああなった」という結果だけを受け取るべきである。その結果を別の場所にレンダリングしたいな〜とかそういうときには、「どのViewControllerがどのViewControllerのライフサイクルを管理するのか」をきちんと意識しながら、適宜 VC が別のVCを作ったりしてあげればよい。これはおそらくほとんどの場合、どのModelがどのModelに依存しているのか、という関係と一致すると思うので、結局Model をしっかり設計するのが大事だね、というあたりまえの結論になるのだった。

以上です!!!!!