プロトタイプの作成
では、基本的なLiftの機能を利用して、プロトタイプを作成しましょう。作成の順序としては、まずイベントの情報を保持するModelを作成し、次に一覧画面と登録画面を作ります。
Event情報のModelの作成
では、イベントの情報を保持するModelを作成します。
Google App Engineでのデータアクセス
Google App Engineのデータストアにアクセスするための手段としては「Java Data Objects(JDO)」「Java Persistence API(JPA)」を利用する2種類の方法の他に、Low Level APIと呼ばれるGoogleが提供するAPIを利用する、計3通りの方法があります。
JDOやJPAを利用する方法は、これまでのJavaアプリケーションと同じようにデータストアへのアクセスができる、という利点があります。しかし、JDOやJPAは、本来リレーショナルデータベースへのアクセスのために用意された仕様であり、BigTableをベースとしたGoogle App Engineでのデータストアに適用するために、さまざまな制約が発生しています。
最近のGoogle App Engineでのアプリケーション開発では、リレーショナルデータベースではなくBigTableに合わせたデータ設計が必要であり、そのためにはJDOやJPAではなくLow Level APIを利用して柔軟に実装する方が良い、と言われています。
例えば、Google App Engineに最適化されたフレームワークであるSlim3で提供されるSlim3 Datastoreは、Low Level APIのラッパーとして提供され、Low Level APIのEntityオブジェクトとユーザー定義のデータクラスを透過的に扱えるようになっています。
上記のような理由で、本稿で作成するアプリケーションでのデータアクセスは、Low Level APIを利用する形で作成します。
Low Level APIを利用したModel
今回作成するModelは、一回のイベントの情報をEventというクラス名のcase class(*1)として扱うようにします。データストアへのアクセスを実際に行うのは、EventクラスのコンパニオンオブジェクトであるEventオブジェクト(*2)が行う形です。
Eventオブジェクトが、データストアからの検索や保存を行い、Eventクラスのインスタンスを作成します。Google App Engineにおけるデータストアでは、データはkindという種別と、データ項目であるプロパティを持っています。
kindは、データベースにおけるテーブルに、プロパティはデータの属性(カラム)に相当しますが、リレーショナルデータベースのテーブルのようにカラムを定義するわけではなく、レコードごとに任意の名前のプロパティを持たせることができます。つまり、データの形が決まっていない(ソフトスキーマである)ことが、リレーショナルデータベースと決定的に異なる点なのです。
今回作成するイベント情報のデータは、"event"という名前のkindで保存することにします。
ソースコードですが、「src/main/scala/demo/model」ディレクトリに、Event.scalaというファイル名で、下記の内容のファイルを新規作成します。
package demo.model
...中略...
// イベントの情報を持つクラスです。
case class Event ( // *1
id:Option[Long],name:String, description:String, fromDate:Date, toDate:Date,
place:String, join:Long, count:Long)
// イベントEntityを操作するオブジェクトです。
object Event { // *2 // GAEのLowLevelAPIを利用するためのDatastoreServiceを返します
private def dsService = DatastoreServiceFactory.getDatastoreService // *5
// Eventオブジェクトを保存します。
def save( event:Event ) = // *6
dsService.put( event2Entity( event ) )
// IDからentityを検索して返します。
def find( id:Long ) = { // *7
val key = KeyFactory.createKey("event", id)
entity2Event( dsService.get( key )) match{
case null => None
case e => Some(e)
}
}
// Eventの情報を検索し、Listで返します。
def findAll(limit:Int) = { // *8
val q = new Query( "event"
).addSort("toDate", SortDirection.ASCENDING)
dsService.prepare( q ).asList(
FetchOptions.Builder.withLimit( limit )
).map { e => entity2Event( e ) }
}
// 以下はユーティリティ関数
// GAEのEnityがもつPropertyの値を型を指定してとりだします。
def entityValue[T]( e:Entity, name:String ) =
e.getProperty( name ).asInstanceOf[T]
// GAEのEntityオブジェクトからEventオブジェクトを作成します。
def entity2Event( e:Entity ) = { // *3
Event( Some(e.getKey.getId), entityValue[String]( e, "name" ),
entityValue[String]( e, "description" ),
new Date(entityValue[Long]( e, "fromDate" )),
new Date(entityValue[Long]( e, "toDate" )),
entityValue[String]( e, "place" ), entityValue[Long]( e, "join" ),
entityValue[Long]( e, "count" )
)
}
// EventオブジェクトからGAEのEntityオブジェクトを返します。
def event2Entity( event:Event ) = { // *4
val e = event.id match {
case Some(id) => new Entity( KeyFactory.createKey("event", id) )
case None => new Entity( "event" )
}
e.setProperty( "name", event.name )
e.setProperty( "description", event.description )
e.setProperty( "fromDate", event.fromDate.getTime )
e.setProperty( "toDate", event.toDate.getTime )
e.setProperty( "place", event.place )
e.setProperty( "join", event.join )
e.setProperty( "count", event.count )
e
}
}
Low Level APIを利用したデータストアでは、一行のレコードはEntityクラスのインスタンスです。
Entityクラスからのプロパティの設定はEntity#setProperty( String name , Object value )を利用し、プロパティの取得はEntity#getProperty( String name )を利用します。
そのため、EventクラスとEntityクラスの相互変換を行うために、entity2Event(*3)とevent2Entity(*4)というユーティリティ関数を用意しました。
実際にデータストアへのアクセスを行うためには、DatastoreServiceクラスのインスタンスを取得する必要があります。このため、*5でdsServiceという関数を用意して、DatastoreServiceを取得できるようにしました。このdsService関数を、保存や検索を行う際に呼び出して利用します。
データの保存は、DatastoreService#put( Entity entity )を呼び出します。*6のsave関数では、引数に受け取ったEventクラスのインスタンスを、*4で作成したevent2Entity関数でデータストア用のEntityオブジェクトに変換してDatastoreService#putを呼び出しています。
データの検索は、データストア側で採番される一意なキー(Keyオブジェクト)を指定して一件取得する方法と、Queryオブジェクトを利用して問い合わせを行う方法があります。
*7のfind関数は、Keyオブジェクトを指定して検索する関数です。Longのidを基に、KeyFactory.createKey(String kind, long id)でKeyオブジェクトを作成し、DataStoreService#get( Key key )を呼び出してEntityオブジェクトを取得します。
取得したEntityオブジェクトは、*3で作成したentity2Event関数により、Eventクラスに変換されて、Option型に包んで返すようになっています。
複数件のデータを、条件を指定して取得したい場合はどのようにするのでしょうか?
Low Level APIでは、検索条件を指定するQueryというオブジェクトを利用することで、データストアへの問い合わせを行うことができます。
*8のfindAll関数は、Queryオブジェクト並び替えの条件を指定して作成しています。このQueryオブジェクトをDataStoreService#prepare( Query query)に渡して、返されるPreparedQueryオブジェクトのasList関数を呼び出して、List[Entity]を取得しています。
取得したEntityは、find関数と同じく1件づつentity2Event関数でEventクラスに変換し、List[Event]型で結果を返すようになっています。
以上、簡単に今回作成したModelの処理を解説しました。Low Level APIの詳しい使い方は、JavaDocをごらんください。
