ZF勉強会#2フォローアップ Zend Frameworkでモデルを始める前に理解しておきたいこと
Zend Framework勉強会#2 はGMOペパボ株式会社様の協力もあって、盛況でしたが、どうもZend_Dbに関して誤解があるような気がしているので(私も含めて)一通り確認してみようというフォローアップ記事です。
Zend Frameworkで対応しているモデル構成は、ドメインモデル+サービスレイヤーで直接的にはデータマッパーです。
CakePHPでは標準ではActiveRecordを採用していると思いますが、ここがCakePHPやsymfonyで学習してきた人が一番最初に戸惑う部分ではないかと思います。また、初学者がデータマッパーの意義をいきなり理解するのは難しいような気もします。
要は、多くの初心者が“モデルって、DBテーブルのことだよね”と考えてしまうのはよくない、と。結果的にコントローラがふくれあがり、UnitTestで影響が出てしまう、という話になっています。
- -
CakePHPのおいしい食べ方より
http://cakephp.seesaa.net/article/99302070.html
今では、初心者に限らなくなってきているように思えます。
元記事はKeeping it Simple: ActiveRecord does not suckこちらですが、Zend Frameworkのメイン開発者の方のコメントとして、Model !== Databaseだという主張が明確に述べられています。
公式ドキュメントを確認してみる
Zend Frameworkの公式ドキュメントのクィックスタートによれば(http://d.hatena.ne.jp/netjockey/20090702/p1 (日本語訳))
- Zend_Db_Table
- Zend_Db_Table_Row
- zfコマンドで作成されるマッパー
が今のところZend FrameworkとZend_Dbで作成するモデルの標準構成と理解していいと思います。zfツールもサポートしています。
誤解を恐れずに、端的な違いを書く
- DoctrineやCakePHPで採用されているActiveRecordとZend_Db_Table_Row(ローデータゲートウエイ)の違い
ActiveRecord | データアクセスオブジェクトにドメインロジックを含む |
---|---|
Row Data Gateway | データアクセスオブジェクトにドメインロジックを含まない |
- ActiveRecordとデータマッパーの決定的な違い
ActiveRecord | ドメインはデータソースを継承 |
---|---|
Data Mapper | ドメインとデータソースの分離を仲介 |
※マッパーから取得したドメインモデルにはデータアクセス機能はない
そこから言えること
ActiveRecordがデータソースを内包したビジネスロジックの実装なのに対して、データマッパーはドメインとデータソースの分離を目的にしているので、志向性が全く逆になります。
ドメインモデルの初期設計で最初に判断するべき選択は、アクティブレコードかデータマッパーのいずれを使用するかである。
- -
PofEAAの10.3.2
データマッパーはアーキテクチャ的にはDomain-Driven Designとの相性がよいですし、軽量でクリーンなドメインを構成するのに役立ちます。
一方、ActiveRecordはRDBMSを軸としたアプリケーションの実装を高速化するのに役立ちます。オブジェクトから即保存されるということで実装者から見た透明感が高いです。
クリーンなモデルを目指すならDDD、スピードを追求するならActiveRecordという図式はここから生まれるものと思いますが、一方で、DDDの実装スピードが必ずしも遅くないという点、ActiveRecordが複雑なドメインを扱えないわけではないという点で、思想の違いはあれ、できることに大きな違いがあるわけではないとは思います。
そのため、ActiveRecordかデータマッパーかという議論は哲学的で、実践的ではないという批判を受けることもあります。ただ、それはどちらかを選択して進みさえすれば、いずれでも成果物が得られることで、混同して使った場合には両方の良さを食いつぶすことは間違いないと思います。
AcriveRecord or Data Mapper
では、優劣の話ではないという前提で、Zend Frameworkでモデルを実装するならどうするべきでしょうか。
データマッパーとActiveRecordは目的の部分で大きくかい離しているので混合して使うのは筋が悪いとして、設計初期にどちらかを選択する必要があります。
Zend_Dbを生かすならデータマッパーが正解。
Zend_DbはActiveRecordをサポートしていないので、ActiveRecordを構成しようとして、Zend_Db_Table_Rowを拡張しようとすると、恐ろしくコード量が増えてしまいます。つまり、Zend FrameworkでCakePHP的な実装をしようとするのは効率が悪いと思います。というか、Zend Frameworkを選ぶ理由はActiveRecord以外を使いたいからってのがトップにくるものと思ってるんですが、どうなんでしょう・・・
ですので、Zend_Dbを使うならデータマッパーが正解かと。
AcriveRecordならDoctrine
ActiveRecordで使えるシステムを作っていくには、ライブラリ側の充実が欠かせないと思います。Zend_DbはDoctrineで言えば、DBAL(Database Abstraction Layer)にすぎません。
http://www.doctrine-project.org/documentation/manual/1_2/en/introduction#basic-overview参照
そんな中でARを自作するのは、あまりにもコストが高いと思います。もちろん作るのは自由ですが、Zend FrameworkとDoctrineとの連携はさほど難しくはないので、ActiveRecordがしたければDoctrineの採用を検討するべきだと思います。
Zend Framework (日本)でData Mapperを採用するときの注意
日本のPHPフレームワーク界でCakePHPは不動の人気を誇っていますし、雄たるsymfonyもDoctrineの採用でActiveRecord側についています。そのためか日本語でのWeb経由の情報量ではモデル=ActiveRecordが圧倒的です。
データマッパーを使うなら(=Zend_Dbを使うなら)、開発者間の意識のズレを補うためActiveRecordと対比してData Mapperとはどういったものなのかを、確認しておく必要があるように思えます。データマッパー自体は難しい話ではなく、ZF公式ドキュメントのquick-startで十分だろうと思いますが、ActiveRecordとの違いは明確にしておくとよいと思います。
モデル、その他の選択肢
さて、Zend_DbはDALだという話で終わってしまうと、空洞にしてあればなんでもありか、という話になってしまいますが、意外とそうではなく・・・
- Zend_Db_TableとTable Moduleパターン
Table Module | テーブルに対する複数のドメインロジックを含む操作のカプセル化 |
---|---|
Table Data Gateway | テーブルに対するデータソース操作のカプセル化 |
Zend_Db_Tableはテーブルデータゲートウエイですが、それと連携するテーブルモジュールを書くと言う方向性は実は日本のWebアプリに最適だったりします。これについては、また後日。また、場合によっては、こういうくだらないアーキテクチャ論争をする必要はなく、すっぱりZend_Db_Table_Adapterと生クエリで十分ということも言えます。
Zend_Db_SelectでORM/クエリオブジェクト
こちらも後日。
でも混同はなし。
その他の
発表関係の資料はZend Framework勉強会#2 で使用した資料他 - noopな日々でリンクしていますが、下のスライドは、Zend Frameworkのモデルについて話す予定にしていて原稿も用意していたのですが、ネタがかぶるかなというのもあって没稿にしていたものです。PofEAA関連の話を流し読みできるように図解してあります。もしよろしければごらんください。
他の方の意見など
-- 勝手訳 --
Zend FrameworkはActiveRecordではなく、代わりに、テーブルデータゲートウエイとローデータゲートウエイパターンを使い、ローデータゲートウエイのデータをモデルにマップするのにデータマッパーを使います。なぜなら、モデルがデータベーステーブルと1:1にマッピングしていないとActiveRecordは成立しないからです。
- -
Zend Framework does not use ActiveRecords but instead uses the Table Data Gateway and Row Data Gateway pattern, and uses a DataMapper to map the contents of the Row Data Gateway to the model, because ActiveRecord breaks down when your models don't have a 1:1 mapping to your database tables.
http://stackoverflow.com/questions/1016966/does-the-datamapper-pattern-break-mvc
という話もありますが、言い過ぎで、ActiveRecordを悪者にするほどZend Frameworkは傲慢ではないと思います。実際、ActiveRecordをサポートするべく、Doctrineとの連携などもやれるようになりつつあります。
また、Domain-Driven Designとの対比では、
-- 勝手訳 --
Domain-Driven Designを考慮すると、Zend Frameworkは、DDD/Repositoriesの代わりに、データマッパーを使っています。それでDDDと言えるか? マーチンハウラーによるリポジトリの解釈では、それはNOだが、Eric Evansによれば、DDDのRepositoryはシンプルにできると言っている。その最もシンプルなRepositoryはデータマッパーだと彼の著書の中に書いてあります。
The Zend Framework is using DataMappers instead of Repositories. Is this really DDD-ish? Well, Fowler's interpretation of a Repository might say no. However, Eric Evans states that a DDD Repository can be very simple. At its simplest, a Repository is a DataMapper (See DDD book).
と言う具合に、データマッパーはDDDのリポジトリとして使っても問題ないだろうという意見があり、私もそう思います。
また、データマッパーやローデータゲートウエイがRepositoryを構成する材料になっていることは間違いないので、高度なRepositoryを組むのにZend_Db関係をうまく利用するのに障害はありません。
ORMとしてActiveRecord的に実装するのがダメかと言うとそんなことはなく、Zend_Db_Table_RowをActiveRecord的に使ってもいいと思います。ただ、今のところ、Doctrineなどの外部ライブラリを使うにしても、Zend_Db_Table_Rowを使うにしても、ActiveRecord的な実装を行うにはいくつかのトリックと膨大な工数を必要としそうです。
まぁ、そこが楽しいんですけどね。