HTML5時代の「運営しやすいアーキテクチャ」の話
O/Rマッピング技術の進化が皮肉にも助長している「えせMVC症候群」

Ruby on Railsの「えせMVC」の弊害

 先日のエントリーでも少し触れたが、Ruby on Railsの最大の問題点は、それが持つ「一見そのフレームワークがMVCの形をとりながら、MVCの最も大切なところを外している『えせMVC』である」点にある。MVC(Model View Controller)がなぜ必要かを根底の部分でちゃんとと意識せずにRailsアプリケーションを作ると、後々ひどい目に会うので注意が必要である。

 その意味では「RailsでMVCを学ぶ」などもっての他だし、「JavaにもRailsと同じようなフレームワークを作って業務用アプリの開発を効率化しよう」などという発想もとても危険である。

 ということで、今日はまずはMVCの解説から。

 MVCの発想の根底には、「モジュール化と情報の隠蔽により、プログラムがスパゲッティ化するの(コード間の相互依存関係が複雑に入り込んでしまってにっちもさっちも行かない状態になること)を避けよう」というオブジェクト指向の発想がある。MVCは、そのオブジェクト指向に基づいて、GUIアプリケーションを以下の三つの大きなモジュールに分けて設計しましょう、というアーキテクチャの指針である。

  • Model:データベースなどに格納された生のデータを隠蔽し、抽象化された形のAPIを通じて他のモジュールからのアクセスをコントロールするモジュール。アプリケーション特有のルールやロジック(ビジネスロジック)を持ち、データの整合性(後述)に絶対の責任を持つ。
  • View:Modelが提供する抽象化されたデータを、どんな形で人間に見せるかを記述したモジュール。GUIアプリケーションの場合、使うGUI部品や画面上のレイアウトを指定するのがこのモジュール。
  • Controller:ViewとModelの間に位置して、ユーザーにどんな順序でデータを見せて行くとか、Viewを通したユーザーからの入力をModelへのAPIコールへとマッピングするのがこのモジュールの役目。

 この中で、もっとも丁寧に設計し徹底的にテストをしなければならないのがアプリケーションの土台となるModelである。ControllerやViewに多少のバグがあってもユーザーに一時的な迷惑をかけるだけの話だが、Modelにバグがあるとデータの整合性が壊れてしまう。

 ここで、先ほどから何度か触れている「データの整合性」とは何かを、業務用の「会計ソフト」を例に上げて説明してみよう。例えば、八百屋さんで「60円で仕入れたリンゴ1つを100円で売った」こと(Sales Transaction)を記録する場合を想定しよう。

 会計ソフトが一般的な会計の常識に従って作られていれば、この1つのSales Transactionにより、少なくとも4つの変更がデータベースに加えられる。

  1. 「手持ちの現金の増減」を記録するテーブルに「現金100円の増加」を記録
  2. 「売り上げ」を記録するテーブルに「100円の売り上げ」を記録
  3. 「在庫の増減」を記録するテーブルに「リンゴ1つ減少」を記録
  4. 「経費の計上」を記録するテーブルに「仕入れ値60円の経費計上」を記録

 八百屋さんのビジネスにとって大切なことは、この4つの変更が同時に行われること。このうち一つの変更だけが欠けていたりすると、テーブル間のつじつまが合わなくなってしまい、年度末の決算がちゃんとできなくなってしまう。

 この「データの変更が中途半端でつじつまが会わなくなってしまっている状況」のことがすなわち「データの整合性が壊れた」状況である。

 Modelの外部インターフェイスの設計においてもっとも大切なことは、この「データの整合性」の責任を100%Model側で引き受け、「Controllerが何をしてもデータの整合性だけは絶対に壊れない」ように作っておくことである。そのためには、上の例の「手持ちの現金の増減を記録したテーブル」へのControllerによる直接のアクセスは絶対禁物である。Controllerが出来る事は「何をいくらで売ったか」をModelに報告するだけで、その情報に基づいてデータベースに適切な変更を加えるのはModelの役割である。

 この「Controllerが何をしてもデータの整合性だけは絶対に壊れない」という部分はとても重要で、Controllerに少々のバグがあろうが、会計の知識が全くない新人がControllerを作ろうが、Controllerに悪意のあるコードが紛れ込もうが、整合性だけは絶対に壊れないようにModelを作っておくことが必須である。

 さて、そこで問題となるのは、Ruby on RailsというフレームワークでModelの役を果たしていると言われている(もしくは誤解されている)ActiveRecordである。

 RailsのActiveRecordは、データベースのテーブル(厳密にはクエリーの結果)の1行1行をオブジェクトの形にパッケージ化し、メソッドとプロパティの形で簡単にアクセスできるようにするもの(「O/Rマッピング」と呼ばれる)である。

 ActiveRecordそのものはとても便利なもので全く問題はないのだが、問題はRailsの解説書などでActiveRecordを使って抽象化されたデータベースをModelと読んでいるケースが多く見受けられる点だ。

 上に述べた通り、ActiveRecordは、データベースのテーブルを単にオブジェクトの形に抽象化しただけのものであり、そこにはビジネスロジックは一切含まれていない。その意味では、MVCにおけるModelとはほど遠いものであり、これをModelと呼ぶ事は(特に初心者に)大きな誤解を生むのでとても危険である。

 私は「どんなプログラムを常にMVCに厳密に基づいて作るべきだ」と主張している分けでも、「Ruby on Railsは使い物にならない」と批判しているわけでもないので誤解しないでいただきたい。ここで私が強調したいのは「Ruby on Railsフレームワークは本来のMVCとはほど遠い」「Ruby on Railsの上にMVCのことを意識せずに普通にアプリケーションを作ると、ビジネスロジックをControllerに混ぜ込んで書く事になってしまい、『データの整合性』を保つことが難しくなる」という点である。

 (誤解した人が多かったので追記すると)結論としては、「Railsを使って(データの整合性が大切な)アプリケーションを作る場合、素のままのActiveRecordにControllerから直接アクセスするのは避け、ActiveRecordの上に一枚皮をかぶせる形でビジネスロジックを含んだModelをきちんと設計・実装し、ビジネスロジックがControllerに浸食していくことを意識して避けることが大切である。

 なお、読者からのコメントで学んだのだが、Javaにおいても同じような「えせMVC症候群」は蔓延しているようで、MVCの本質を理解していないエンジニアが、うすっぺらなDAO(Data Access Object)の上にビジネスロジックを含んだ分厚いControllerを書きながらそれをMVCと思い込んでいる、という話は良くあるらしい。

 それから「MVCは時代とともに変わる」というコメントもいただいたが、この手の基本原理の定義を変えてしまうことはとても混乱を招くのでやめた方が良いと思う。MVCの発想は未来永劫いろいろな場面で使える話なので、別のアーキテクチャにはちゃんと別の名前を与えて混乱を避けるべきである。

