SiNBLOG

140文字に入らないことを、極稀に書くBlog

Slim3 Source Code Reading No9 ModelRef

Slim3 Source Code Reading #9 - ChugokuGTUG

に参加してきました!


今回も@ttyokoyama 殿と2人だったので、まったりと進みました。
まぁ、他の方が参加していても、まったり進むのですがw

T.Yokoyamaのブログ: Slim3 Source Code Reading #9


今回読んだのはModelRefです。
ModelRefは1対多などのリレーションシップを表現するための機能です。
リレーションシップ - Slim3 日本語サイト(非公式)

この機能を使えば、リレーションシップをある程度タイプセーフに作成できます。
例えば、ModelRef#setModel()に誤ったModelを渡せばコンパイル時にエラーとなります。
また、ModelRef#setKey()でも、実行時にエラーとなります。


取得も簡単な記述でできます。
まず、1の側のModelを取得するgetModel()です。
ModelRef#getModel()


public M getModel() {
if (model != null) {
return model;
}
return refresh();
}

public M refresh() {
if (key == null) {
return null;
}
model = Datastore.getWithoutTx(getModelMeta(), key);
return model;
}

一度取得すると、メンバに保持するようになっています。
また、ここでDatastoreへのアクセスを行なっています。


次は、双方向1対1の場合などに使う、getModel()です。
InverseModelRef#getModel()


public M getModel() {
if (model != null) {
return model;
}
return refresh();
}

public M refresh() {
Key key = getOwnerKey();
if (key == null) {
return null;
}
model =
Datastore.query(getModelMeta()).filter(
mappedPropertyName,
FilterOperator.EQUAL,
key).asSingle();
return model;
}

こちらも一度取得すると、メンバに保持しています。
また、Datastoreへのアクセスも行なっていますが、こちらは何故かQueryです。
Queryである理由を考えましたが、特に何も思いつかず・・・。


最後は、多の側のgetModelList()です。
InverseModelListRef.getModelList()


public List getModelList() {
Key key = getOwnerKey();
if (key == null) {
modelList = new ArrayList();
} else {
query.filter(mappedPropertyName, FilterOperator.EQUAL, key);
if (!sortsSet) {
query.sort(defaultSorts);
}
modelList = query.asList();
}
return modelList;
}
こちらはQueryでKeyと一致するものを取得しています。
この他にも通常のQueryと同じようにfilterやsortもできます。
また、filterInMemoryとsortInMemoryもありますし、カーソルを扱うasQueryResultListもあります。
ただし、ancestor query を扱うことはできません
まぁ、RDB的なリレーションシップなので、KVSの考え方であるancestorと混ぜて使うような複雑なことにはならない気がするので、問題ないと言えば問題ないですね。


ただ、ここまで書いておいてあれなのですが、僕はModelRef使ってないです。
理由は大きく分けると以下の3つです。

  • ReadOpsの節約策が取りにくい。
  • 意図しない箇所で、Datastoreへのアクセスが発生する可能性がある。
  • あまり、JOINしたいと思うことがない。

ReadOpsの節約策が取りにくい
Datastoreへの無駄なアクセスを減らすのは、課金的にもパフォーマンス的にも良いことです。
そのための策は色々とありますが、どれもModelRefを使うとやりにくくなってしまいます。
Memcacheに入れるという手段もModelRefはサポートしてくれないため、自分でやる必要があります。
また、元のModelを取得しないとそれに関連するModelを取得することができません。
そんなことをしなくても、元のModelのKeyを生成して、queryを投げるとか、queryを避けるために元のModelにListを持っておき、batch getで取得するとか様々な手があります。


意図しない箇所で、Datastoreへのアクセスが発生する可能性がある
まぁ、これは僕の趣味なのですが・・・。
あまり、Service以外でDatastoreにアクセスしたくないのですね。
ModelRefを使うとController側に返ってきた後に、getModel()とかするとDatastoreへのアクセスが発生していまいます。
また、queryでfilterやsortを使うと、カスタムインデックスが必要になってしまう可能性があります。
その辺りが制御しづらくなるので、あまり使いたくありません。


あまり、JOINしたいと思うことがない
なかなか、本末転倒なのですが・・・。
GAEのDatastoreにおいて正規化はしばしば悪となるため、そんなに正規化していません。
検索できなくなるし、ReadOpsも増えてしまいます。
そのため、あまりRDBのJOINみたいなことをしたいと思うことがありません。


ということで、僕はModelRef使いません!ってのが結論です!
なんで、読んだし!って感じですが、実は以前使うかどうか悩んだ時に、既に読んじゃってたんですよねー。
それと合わせてGAE Office HourでModelの設計なんかを聞いて、使わない方が無難であるという結論に達してしまったわけです。


testerの方も読み進めていましたが、長くなってきたので、エントリーを分けます!