AS3SX入門4。
やること
AS3SXのウリ?の一つに"Super Easy Database Access without any SQL"とある。
SQLを使用せずに簡単に利用できるDBが一緒に提供されているようだ。今回はそのお話。
Rest-termさんが以下の記事でこのDBの実体がMongoDBではないかと考察されている。
Rest-term "AS3SXはPaaSとして成功するか"
用語も似ているようなので、もうMongoDBだと仮定しちゃって話を進める。
MongoDBはドキュメント指向と呼ばれるDBで、このタイプのDBはスキーマレスでデータ構造を自由に変えることができる、オブジェクト指向プログラミングとなかなか相性が良いもののようだ。
MongoDBでは一件のデータを"ドキュメント"、ドキュメントの集まったグループ(RDBMSでいうテーブル)を"コレクション"と呼ぶ。
さて、AS3SXではDBに保存するドキュメントはPersistentObjectクラスの派生クラスという形にしなくてはならない。
また、派生クラスのクラス名によってドキュメントの所属するコレクションが自動的に分けられる。
つまり、PersistentObjectの継承クラスがコレクション、そのクラスインスタンスがドキュメント、という形になる。
PersistentObject
PersistentObject及びその継承クラスはまず、普通にnewして作成する。
そして作られたインスタンスをinsert()することでコレクションに挿入することができる。
挿入時に_id:Stringという、DB内で一意に識別されるプロパティが与えられ、同時にDBの該当ドキュメントと紐付けられる。
以降そのインスタンスをupdate()したりremove()すると紐付けられたDB内のドキュメントが操作される。
なので当然insert()されていないPersistentObjectはremove()やupdate()することはできない。
ドキュメントの保存時にはインスタンスがJSON形式に変換され、保存される。
この時JSONとして保存されるのは、___type:Stringというコレクション名(=クラス名)とインスタンスのpublicなインスタンス変数だ。
PersistentObjectにデフォルトであるのは_id:Stringのみだが、PersistentObjectを継承したクラスでpublic変数を定義することでその変数も保存することができる。
static変数やprivate変数は当然保存されない。
(Functionをpublicインスタンス変数として保存した場合、DBからのデータ取得時にエラーが出たのでやめたほうがいいかもしれない)
(*注)このremoveメソッドを呼び出すと内部エラーが起こるので、現状(version 0.1)使用できない。
具体的には
Error #2067 ExternalInterface はこのコンテナで使用できません。ExternalInterface を使用するには、Internet Explorer ActiveX、Firefox、Mozilla 1.7.5 以上、または NPRuntime をサポートするブラウザーが必要です。
が起こる。ExternalInterfaceを使用するにはswfが既定のブラウザ上で実行されていなくてはならないのだが、馬鹿全さんの記事によるとサーバーサイドswfはスタンドアロンプレイヤーで実行されているようだ。
このことにより起きているランタイムエラーだと考えられる。
一応解決方法はあるのだが後述。
Complete系のメソッドは、それぞれDBへ出した命令が完了した際に強制的に呼び出されるコールバック関数だ。
つまり、たとえばpersistentObject.insert()を実行すると完了時にpersistentObject.insertComplete()が勝手に呼びされる。
この3メソッドはPersistentObjectでは中身の記述されていないメソッドなので完了時にもなにも起こらない。
PersistentObject継承クラスでoverrideすることによって自分でコールバック処理を実装することができる。
DB操作完了時になにか処理をしたいときはここで実装する。
お勧めというか、自分の使い方としては、public static変数にiComplete:Functionを定義しておいてoverride public function insertComplete内でiComplete()を呼んでみたりした。これで外部からExtendedPersistentObject.iComplete = this.insertCompleteのように指定することでドキュメント挿入完了時の処理をイベントリスナのように登録できたり。
getIndexedFields()ではgetという名前からインデックスの張られたフィールドが取得できるのかと思いがちだが騙されてはいけない。
自分で定義するのだ。
PersistentObjectではgetIndexedField()の実装はreturn [ ];と空の配列が返されるようになっている。
このメソッドをoverrideして自分の好きなフィールド名(publicインスタンス変数名)をArrayにして返すことで内部的にgetIndexedField()が呼ばれ、返したフィールド名でインデックスが張られる。
例えばpublic var userName:Stringにインデックスを張りたいときにはreturn ["userName"];とインスタンス変数名のStringの入ったArrayを返すようにする。
実際にはPersistentObject継承クラスをセレクトする際に、サーバーサイドswfが実行されてからそのクラスを初めてセレクトするとき、getIndexedField()で取得したインデックスを張るべきフィールド名のArrayをもとにDBにインデックス張れよ命令を出すようだ。
これだとサーバーサイドswcが起動するたびにインデックスを張る命令を出すことになるが、MongoDBではインデックスを張る命令ensureIndex()はインデックスが存在しない場合に限りインデックスを作成するようなのでたぶんだいじょうぶ。
インデックスの張られたフィールドでは検索のパフォーマンスがあがるらしい。
詳しい仕組みやインデックスを張るフィールドの有効な選び方は良く知らないので他で調べてもらいたい。
また、AS3SXで実際に検索パフォーマンスがあがっているかどうかは未確認だ。むしろ誰か確認して。
以上がPersistentObjectとその継承クラスの基本的な使い方だが、これではドキュメントの作成と挿入しかできないので、DBから取得する方法が必要だ。
その機能を持っているのがPersistentObjectManagerクラスだ。
このクラスインスタンスにはServerAS3SX.persistentObjectでアクセスすることができる。
PersistentObjectManager
まず、persitentという英単語は存在しないが、誤字ではない。たぶんpersistentのことだと思うのだがなぜ一文字省略したかは謎だ。もしかしたら現地語の可能性もあるが。
コード補完機能のあるエディタを使わない場合は注意が必要。
4つあるメソッドすべてが、最後の引数にcompleteHandler:Functionをとる。察しが付くとおもうが、DBからドキュメントの取得は非同期処理なので、完了したときのコールバック関数をここで指定する。
大きく分けて、1つ取得するもの(getOne~)、複数取得するもの(get~Array~)に分けることができる。
前者はコールバック関数がtype:Classで指定したクラスを、後者はArrayを引数にとるので、受けるメソッドはその形で記述する。
つまり取得したドキュメント(がPersistentObject継承クラスに変換されたもの)あるいはそのArrayがそのままコールバックの引数で渡されることになる。
検索の結果見つからなかった場合は前者はnullが、後者は[ ]が返ってくる。
getOnePersitentObjectByはname:Stringとしてフィールド名(publicインスタンス変数名)を、value:Objectとしてその値を指定することでtype:Classで指定したPersistentObject継承クラス(コレクション)の中でフィールドが値と一致するものを(たぶん最初にみつかった)1つ取得する。
フィールドの値が一意とわかっている場合はこれを使えばいいだろう。
(*注)このgetOnePersitentObjectByIdメソッドも上手く機能していない。AS3SX Admin CenterのEdit Databaseでドキュメントの_idを見ることができるが、その_idについてgetOnePersitentObjectByIdをしてもnullが返ってきてしまう。バグなのかなんか間違ってるのかはわからないが機能的にもこれを使うことはないだろう。
ちなみにgetOnePersitentObjectByのname:Stringに"_id"を指定しても同じ結果になる。
getPersitentObjectArrayByはgetOnePersitentObjectByとまったく一緒。返ってくるのが(コールバックの引数にしなくてはならないのが)Arrayなだけだ。
このArrayだがDBの順番に取得されるわけではない(そもそもDBではinsertした順番に並んでいるわけではない?)ので注意。順番が大事な場合はuintで(順番という意味の)インデックスを定義して保存しておいて、適宜ソートをする必要がありそう。
getPersitentObjectArrayByQueryではquery:Queryに任意のQueryオブジェクトを指定することで多少複雑な検索ができる。
実は先の3メソッドはすべて内部でこのメソッドが呼ばれている。
Query
Queryオブジェクトにはtype:Classにコレクションを、conditionsArray:Arrayに検索条件であるConditionオブジェクトのArrayを、limit:intに取得するドキュメントの最大数を、skip:intに(たぶん)検索で発見した順番に頭からいくつ無視するかを指定する。
それぞれのコンストラクタ引数は同名のpublicインスタンス変数を持つのでそちらからでも指定が可能。
このQueryオブジェクトでコレクションのクラスと条件、取得最大数と見つかっても無視する数を指定する。
conditionsArray:ArrayからわかるようにConditionオブジェクトの配列であり、ここで複数のConditionオブジェクトを指定すると両方の条件に合致するドキュメントを検索するAND検索ができる(OR検索はできない).
Condition
ConditionオブジェクトにはpropertyName:Stringでフィールド名を、value:Objectで比較する値を、 type:Stringで比較演算子を指定することができる(*注)
比較演算子はConditionクラスのpublic static constに
があり、それぞれの意味で使えるものと思うのだが、
(*注)EQUAL = "EQ"以外は使いものにならない気がする。
試しに使ってみたのだが、どうもぜんぶ"EQ"と同じ結果になるようだ。よくわからんので保留。
保留おおすぎ。
というわけでQueryオブジェクトのconditionsArray:Arrayに[new Condition("i", 2), new Condition("j", 4)]のように指定することでi = 2、j = 4を持つドキュメントを指定のコレクションから探してきてくれる。
以上がPersistentObject及びその継承クラスの使い方だ。
これで独自にサーバーを用意することなく簡単なデータを保存しておくことができる。
うーん。。このDBがどこまで使い物になるかはちょっと不明。
最後になるが、AS3SX Admin Centerの該当プロジェクトのEdit Databaseでコレクションごとにドキュメントを操作することができる。
ドキュメントの閲覧、挿入、削除、値の変更、条件を指定しての検索など一通りの操作が可能だ(特定のドキュメントにのみフィールドの追加なんてこともできる)。
最初の状態ではなにも表示されないが、1つPersistentObject継承クラスをinsertすると該当のコレクションが追加され、表示される。
Exportというメニューがあるがなんかエラー出るので保留。
Deleteも書いてあるのだが選択できないため、コレクション自体の削除がこれまたできない。
ドキュメントが0件になってもコレクションは残るのでちゃんと考えずにポンポンコレクション作るとごちゃごちゃする。
長くなったので実際にPersistentObjectを使ったコードは次回。
おまけ。
上記のpersistentObject.remove()時のError #2067を無理矢理回避できるクラスを作ったので使いたい人はどうぞ。
使い方
PersistentObjectRemover.instance.remove(persistent);
のようにPersistentObjectRemover.instance.removeメソッドに削除したいPersistentObjectを引数にするだけ。
同梱のcom.as3sx.container.communcation.ServerCommunicatorも階層(パッケージ)をそのままで使うこと。
パッケージ名のcommuncationも存在しない英語単語だけどつけたの俺じゃないから突っ込まないで。
remove完了時に引数にしたPersistentObjectのremoveCompleteも呼ばれるので通常のものと同じように使えるはず。
AS3SX 入門4 PersistentObjectについて(後編)
Author:9ballsyn
ActionScriptについて
最近はMolehill