【参考文献】

Comments

通りすがり

そうすると、hibernateなどの1次キャッシュをもったO/Rマッパーでモデルを構築したほうがよい、ってことになるんでしょうか。

Satoshi

>そうすると、hibernateなどの1次キャッシュをもったO/Rマッパーでモデルを構築したほうがよい、ってことになるんでしょうか。

いえ、そういう話ではなくて。O/Rマッパーのもう一つ上にモジュールを作ってそこにビジネスロジックを記述してデータの整合性の責任を持たせる必要があります。Controllerがアクセスしていいのはそのモジュールの外部APIだけで、O/Rマッパーに直接アクセスしてはいけません。

通りすがり

java屋の発想では

>Controllerがアクセスしていいのはそのモジュールの外部API

これがサービスになり

>O/Rマッパーに直接アクセスしてはいけません。

これがDaoになり

コントローラーはサービスに依存し、サービスはDaoに依存するというレイヤリングになるのですが、こういうイメージなんでしょうか。ビジネスロジックの複雑度によってサービスがさらに階層化する場合もありますが。

Satoshi

Javaの人たちが言うところの「サービス」とか「Dao」については知りませんが、MVCは言語とかフレームワークとは独立したアーキテクチャレベルの話。JavaであれRubyであれ、MVCを意識して設計することはとても良いことだと思います。その中でも一番大切なことは、本文でも書いている「Modelのレイヤーでデータを抽象化して、データベースの構造をコントローラーから見えなくし、データの整合性の責任を100%負う」ことだと思っています。それさえ守れば、その上や下にさらなるレイヤーがあってもかまわないと思います。

