Entity設計について
GAEではEntityというモデルを定義してJDOを使ってデータを永続化する。Entityはオブジェクトモデルであり画面など実際の業務アプリケーションや外部インターフェースなどを直感的に表現できるという点で優れている。しかし、階層型ということもあり、リレーショナルなモデルとはあまり馴染まない。もしかしたら、これまでRDB的な設計を中心にやってこられた方にとっては苦痛さえ感じるかもしれない。Entityをうまく設計するためには、ひとまずRDB的な頭をリセットしてオブジェクト的な発想をすることをおすすめする。一度慣れてしまうと誰でも簡単にサクサク作れるようになるだろう。逆に、RDBの呪縛から開放されOOに覚醒してしまうともう後には戻れないので注意が必要である。RDBって不要じゃん?とか、これからの時代はOOでKey/Valueだ!などと思うようになれば覚醒した証拠だ。だがこのモデルがRDBにとって代われるかというとそうではない。特に運用面を考えると全く使い物にならないだろう。データ操作はSQLとは比べ物にならないぐらい酷いので、完全移行はそれほど甘くはないと心得ておくべきだ。現時点のクラウドはあくまでXTP(eXtreme Transaction Processing)基盤として考え、RDBはデータウェアハウスなど裏で動く情報系システム用として考えるとよいと思う。モノシリックなアプリはRDBで動作するがあくまで正はRDBであるとする。(このようなハイブリッド方式は、Reflex BDBでも採用している)
オブジェクトモデルを使ってEntityを設計する方法については実はReflexも同じである。ReflexはGAEが出る前から存在するフレームワークであり、決して真似して作ったたわけではないが、偶然にも非常に似た考え方で設計できるものとなった。具体的には、ZEROからRESTfulアプリを作成を見ていただくと分かるのだが、週報アプリ画面から実際にEntityを作成しながら開発している。
1.画面のモックアップを作る
2.モデリングを行い画面のEntity(Model)を作成する
3.テーブルのEntityを作成する(DAO)
4.Entityのインスタンスを作成する
また、Entityはマインドマップなどを使って直感的に生成できる利点もある。下図はReflex表現(画面左)を使ってEntityを定義してマインドマップを生成した様子である。
Reflex表現さえ作成できればマインドマップを作成できるし、Entityのソースも自動生成できる。Reflex表現は、今流行りのドメイン特化言語のようなものだが非常にシンプルな表現でEntityを作成できることが特長である。Reflex表現の詳細は、Reflex Entity Editorを見てもらえればわかるが、要は、要素の型と多重度、および親子関係を定義するだけである。
ReflexとJDOの共存
JDOのEntityとReflexのEntityは同じ構造をもてる。例えば、上のマインドマップで、one-to-manyを示すものはReportとactivityであるが、これはReportクラスのListプロパティとしてactivityを定義したものとなる。これはJDOもReflexも全く同じ。Entity定義におけるJDOとReflexの違いは、JDOはデータの永続化のために使うマーカとして、@PrimaryKeyや@Persistentなどアノテーションを使うのに対し、Relfexは主にXMLやJSONなどの出力対象をpublicにするなど、CoC(設定より規約)に基づく考え方で生成することである。(詳細=>reflexcore)CoCは規約を覚える必要があり敷居を高くしているがReflex Entity Editorなどの自動生成ツールがそれを補っている。
また、偶然の産物であるが、両者はお互いに干渉しないため、永続化と外部インターフェースを1つのクラスのなかで表現できるというメリットが生まれている。
Entity Groupsとトランザクション
GAEでは、全てのentityはentity groupに所属しており、entity group単位でトランザクションを実行できる。先ほどのReportとactivityの例のように親子関係をもっているものは、同じentity groupとみなされ同一トランザクションのもとに扱うことができる。以下の例は1対多の関係を持ったEntityの場合であるが、1対1の場合についてもListの代わりに単独のクラス名になるだけで同様に考えることができる。(追記:親子関係については嵌りやすいので注意=>JDOから直接JSON、XML
Owned_One_to_Many_Relationships
import java.util.List;
// ...
@Persistent
private List
また、子から親へのパス(双方向パス)をつけたいときは、@Persistent(mappedBy ="employee")のように、子のキーを示すアノテーションを親のクラスに追記すればよい。
import java.util.List;
// ...
@Persistent(mappedBy = "employee")
private List
トランザクションは、以下のように、PersistenceManager(シングルトン)を使いpm.currentTransaction()を呼び出すことで、txコンテキストを取得する。tx.begin();でトランザクションが開始、tx.commit();でコミット、tx.rollback();でロールバックとなる。以下の例では、tx.begin();で開始、ClubMembersのインスタンスをキー”k12345"で取得して、インスタンスのカウントを足した後、pm.makePersistent(members);で永続化、tx.commit();でコミットしている。
import javax.jdo.Transaction;
import ClubMembers; // not shown
// ...
// PersistenceManager pm = ...;
Transaction tx = pm.currentTransaction();
try {
tx.begin();
ClubMembers members = pm.getObjectById(ClubMembers.class, "k12345");
members.incrementCounterBy(1);
pm.makePersistent(members);
tx.commit();
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
これは楽観的ロックの仕組みであり、tx.begin();の後でもロックがかかっているわけではないので注意が必要だ。つまり、他のプロセスが同じインスタンス”k12345"を読むことができるが、使用中の場合は、java.util.ConcurrentModificationExceptionが発生し、JDOはJDODataStoreExceptionかJDOExceptionをスローする。また、JDOは1回しかトランザクション処理を行わないため、何回か繰り返し実行してコミットできたら抜けるように、アプリケーション側(以下参照)でフォローしてあげなければならない。
ちなみに、同一トランザクション内で複数のentity groupを更新しようとしても、JDOFatalUserExceptionが発生してコミットできないようになっている。
What Can Be Done In a Transaction
for (int i = 0; i < NUM_RETRIES; i++) {
pm.currentTransaction().begin();
ClubMembers members = pm.getObjectById(ClubMembers.class, "k12345");
members.incrementCounterBy(1);
try {
pm.currentTransaction().commit();
break;
} catch (JDOCanRetryException ex) {
if (i == (NUM_RETRIES - 1)) {
throw ex;
}
}
}
関係を持たない複数のEntityのトランザクション
関係を持たない複数のEntityをEntity groupとすることで1つのトランザクションとして実行できるようになる。それは、以下のように、keyBuilder.addChild()を使って、一方のEntityのキー生成を他方のキー生成と結合させることで行う。
Creating Entities With Entity Groups
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class AccountInfo {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
public void setKey(Key key) {
this.key = key;
}
}
// ...
KeyFactory.Builder keyBuilder = new KeyFactory.Builder(Customer.class.getSimpleName(), "custid985135");
// AccountInfoのキー生成をCustomerのキー生成と結合させる
keyBuilder.addChild(AccountInfo.class.getSimpleName(), "acctidX142516");
Key key = keyBuilder.getKey();
AccountInfo acct = new AccountInfo();
acct.setKey(key);
pm.makePersistent(acct);
また、Entity group内の親キーにアクセスするには、以下のようなアノテーションを追記すればよい。
// ...
@Persistent
@Extension(vendorName="datanucleus", key="gae.parent-pk", value="true")
private Key customerKey;
ちなみに、関係を持たない複数のEntityを、Entity Groupとして定義すると、分散ネットワークの同じ場所に格納される。これは、あるトランザクションで遠くにあるentityが関係すると大量のネットワーク通信が発生し性能が落ちてしまうのを避ける目的がある。Entityがあちこちのサーバに散らばってしまうのはまずい。
ただ、What Can Be Done In a Transaction
Every entity belongs to an entity group, a set of one or more entities that can be manipulated in a single transaction. Entity group relationships tell App Engine to store several entities in the same part of the distributed network.
によれば、分散ネットワークの同じ場所とあるだけで、それがBigTableの同一row(行)なのかTabletなのか、あるいは、その両方でもないのか、不明である。(昨日のクラウド研究会でGregor Hohpeさんは、EntityはRow単位で、いろんなところに格納されるといっていたような気がする。資料=>ProgrammingCloud)
BigTableの行(row)
1.名前は、任意の文字
2.1行のデータへのアクセスは、atomicである
3.行は、データを格納する際に、自動的に作成される
4.行は辞書式順序で並べられている
5.通常、行は1台または数台のマシン上に、辞書式順序の近い順番にまとめられる
BigTableのTablets
1.大きなテーブルは、行の境界で、タブレットに分割される
2.タブレットは行の連続する範囲を維持する
3.クライアントは、局所性を達成するために、行のキーを選択できる
4.1つのタブレットあたりの100MB~200MBが目処
総括
1.Entity設計はオブジェクト設計を元にする必要がある。また、ドメイン特化言語を利用することで生産性向上を期待できる。
2.BigTableなどBASEを元にしたGoogleの基盤技術を使うGAEであるが、トランザクションの機能を使うことでConsistency(整合性)が保証されるアプリケーションを組むことができる。ただし、1UOW(Unit Of Work)= Entity Groupとなる。
今でもモヤモヤしているのは、Entity Groupの粒度をどうすればいいかという点。かなり強引だけどSCAに当てはめて考えてみる。Entity GroupはSCAでいうところのComponentに相当するのだろうが、さらにCompositeで括ったときのトランザクションがどうなるかわからない。今の流れからして、2phase commitはやるべきではない、ということにはなりそうだが。
<関連>
JDOから直接JSON、XML
JDOから直接SOAP、ATOM。それからDeep Copy
AJAX CRUDサンプルとJDO代替ライブラリ
Pagingをどうやって実現するか
RESTfulアプリのCRUDサンプル -Servlet編-
RESTfulアプリのCRUDサンプル -Modeling編-
Entityとトランザクション2