データモデルクラスは何をモデル化してるのか


 100体を超えるゴブリンの群集がプレイヤーに襲いかかり、プレイヤーは彼らを一騎当千の力で返り討ちにした。傷ついた一部のゴブリンは撤退を判断する。撤退するゴブリンの中でも残りのライフに余力のある個体がリーダーとなり、リーダーとなった個体の周りの負傷ゴブリンを最大8体まで指揮下において新たなスクワッドを組織する。そして彼らのAIモードは撤退へと書き変わる……

 CakePHPなど、最近のフレームワークはだいたいActiveRecordを実践している。次のようにすれば簡単にデータを取得できる。

<?
 $data = $this->Model->find( $id );
?>

 書き換えたデータは保存しなければいけない。

<?
 $data['leader_flag'] = 1;
 $this->Model->save($data);
?>

 XOOPSでいえば、モデルの役割はハンドラに近いように見える。

 モデルから取り出すレコードが配列である点には批判もあり、レコードを配列ではなく、オブジェクトで返そうとする話もある。RoRはオブジェクトを返すらしい。その志に従えば、下のようなコードが予想される。

<?
 $data = $this->Model->find( $id );
 $data->setLeader();
 $this->Model->save( $data );
?>

 長年 XOOPS にお付き合いのある方なら、よくも悪くもお馴染みの感覚だろう。

<?
 $data = $handler->get( $id );
 $data->setLeader(); // もしくは setVar()
 $handler->update($data);
?>

 ところが、どうもモデルによる操作というのはこういうことではないらしい。ひとつは先ほど書いたActiveRecordインスタンス化する場合は、そのモデルのクラスのインスタンスが帰ってくる実装が存在するらしいという話。XOOPSでいえばハンドラとオブジェクトが合体しているような感覚なんだろうか。妥当かどうかはともかく次のようなコードが通ってしまう。

<?
 $t_model = $this->Model->get( $id );
 $t_model->setLeader();
 $t_model->save( $t_model );
?>

 まぁちょっと上の話は、どうしてもありえない気がするので、勘違いかもしれない。

 もうひとつは、上のような話の前に、モデルを使った操作は以下のようになるという話(↓)

<?
 $this->Model->setLeader( $id );
?>

 つまり、データを取り出して書き換えるのではなく、データを書き換えることをモデルにオペレーションするということになる。ビューに出力するためにデータを取得するのはありだが、取り出して操作して書き戻すのをコントローラなどで実装するのはNG……というスタンスに見える。

 サブルーチンとしては理解できる。しかしそのようなメンバ関数を多く持つデータモデルとは一体どういう存在なのだろう? なにかを具象化しているのだとすれば、それはなんなのだろう。データ1行のインスタンスでもなければ、コンテナに操作を取り付けたものとも異なる。少なくともデータモデルがゴブリンの個体でないことは確かだ。

<?
 // こうなるの?
 $this->Model->changeCandidatesToLeader();
 $this->Model->makeSquad();
 
 // もしくはこうか?
 $this->Model->regroupEspaceTeam();
?>

 昨日、実機上で動作するツールを書いていたとき、 Web MVC のことを考えて実装をしてみた。そこでどうしても理解できなかったのが、「個体とコンテナ」という関係を作れないことだった。これは Web MVC のよしあしの話ではなくて、 Web MVC の M が何をモデル化しているのかが体感的に理解できないという話です。たとえば、キャラクターとしてのステータスと、表示物としてのモデル参照/テクスチャ参照をもつゴブリン(CGoblin)と、大部隊(CDivision)があったとする。もうこの時点でアウトな気がするが、くじけず、何も考えず書けば下のような形が考えられる。

 // 指定のゴブリンをリーダーにする
 CGoblin *pGoblin = pDivision->get( id );
 pGoblin->setLeader();
 
 // リーダーを中心とした新しいスクワッドを作成
 CSquad *pNewSquad = pDivision->createSquad( pGoblin, 8 );
 
 // リーダー周辺の負傷ゴブリンを取得して配属
 int itr;
 for (CGoblin *pFollower = pDivision->getWoundedFirst( &itr, pGoblin );
      !pFollower; pFollower = pDivision->getWoundedNext( &itr, pGoblin ))
 {
     if (!pNewSquad->add( pFollower ))
         break;
 }

 撤退制御のコードを書く人間と CGoblin CDivision を書く人間が果たして同一人物かどうかをともかくとすれば、上のコードは CDivision にメンバ関数として実装してもいいのかもしれない。だとすれば、モデルとは「コンテナ」なのだろうか。しかし、ゴブリンは個体としても確実に動作する存在であり、その個体をしめすクラス(インスタンス)がないとはどうしても考えられない。特定の個体とヒットを取ったり、通信したり、制御する機会はいくらでもあるはずだ。つまりコンテナなりマネージャから取り出して、個体とやりとりをするというアプローチが一番しっくりくるはずだ。

 ここにいたって、自分なりの結論を出すならば、データモデルがモデル化しているのは、データベースの特性そのものではないだろうか。それが理由で、畑違いの人間には一体なにをモデル化して扱っているのかが、感覚的にまるで理解できないということになるのでは。

 Web アプリケーションはメインループが存在しない。データベースに保存しない限り、次回のアクセスに今のアクセスの結果を引き継ぐことはできない。そしてデータベースのレコードは個体として勝手に変化することはまず考えられない。外部からクエリーをかけない限り中身は変化しない。

 Web アプリケーションのエンジニアは、基本的にはデータベースを操作して全体のシステムを稼動(状態変化)させている。そういうデータベースというストレージに対するオペレーターをモデルにしたものが RoRCakePHP のモデルなのではないか。

 たとえば、一括更新を発生させる場合、データベースなら SQL 一発で済んでしまう。上のように、条件にしたがって取り出したインスタンスの配列に操作を行い、書き戻すという手順はデータベースの感覚からすれば非直感的といえるだろう。だからデータモデルクラスは、検索と書き換えを一発で行うようなメンバ関数を実装する。それは、データベースを直接扱う技術を持つ専門職の方にとって直感的な作りになる、と言える。

 wikipedia:データモデルに書かれている、

データモデル(Data Model)とは、抽象的な形式でビジネスや情報システムやデータベース管理システム (DBMS) でのデータの表現方法をモデル化したものである。

 の「DBMSでのデータ表現方法をモデル化したもの」というくだりは、そういう解釈でいいのだろうか。

 モデルは通例データソースと深く結びつかないほうがよいと言われるけど、データモデルという時点で、データベースというデータソースの1カテゴリとばっちり結びついて問題がないのかもしれない。

(それともデータソースとは、ROMとか、ファイルシステムとか、データベースといった要素ではなく、単にデータベースの中のどの製品か、ということ抽象化した情報なのかも……)

 ところで、以上のことを考えると1レコードを1インスタンスとして取り扱う O/R マッパーは、想像よりはるかに意義のあるアプローチだったのかもしれない。

pDivision->get( id )->setSomething(...);

 と扱えることはデータベースという存在に対して非論理的なアプローチだった。しかし、データベースを知らないエンジニアでも制御することができる。データベースアクセスそのものを簡単にするアプローチだと考えていたが、存在意義は「異分野の人間に対するデータベースの特殊性の抽象化」だったのかもしれない。そこを勘違いしていたのか。