yas

もはや MVC が本当に正しいのかどうか見直す時期に来ているのでは?

本当にオブジェクト指向や MVC は生産性をもたらしたのだろうか? Java なんか単に複雑になっただけなんじゃないだろうか?そういう疑問はあっていいと思います。

今は最先端を行ってるのは PAC(Presentation Abstract Controller)だと確信します。

PAC は MVC の階層的アーキテクチャとみることができます。
これを採用しているソフトには Drupal、Firefox、Eclipse などがあります。誰も指摘しないけど。

Drupal は PAC であり、米アマゾンのウェブ開発カテゴリでの本のランキングに Drupal が入ってきていることからもその生産性の高さが伺い知れると思います。

通りすがり

railsでは app/models以下のファイルに、ActiveRecord::Baseを継承したクラスを「モデル」として作ります。ここにビジネスロジックを思いっきり書きますよ。
ActiveRecord自身はO/Rマッパーのライブラリですので、「ActiveRecordは、データベースのテーブルを単にオブジェクトの形に抽象化しただけのものであり、そこにはビジネスロジックは一切含まれていない」というのはそりゃ正しいですが、ActiveRecordを使ってビジネスロジックを実装することが可能です。何を根拠に『えせMVC』とまで言ってしまうのか全く理解に苦しみます。
なにか具体的なアプリケーションを例に説明してみてはいかがでしょうか?公開されているRailsアプリはいくらでもあります。例えばRails製のIssue Tracking System、Redmineなどはいかがでしょう?
https://redmine.rubyforge.org/svn/trunk/


また「Controllerが何をしてもデータの整合性だけは絶対に壊れない」というのは、フレームワークを使う側で実現するべきことであり、コードの書き方次第ではRails以外でもいくらでも壊れることはあり得ますよね?なんだかフレームワークが全ての責任を持たなければいけないように読めました。これは書き方に問題があるのでは?

いたさん

ちょっと気になったのでコメントさせていただきます。

MVCによる設計に関する考え方は全く同感なのですが、Ruby on Railsが「えせMVC」というのは、ちょっと認識が違うのではないでしょうか。

たしかに、Ruby on Railsを使えば誰でも正しいMVCでプログラムができるというのは間違いです。正しいMVCのためには意識的にプログラミングをする必要がありますが、このへんについては、2006年の10月にJamis Buck氏がブログで書いた"Skinny Controller, Fat Model"がプラクティスとして定着していると思っています。

https://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model

もっとも、Jamis Buck氏の例は単純なものですし、Railsが備えているトランザクション管理機能も最低限のものではあるので、フレームワーク側の機能が足りないという意見であればもっともですが。

hiro

ここの論点としては、Controllerにはビジネスロジックを含ませるべきではない、というMVCの基本的なところで、ところが、この点は残念ながら、日本のSIerを見ていると、Controllerにこそビジネスロジックをもたせて、Modelは(Javaでいうところの)DAOになっている、という点でしょう。

では、なぜ、Controllerにビジネスロジックを含ませると駄目なのでしょうか?
そこを是非論じてほしいです。(わかっているひとはわかっていると思いますが)

> Modelの外部インターフェイスの設計においてもっとも大切なことは、この「データの整合性」の責任を100%Model側で引き受け、

ここは、もっとも大切なことのひとつでしかないと思います。これはModelが最低限、遵守すべきことです。

> java屋の発想では

昨今、java屋は勉強しない人の象徴みたいになってきていますね。


junichi

現在、Rails、HTML5、JavaScriptを猛勉強中の若輩者ですが、ここでの話は大変勉強になります。(本来のMVCの意義をちゃんと理解していない一人でした)
有識者の皆様にお聞きしたいのですが、ここで議論されたMVCの意義を実現できている(またはできていない)Railsアプリの具体例などはありますでしょうか。
質問する場所が違いましたら申し訳ありません。

Satoshi

>ActiveRecordを使ってビジネスロジックを実装することが可能です。
>何を根拠に『えせMVC』とまで言ってしまうのか全く理解に苦しみます。

