前回 に続いて、今回はMerbで主要なORMとして使われているDataMapperを紹介します。
DataMapperは、『 Patterns of Enterprise Application Archtecture 』( 通称PofEAA)で紹介されている「データソースのアーキテクチャに関するパターン」の一つです。他にも、Railsでお馴染みのActiveRecord、テーブルデータゲートウェイ、行データゲートウェイなどが紹介されています。
DataMapperは、その名の通りPofEAAのDataMapperパターンを実装するORMとなっています。
DataMapperパターンとは
DataMapperパターンは、モデルとデータベースの間を取りなすMapperという中間的な構造を持っています(図1 ) 。
図1 DataMapperパターン
そのため、ActiveRecordよりも抽象度が高く、様々なデータソースを透過的に扱う事ができます。例えば、DataMapperでCouchDB を使うためのアダプタ(dm-couchdb-adapter) 、Google App EngineのDataStoreを利用するアダプタ(dm-datastore-adapter) 、REST APIを公開するWebサービスをストレージとして利用するアダプタ(dm-rest-adapter)などが存在しています。
DataMapperの仕組み
DataMapperのコア部分(dm-core)は、主に表1 のような構成要素からなります。
表1 DataMapperの構成要素
構成要素 役割
DataMapper::Resource レコードの実装を提供する
DataMapper::Model モデルクラスの実装を提供する
DataMapper::Query データストアへのリクエストを表す
DataMapper::AbstractAdapter データストアを抽象化する
DataMapper::Collection クエリの結果を表す
DataMapper::Associations モデル間の関係を表す
DataMapper::Property モデルの属性を表す
DataMapper::Repository Mapperを表す
DataMapperのコンポーネントは、基本的にクラスではなくモジュールとして提供されているため、アプリケーションで利用するモデルクラスにインクルードして使います。
Resourceモジュールをインクルードすると、自動的にModelモジュールがextendされ、PersonクラスはModelクラスとして振る舞うようになります。これは図1のPersonに相当します。
図1のPerson Mapperに相当する仕組みはRepositoryです。Repositoryは、create, update, delete, read_one, read_manyなどの、データストアに対する基本的な操作のインターフェイスを規定します。Modelクラスは、Repositoryへの参照を持っていて、 Modelクラスによるデータストアへのアクセスは、全てRepositoryを介して行われます。この際、データストアへのクエリは、SQLではなく Queryというオブジェクトによって渡されます。Queryは、conditionsやlimit, order, fieldsなどの、クエリに必要な情報を保持し、Repositoryを介してデータストアに要求を伝える役割を持っています。
このため、DataMapperはSQLベースのRDBMSだけでなく、CouchDBやGoogle App EngineのDataStoreなど、様々なタイプののデータストアに対応する事ができます。
Adapterの仕組み
Repositoryが様々なタイプのデータストアに対してリクエストを発したり、結果を受け取る際に、Repositoryとデータストアの間を取り持つのがAdapterの役割です。DataMapperでは、全てのAdapterに共通する抽象基底クラスとして、AbstractAdapterが用意されています。また、MySQLやPostgres, Sqlite3などのRDBMS全般のアダプタの共通部分をくくりだしたDataObjectAdapterも用意されています。
Adapterの仕事大きく分けて二つあります。
一つは、Repositoryから受け取ったQueryを解釈し、実際のデータストアが理解できる形式(例えばSQLなど)に変換して実行する事です。
そしてもう一つは、クエリの結果を返す事です。クエリの結果が単体の場合、リソースオブジェクトが直接返されますが、クエリの結果が集合である場合には、Collectionオブジェクトとして返されます。Collectionオブジェクトは、LazyArrayの派生クラスとして実装されていて、要素へのアクセスがあった場合に、データストアへのリクエストを遅延発行させる仕組みを備えています。
Property
DataMapperの大きな特徴の一つが、テーブル定義をmigrationファイルで行わずに、モデルクラスの中で行う事です。以下のように、モデルクラスのクラス定義文で、DSL的にpropertyメソッドを呼んでモデルの属性を定義します。
リスト1 Personモデルの定義例
class Person
include DataMapper::Resoruce
property :id, Serial
property :first_name, String
property :last_name, String
end
ここで定義された属性は、Adapterがマイグレーションに対応していれば rake db:automigrate もしくは rake db:autoupgrade でデータストアに反映させる事ができます。テーブル構造が変わっても、autoupgradeを実行するだけでインクリメンタルに変更を適用してくれるのがとても便利です。
また、dm_should というライブラリを使う事で、以下のように propertyブロック内にvalidationを記述する事ができます。
リスト2 バリデーションの設定例
class Person
include DataMapper::Resource
property :id, Serial
property :first_name, String do
should be_present
end
property :last_name, String do
should be_present
end
property :email, String do
should match(:email)
end
end
validationを行うためにはdm-validationsというGemもありますが、RSpecが大好きな人にはこちらがお勧めです。
まとめ
DataMapperはデータストアを抽象化したRepositoryを介してデータを操作するため、ActiveRecordなどと比べると全体的にRDBMSなどの内部構造に縛られない作りになっています。そのため、様々なデータストアを利用する事ができます。
Merbと統合される予定のRails3の開発が進む中、DataMapperやActiveRecordなどのORMを抽象化する存在として ActionORM(fka ActiveORM)という名前も出てきていますが、DataMapperのRepositoryの仕組みをベースに実装するといい感じにまとまるのではないかと思います。RackのORM版とも言えるようなシンプルなAPIを提供してくれると良いですね。