もちろん可能ですしそうすべきなんですが、そうしなくても「形だけはMVCの形をしているアプリケーション(えせMVC)」ができてしまう点が問題なんです。参考文献の3番目の"Active Record vs. Objects"にも書いてありますが、「データベースがActiveRecordという形で抽象化されているから、Model作りはこれで十分」という幻想を抱かせてしまうんですね。

shunsuk

トラックバック失敗したので、コメントで。

Ruby on Railsは「えせMVC」じゃないよー - 医者を志す妻を応援する夫の日記
https://d.hatena.ne.jp/shunsuk/20091012/1255351852

と思うのですが。。

ryu

いや。皆さんもsatoshiさんも言っておられる事は正しいと思います。
作成する規模や求められる物(メンテのしやすさ堅牢性 vs 納期・予算)によって何が正しいかは変わってくると思います。satoshiさんが書かれていることは「こうあるべき論」に対して「そうじゃない実装が出来る」事や「そう言う間違った実装が紹介されていることがある」というオピニオンですよね。
目標を「早期にサービスを立ち上げる」から「メンテしやすいサービスにする」と趣旨替えしたとしてもコードは変わってくれません。コントローラーのビジネスロジックをリファクタリングして行くしか無いでしょう。とはいえ動いているサービスに手を加えるのはリスクを伴います。
Webで有ればHTTPのリクエストレスポンスの世界なので再帰テストも自動化しやすいですがAjaxだと自動化って進んでいますか?
AWSならやりやすいと思うので本番想定のテスト環境を整備、テストの自動化の上、障害発生ケースをこまめに追加しつつ運用していくのが良いのではないでしょうか?
masuiさんも頑張って下さい。

山田某

RailsのmodelやJavaのWebフレームワークで語られるMVCと本来の(Smalltalk的な)MVCとは別のものなので、
まずはここにコメントしている方は自分がどちらのMVCについて語っているかを明確にしたほうが良いでしょうね。

Rails 上がりの開発者は実際 model = DB の感覚でいる人が多く
「まずは業務をモデリングしてみてくれる?」
と依頼したときにいわゆるDB設計を行ってしまって話が通じなかった事があります。

私は、MVC自体は「生産性を劇的に向上させるもの」ではなく
「GUIを一般的な人間の知性で管理できるレベルで破綻無く記述するための一つの設計テクニック」だと考えています。
# それが結果として生産性向上に寄与した時代もあったと言うだけの話で

>>hiroさん

>では、なぜ、Controllerにビジネスロジックを含ませると駄目なのでしょうか?
>そこを是非論じてほしいです。(わかっているひとはわかっていると思いますが)

何一つ駄目ではないです。
Railsはビジネスジックと画面遷移の厳密な分離を行わない事で、煩雑さを省き生産性向上を実現しています。
# もちろんアプリケーション規模や、1つのビジネスロジックに対して複数の
# インタフェイスが必要になる状況ではこの方法はベストではありませんが。

>>junichiさん
おそらく Smalltalk 的な MVC を実現しているRailsアプリは無い or 殆ど無いのではないかと思います。
まず、Rails の model が Smalltalk 的な MVC とは異なるものです。

またRailsには model 間のビジネスロジック的な協調動作を扱うクラス(Java(Seasar系FWの場合)で言うService
Smalltalk的にはこれも model)を記述するような適切な役割が定義されていなかったはずです。

RailsはそもそもSmalltalk 的な MVC を厳密に尊重して生産性を向上させるフレームワークではなく、
もっとWebアプリの特性に最適化されたフレームワークだと思っています。

hiro

>> 山田某さん

まず、MVCは言葉が産み出されたから30年経っているから、すっかり多義になってしまっています。

これが問題なのかどうか、という点ですが、昨今の風潮から行くと、おそらく多くの人にとってはあまり問題はないのでしょう。

でも、私が問題にしたい点は本来の意味を失うことで、元のMVCの目的も失いかけているという点です。

「Controllerにビジネスロジックを含ませる」のが問題ないというのは、全ての場合に当てはめてそうですか?「Railsはビジネスジックと画面遷移の厳密な分離を行わない事」ことが全ての場合に問題はないですか?

RoR自体はすばらしいフレークワークであり、擁護したい気持ちはわかりますが、ここは議論が十分必要な命題ではないでしょうか。

山田某

>>hiroさん
あ、すいません。説明が不足してましたね。正しくは
「Railsの枠組み上でRailsの言うControllerにビジネスロジックを含ませるのはRails的には問題ない」が、正しい意図です。
ただ、そのアプリを MVC と呼ぶのは良くないと思いますね。少なくとも「筋のいいMVCアプリ」とは呼べないはずです。

HTML5も本格的に浸透しようとしてきている時期ですので従来のWebアプリの制限で歪められたMVCの本質を
もう一度見直し、筋のよいMVCの設計を知ってもらう意味で、本エントリは有意義だったのではないかと思います。

# しかし、はてブや twitter などの発言を見ていると MVC model2 と呼ばれていたものが
# Web-MVC とか呼ばれて受け容れられてたりもするんですねぇ。私は今でも抵抗ありますが。

ちなみにRailsはとてもいいフレームワークだと思っていますが、本来の思想を汲み取って
使いこなせている人が多いわけでも無いので、とりたてて擁護する気はありません ;-)

junichi

みなさんのお話、大変参考になります。

>> 山田某さん

>RailsはそもそもSmalltalk 的な MVC を厳密に尊重して生産性を向上さ
>せるフレームワークではなく、もっとWebアプリの特性に最適化された
>フレームワークだと思っています。

ここの考えをもう少し詳しく教えていただけたらうれしく思います。

hiro

>> 山田某さん

ようやく分かり合えた気がします。

でももう少し議論を深めたいです。

モデルにビジネスロジックを詰め込めば高い凝集性は確保されます。それで十分でしょうか。
このエントリーでデータの整合性にふれられています。ActiveRecordでは何もしないとCRUD操作出来るクラスになってしまいます。つまりデータは自由に操作でき、整合性もあったもんじゃない、きちんとカプセル化するようにしてほしい、そういったアーキテクチャの議論がほしいです。

Yugui (Yuki Sonoda)

追記を拝見するには、この記事は「active recordパターンの限界」と題するべきだったのではないでしょうか。そもそも"Pattern of Enterprise Application Architecture"あるようにこのパターンは「構築が容易であり、また理解もしやすい」代償としてそのままでは複雑なロジックを扱いづらく、「トランザクションスクリプトに保持され」への一部分離を必要とすることがある、そういうものです。

ソフトウェアの成長の中で分離のタイミングを見極め損なうとactive recordで扱いづらいロジックがコントローラーに流出する虞があるというのはその通りです。active recordの実装たるActiveRecordもその特徴と制約を受け継いでいます。

「この手の基本原理の定義を変えてしまうことはとても混乱を招くのでやめた方が良い」まったくその通りです。「データベースのテーブルを単にオブジェクトの形に抽象化しただけのものであり、そこにはビジネスロジックは一切含まれていない」というのはactive recordではありません。まともなRails入門書でもそのように開発を進めて見せているものはないはずです。「ActiveRecordの上に一枚皮をかぶせる形でビジネスロジックを含んだModelをきちんと設計・実装」もまたこのパターンにおける最初の選択肢ではありません。トランザクションスクリプトに分離する前にactive recordオブジェクトそれ自体にロジックを組み込むことを検討すべきであり、それこそがactive recordの特長です。

また、こういった問題に直面したエンジニアがおおもとをきちんと理解していないときにロジックをcontrollerに流出させてしまう傾向は確かにあります。しかしこの問題に対して、active recordパターンはそれほど悪い選択肢でしょうか。既に追記で触れられているように、DAOを採用してサービスレイヤの挿入を意図したところで、分かっていない開発者はトランザクションスクリプトをcontrollerに混ぜ込むするだけです。少なくともactive recordには、サービスレイヤの分離を必要としない規模で実装をシンプルに保てるという利点と、それ自体の歴史的経緯を辿ってモデル層の責務の重要性についての議論に到達するポインタの役割を果たすという利点はありそうです。

そうしてみるとこれはactive recordパターンの問題ですらなく、特有の注意点でもなく、実はこの記事は「ドメインモデル貧血症」と名付けるべきかもしれませんね。
* https://capsctrl.que.jp/kdmsnr/wiki/bliki/?AnemicDomainModel

active recordパターンの実装がドメインモデル貧血症を引き起こしているとしたら皮肉な話ですね。

sumim

横から失礼します。Smalltalk の MVC についてあまりなじみのない方におかれましては、Seaside という WAフレームワークでの応用もありますので、この機会に参考にしてみてください。

https://www.ogis-ri.co.jp/otc/hiroba/technical/seaside/seaside2/index.html

hnakamur

興味深い議論をありがとうございます。ちょうどDeliciousのhostlistに関連するスライドが入っていたので紹介します。238〜248ページに Model is-a ActiveRecord(s) はアンチパターンで Model has-a ActiveRecord(s) のほうが良いと説明されています。
https://www.slideshare.net/billkarwin/sql-antipatterns-strike-back?src=embed
(私自身は Model is-a ActiveRecord(s) しか経験が無いです)

Fomalhaut Weisszwerg

MVC はアプリケーション設計モデルの一つにすぎないので、言語には依存しないものだと思います。したがって「Java 屋としては」とか「Smalltalk の MVC については」というのは、的外れな感じを受けますね。

データの整合性全てについては M(モデル)が責任を持ち、C(コントローラ)は報告をするにとどめるという "Skinny Controller, Fat Model" は MVC の原則の一つですが、現実は「よく理解していない」プログラマが "Skinny Model, Fat Controller" なコードを書いてしまっていることが問題だ、ということが提起されているのではないでしょうか?

だとしたら、この問題は RoR に限らずどのフレームワークでも起こりうることです。
わたしは V と C しかないコードを見たこともあります ( DB 操作は C が直接行っていたため、M が存在しないも同然だった )

根本的な原因は MVC とは何ぞや、なぜ分離しなければならないのかを理解しているプログラマが減っていることではないでしょうか?

浜村拓夫

大変参考になりました。It's GREAT!!!(・∀・)

modelはデータの整合性を保証する。

controllerにビジネスロジックが入り込まないようにする。

ぶっちゃけ、
・controllerに書くコードは極限まで削り、極力modelにブチ込む。
・controllerに残ったコードは「条件分岐」くらいしかない。=「逐次処理」がない。
という指針でやればOKでしょうか?

>Skinny Controller, Fat Model
MVCのリファクタリングとは、controllerのダイエットだったんですね!

ハマ

うーむ、MVC の C にビジネスロジックを入れるのは関心しないのですが。。。

MVC の M ってドメインモデルなんでしたっけ?
データモデルなのでは?

V は UI、UI にデータモデルをどう見せるか、UI と M の間を取り持つものが C の役割ですよね?

これって別に業務を意識しているわけではないので、そのまま当てはめるのはいけないと思います。

で、どうすればいいのかというと、単一責任の原則に則って、変更理由をひとつになるようにクラス設計をするのが正解だと思っています。

(MVC + 単一責任の原則 = 3階層レイヤーパターン)

こうすると、おのずとデータモデルにビジネスロジックを入れることはダメ、コントローラにビジネスロジックを入れることもダメ、じゃあもう一個クラスをつくろうよってなりますよね?

なんか、ここの議論はちょっと違う気がしました。

hiro

今回の議論を通して感じたこと。

MVCは十分に浸透した基本的な用語と思いきや、意外にも目的や意味を理解できてないままにいる人がまだまだ多いこと。

有識者らしき方がControllerはテストやリファクタリングすればいいみたいなとんちんかんな論述をしている辺りに日本が抱えている閉塞感すら感じました。(ましてやServiceの辺りは・・・)

英語圏ではこういう議論は盛んに行われているので日本でも最も大いに議論してほしいです。(せめて用語くらいは正しく共通理解されるまで)

Verify your Comment

Previewing your Comment

This is only a preview. Your comment has not yet been posted.

Working...
Your comment could not be posted. Error type:
Your comment has been posted. Post another comment

The letters and numbers you entered did not match the image. Please try again.

As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

Having trouble reading this image? View an alternate.

Working...

Post a comment

Your Information

(Name is required. Email address will not be displayed with the comment.)