リレーショナル・データベースはこれまで 30 年以上にわたってデータ・ストレージの主流となってきましたが、スキーマレスな (あるいは NoSQL) データベースの注目度が高くなっていることは、この状況に変化が訪れていることを示しています。RDBMS は、従来のクライアント・サーバー・アーキテクチャーでデータを保管するための確固たる基盤となるものの、複数ノードへのスケーリングはそう簡単 (あるいは安価) ではありません。こうした点は、Facebook や Twitter のように Web アプリケーションが極めてスケーラブルになってきているこの時代においては、かなり時代にそぐわない弱点になります。
リレーショナル・データベースに代わるものとして当初考えられた手段 (オブジェクト指向のデータベースを覚えていますか?) は差し迫った問題を解決することができませんでした。それに対し、Google の Bigtable や Amazon の SimpleDB などといった NoSQL データベースは、Web が必要とする高度なスケーラビリティーに直接対処するための手段として登場しました。基本的に NoSQL は、Web 2.0 が進化するにつれて Web アプリケーション開発者が遭遇する回数は増えることはあっても、決して減ることのない、極めて厄介な問題に対するキラー・アプリケーションになり得ます。
連載「Java 開発 2.0」の今回の記事では、スキーマレスなデータ・モデリングの方法を手ほどきします。リレーショナルな考え方を身に付けた多くの開発者にとって、スキーマレスなデータ・モデリングは NoSQL の第 1 の難関です。これから学ぶように、(リレーショナル・モデルではなく) ドメイン・モデルから始めることが、スキーマレスなデータ・モデリングへの移行を楽にする鍵となります。記事の例と同じく Bigtable を使用している場合には、Google App Engine を拡張する軽量のフレームワーク、Gaelyk の助けを借りることもできます。
開発者が非リレーショナル・データベース、あるいは NoSQL データベースについて語るとき、よく口を突いて出るのは、考え方を変える必要があるという言葉です。私の意見としては、考え方を変えなくてはならないかどうかは、最初にどうデータ・モデリングに取りかかるか、その方法次第だと思います。最初にデータベース構造 (つまり、データベース・テーブルおよびそれぞれのテーブルの間の関係) をモデル化してからアプリケーションを設計する方法に慣れているとしたら、Bigtable などのスキーマレスなデータ・ストアでデータ・モデリングを行う際には、モデリング方法を考え直さなければなりません。けれども、アプリケーションの設計をドメイン・モデルから始めているのであれば、Bigtable のスキーマレスな構造は、より自然なものに感じられるはずです。
非リレーショナル・データ・ストアには結合テーブルや主キーがなく、外部キーの概念さえありません (ただし、いずれのタイプのキーも、主キーと外部キーというほど厳格な形ではありませんが、存在はしています)。そのため、リレーショナルのモデリングを NoSQL データベースでのデータ・モデリングの基礎として適用しようとすると、結局は上手く行かずに苛立つ結果となるのがおちですが、ドメイン・モデルから始めることで事態は単純になります。実際、ドメイン・モデルの基盤となっているスキーマレスな構造が持つ柔軟性には目を見張るものがあります。
リレーショナル・データ・モデルからスキーマレスなデータ・モデルに移行する際の複雑さの度合いは、モデリングの方法によって変わってきます。具体的には、リレーショナルな設計から取り掛かるか、あるいはドメインをベースとした設計から取り掛かるかによって左右されるということです。CouchDB や Bigtable などのデータ・ストアに移行すると、(少なくとも、今のところ) Hibernate のような確立されたパーシスタンス・プラットフォームが持つスマートさは失われることになりますが、その一方でパーシスタンス・プラットフォームを自分で作成できるというすがすがしさがあります。そして、この作成プロセスを通して、スキーマレスなデータ・ストアについて深く学ぶことになります。
スキーマレスなデータ・ストアは、まず初めにオブジェクトでドメイン・モデルを設計するという柔軟性を与えてくれます (Grails のような比較的新しいフレームワークがこの作業を自動的に進めてくれます)。次に行う作業は、ドメインとデータ・ストアとの間のマッピングですが、Google App Engine の場合、この作業はこれ以上ないほど簡単です。
「Java 開発 2.0: Google App Engine のための Gaelyk」の記事で、Google で使用するデータ・ストアを容易に操作できるようにする Groovy ベースのフレームワーク、Gaelyk を紹介しました。その記事の大半で、Google の Entity オブジェクトを使用する方法に焦点を当てました。以下の例 (Gaelyk の記事から抜粋) では、Gaelyk ではオブジェクト・エンティティーがどのように使われるのかを示します。
リスト 1. Entity によるオブジェクトの永続化
def ticket = new Entity("ticket")
ticket.officer = params.officer
ticket.license = params.plate
ticket.issuseDate = offensedate
ticket.location = params.location
ticket.notes = params.notes
ticket.offense = params.offense
|
この方法では問題なくオブジェクトを永続化させられますが、例えば ticket エンティティーをさまざまなサーブレットで作成 (または検索) するなどして多用するとしたら、いかに面倒な作業になるかは目に見えています。この作業を共通サーブレット (Groovlet) に処理させれば、負担の一部は取り除かれます。しかしもっと自然な方法は、これから説明するように、Ticket オブジェクトをモデル化することです。
Gaelyk を紹介する記事で使用した駐車違反切符のサンプルを作り変える代わりに、今回は新たにマラソンをテーマとして、この記事の手法のデモンストレーションを行う新しいアプリケーションを作成することにします。
図 1 の多対多の関係を示す図のとおり、1 つのレース (Race) には複数のランナー (Runner) が参加し、各ランナーは多数のレースに参加することができます。
図 1. レース (Race) とランナー (Runner)
リレーショナル・テーブルを使ってこの関係を設計するとしたら、少なくとも 3 つのテーブルが必要になります。そのうちの 1 つは、多対多の関係を結び付ける結合テーブルです。私はリレーショナル・データ・モデルに束縛されていなくて良かったと思います。Gaelyk (そして Groovy コード) を使えば、この多対多の関係を、Google App Engine に対応した Google の Bigtable 抽象化にマッピングすることができます。そして Gaelyk では Entity を Map のように処理できるため、このプロセスは至って簡単です。
スキーマレスなデータ・ストアならではの利点の 1 つは、事前にすべてを把握している必要がないことです。つまり、リレーショナル・データベースのスキーマと比べ、変更に対して遥かに簡単に適応することができます (スキーマは変更できないと言っているわけではありません。単に、スキーマがないほうが変更にはずっと適応しやすいと言っているだけです)。このアプリケーションでは、ドメイン・オブジェクトのプロパティーをあらかじめ定義することはせず、Groovy の動的性質を利用してプロパティーを定義します (こうすることによって、実質的にドメイン・オブジェクトを Google の Entity オブジェクトのプロキシーにすることができます)。プロパティーを定義する作業に時間を費やす分、オブジェクトをどのように検出し、どのように関係を処理するかを考えることにします。この部分については、NoSQL やスキーマレスなデータ・ストアを利用する各種フレームワークにはまだ組み込まれていないからです。
まずは、Entity オブジェクトのインスタンスを保持する基底クラスを作成するところから取り掛かります。次に、この基底クラスのサブクラスが、Groovy の便利な setProperty メソッドによって、対応する Entity インスタンスに追加される動的プロパティーを持てるようにします。setProperty は、実際にはオブジェクトに存在しないすべてのプロパティー・セッターに対して呼び出されます (奇妙に聞こえるかもしれませんが、心配ありません。実際の動作を見れば、納得するはずです)。
リスト 2 に、サンプル・アプリケーションの Model インスタンスに対する私の最初の試みを記載します。
リスト 2. 単純な Model 基底クラス
package com.b50.nosql
import com.google.appengine.api.datastore.DatastoreServiceFactory
import com.google.appengine.api.datastore.Entity
abstract class Model {
def entity
static def datastore = DatastoreServiceFactory.datastoreService
public Model(){
super()
}
public Model(params){
this.@entity = new Entity(this.getClass().simpleName)
params.each{ key, val ->
this.setProperty key, val
}
}
def getProperty(String name) {
if(name.equals("id")){
return entity.key.id
}else{
return entity."${name}"
}
}
void setProperty(String name, value) {
entity."${name}" = value
}
def save(){
this.entity.save()
}
}
|
この抽象クラスがプロパティーの Map を引数に取るコンストラクターを定義している方法に注目してください。このように定義すれば、後からいつでもコンストラクターを追加することができる上に (この後、実際にコンストラクターを追加します)、フォームから送信されるパラメーターに基づいて動作する Web フレームワークにはかなり重宝します。Gaelyk と Grails はこのようなパラメーターを params というオブジェクトに簡潔にラップします。コンストラクターはこの Map を繰り返し処理し、キーと値のペアごとに setProperty メソッドを呼び出します。
setProperty メソッドを見てみると、キーは entity のプロパティー名に設定され、これに対応する値が entity の値となっています。
前述のとおり、Groovy の動的性質により、getProperty メソッドおよび setProperty メソッドを使って存在しないプロパティーに対するメソッド呼び出しをすることが可能です。したがって、リスト 2 の Model サブクラスはプロパティーを定義する必要はなく、プロパティーに対する呼び出しをすべて、entity オブジェクトに委任します。
リスト 2 のコードには、他にも説明しておく価値のある Groovy 独特の仕組みが盛り込まれています。その 1 つは、プロパティーの先頭に @ を追加することで、プロパティーのアクセサー・メソッドを省略できることです。コンストラクター内で entity オブジェクトを参照する場合は、setProperty メソッドを呼び出さなくても済むように、この裏技を使用する必要があります。コンストラクター内で setProperty を呼び出すと、setProperty メソッドの entity 変数が null になり、明らかにパターンが壊れてしまうためです。
2 つ目は、コンストラクターで this.getClass().simpleName を呼び出すと、entity の「Kind (種類)」が設定されることです。つまり、この simpleName プロパティーが、パッケージ・プレフィックスが付かないサブクラスの名前になります (simpleName を呼び出すと、は実際には getSimpleName が呼び出されますが、対応する JavaBeans 風のメソッド呼び出しを使わずにプロパティーにアクセスしようとしても、Groovy はそれを許してくれます)。
そしてもう 1 つの仕組みとして、id プロパティー (オブジェクトのキー) に対して呼び出しを行うと、getProperty メソッドは賢くも、key にその id を尋ねます。Google App Engineでは、entities の key プロパティーは自動的に生成されるためです。
リスト 3 を見てのとおり、Race サブクラスは簡単に定義することができます。
リスト 3. Race サブクラス
package com.b50.nosql
class Race extends Model {
public Race(params){
super(params)
}
}
|
サブクラスがパラメーターのリスト (キーと値のペアが含まれる Map) でインスタンス化されると、対応する entity がメモリー内に作成されます。これを永続化するには、save メソッドを呼び出すだけでよいのです。
リスト 4. Race インスタンスを作成して GAE のデータ・ストアに保管するためのコード
import com.b50.nosql.Runner
def iparams = [:]
def formatter = new SimpleDateFormat("MM/dd/yyyy")
def rdate = formatter.parse("04/17/2010")
iparams["name"] = "Charlottesville Marathon"
iparams["date"] = rdate
iparams["distance"] = 26.2 as double
def race = new Race(iparams)
race.save()
|
リスト 4 は Groovlet です。このコードでは、Map (別名 iparams) をレースの名前、日付、そして距離という 3 つのプロパティーを使用して作成します (Groovy では、空の Map は [:] によって作成されることに注意してください)。Race の新しいインスタンスを作成した後、続いて save メソッドを使ってデータ・ストアにこのインスタンスを保管します。
Google App Engine コンソールでデータ・ストアを調べると、データが実際に保管されているかどうかを確かめることができます (図 2 を参照)。
図 2. 新しく作成されたレースの表示
Entity は保管されたので、今度はこれを取得できるようにすると便利です。そこで次に追加できるのが、「ファインダー (検索)」メソッドです。このサンプル・アプリケーションの場合には、ファインダー・メソッドをクラス・メソッド (static) にして、Race を名前で検索できるようにします (つまり、name プロパティーを基準に検索します)。これ以外のプロパティーを基準としたファインダーは、後からいつでも追加することができます。
さらにファインダーには、ある 1 つの規約を採用します。それは、名前に all という単語が含まれていないファインダーには 1 つのインスタンスを検出させるという規約です。all という単語が含まれるファインダー (例えば findAllByName) であれば、インスタンスの Collection (つまり List) を返すことができます。リスト 5 に、findByName ファインダーを記載します。
リスト 5. Entity の名前を検索基準にした単純なファインダー
static def findByName(name){
def query = new Query(Race.class.simpleName)
query.addFilter("name", Query.FilterOperator.EQUAL, name)
def preparedQuery = this.datastore.prepare(query)
if(preparedQuery.countEntities() > 1){
return new Race(preparedQuery.asList(withLimit(1))[0])
}else{
return new Race(preparedQuery.asSingleEntity())
}
}
|
この単純なファインダーは Google App Engine の Query および PreparedQuery 型を使用して、渡された名前と (完全に) 一致する名前を持ち、「Kind (種類)」が「race (レース)」となっているエンティティーを見つけます。この基準を満たす Race が複数ある場合には、ページネーション制限が 1 に設定されている (withLimit(1)) に従って、最初に一致した Race を返します。
対応する findAllByName も上記と同様ですが、一致件数をいくつ返すかを指定するパラメーターが追加されているところが異なります (リスト 6 を参照)。
リスト 6. 名前を基準にして一致する結果をすべて検索する場合
static def findAllByName(name, pagination=10){
def query = new Query(Race.class.getSimpleName())
query.addFilter("name", Query.FilterOperator.EQUAL, name)
def preparedQuery = this.datastore.prepare(query)
def entities = preparedQuery.asList(withLimit(pagination as int))
return entities.collect { new Race(it as Entity) }
}
|
前に定義したファインダーと同じく、findAllByName は名前を基準に Race インスタンスを検索しますが、基準を満たすすべての Race を返します。ちなみに、Groovy の collect メソッドはかなり巧妙なメソッドで、Race インスタンスを作成するためのループを組み込むことができます。また、Groovy ではメソッド・パラメーターにデフォルトの値を使えることにも注目してください。したがって、2 番目の値を渡さなければ、pagination の値は 10 になるというわけです。
リスト 7. ファインダーの使い方
def nrace = Race.findByName("Charlottesville Marathon")
assert nrace.distance == 26.2
def races = Race.findAllByName("Charlottesville Marathon")
assert races.class == ArrayList.class
|
リスト 7 の 2 つのファインダーは期待通りに動作し、findByName は 1 つのインスタンスを返す一方、findAllByName は Collection を返します (複数の「Charlottesville Marathon」があるという前提)。
Race のインスタンスを難なく作成して検索できるようになったところで、次は俊足の Runner オブジェクトの作成に取り掛かります。このプロセスは最初の Race インスタンスを作成したときと同じく簡単で、ただ単に、Model を継承するだけのことです (リスト 8 を参照)。
リスト 8. とても簡単に作成できる Runner
package com.b50.nosql
class Runner extends Model{
public Runner(params){
super(params)
}
}
|
リスト 8 を見ると、ほとんど完成間近のように感じますが、まだランナーとレースを結び付けるという作業が残っています。ランナーとレースは当然、多対多の関係としてモデル化することになります。ランナーには是非とも複数のレースを走ってもらいたいからです。
Bigtable をベースとした Google App Engine の抽象化はオブジェクト指向の抽象化ではありません。つまり関係をそのまま保管することはできませんが、キーを共有することはできます。そこで、Race と Runner の関係をモデル化するために、Runner キーのリストと Race キーのリストを互いのインスタンスの中に保管します。
ただし、このキー共有メカニズムには多少のロジックを追加しなければなりません。最終的な API は自然なものにしたいからです。具体的に言うと、Race には Runner キーのリストではなく、Runner のリストを要求したいということですが、これは幸い難しいことではありません。
リスト 9 では、Race インスタンスに 2 つのメソッドを追加してあります。そのうちの 1 つ、addRunner メソッドに Runner インスタンスを渡すと、対応する id が entity の runners プロパティーに格納された id の Collection に追加されます。runners の Collection がすでに存在する場合は、新しい Runner インスタンスのキーがそこに追加され、そうでない場合は、新しく Collection が作成され、その Collection に Runner のキー (エンティティーの id プロパティー) が追加されます。
リスト 9. ランナーの追加および取得
def addRunner(runner){
if(this.@entity.runners){
this.@entity.runners < runner.id
}else{
this.@entity.runners = [runner.id]
}
}
def getRunners(){
return this.@entity.runners.collect {
new Runner( this.getEntity(Runner.class.simpleName, it) )
}
}
|
リスト 9 で追加したもう 1 つのメソッド、getRunners が呼び出されると、id のコレクションから Runner インスタンスのコレクションが作成されます。そのため、Model クラスには新しいメソッド (getEntity) を定義します (リスト 10 を参照)。
リスト 10. id からのエンティティーの作成
def getEntity(entityType, id){
def key = KeyFactory.createKey(entityType, id)
return this.@datastore.get(key)
}
|
getEntity メソッドは Google の KeyFactory クラスを使用してキーを作成します。このキーは、データ・ストア内の個々のエンティティーを検索する際に使用することができます。
最後に、エンティティーのタイプを受け入れる新しいコンストラクターを定義します (リスト 11 を参照)。
リスト 11. 新しく追加したコンストラクター
public Model(Entity entity){
this.@entity = entity
}
|
リスト 9、10、11、そして図 1 のオブジェクト・モデルを見るとわかるように、任意の Race に Runner を追加することも、任意の Race から Runner インスタンスの一覧を取得することもできます。リスト 12 では、これと同じような関係を今度は Race 側ではなく Runner 側に作成します。リスト 12 に、Runner クラスに作成した新しいメソッドを示します。
リスト 12. ランナーと参加レース
def addRace(race){
if(this.@entity.races){
this.@entity.races < race.id
}else{
this.@entity.races = [race.id]
}
}
def getRaces(){
return this.@entity.races.collect {
new Race( this.getEntity(Race.class.simpleName, it) )
}
}
|
以上のように、スキーマレスなデータ・ストアでの 2 つのドメイン・オブジェクトのモデル化はどうにか完了しました。
残る作業は、Runner インスタンスを作成して Race に追加するだけです。図 1 のオブジェクト・モデルのように双方向の関係にするには、同様にして Race インスタンスを Runner に追加します (リスト 13 を参照)。
リスト 13. ランナーとその参加レース
def runner = new Runner([fname:"Chris", lname:"Smith", date:34]) runner.save() race.addRunner(runner) race.save() runner.addRace(race) runner.save() |
新しい Runner を Race に追加し、Race の save メソッドを呼び出すと、図 3 のスクリーン・ショットに示すように、データ・ストアは更新されて ID のリストが追加されているはずです。
図 3. レースに参加するランナーの新規プロパティーの表示
Google App Engine でデータをよく調べてみると、Race エンティティーに Runner の list が追加されていることがわかります (図 4 を参照)。
図 4. ランナーの新規リストの表示
同様に、新しく作成した Runner インスタンスに Race を追加する前は、このプロパティーは表示されていません (図 5 を参照)。
図 5. 参加するレースが表示されていないランナー
けれども Race を Runner に関連付けると、データ・ストアには Race の id の list が新らに追加されます。
図 6. 参加するレースが表示されるようになったランナー
スキーマレスなデータ・ストアの柔軟性には、このように目を見張るものがあります。プロパティーは要求に応じてデータ・ストアに自動的に追加されるので、開発者としての私は、スキーマを更新したり、変更したりする必要もなければ、スキーマをデプロイする必要さえありません。
当然のことながら、スキーマレスなデータ・モデリングには利点と欠点があります。レースへの参加アプリケーションの利点の 1 つは、その際立った柔軟性です。新しいプロパティー (SSN など) を Runner に追加することにしたとしても、必要な作業はそれほどありません。実際、コンストラクターのパラメーターにプロパティーを組み込めば、それで終わりです。SSN を使用して作成されていないインスタンスはどうなるかと言えば、何も問題はありません。これらのインスタンスのフィールドは null になるだけです。
その一方、効率性のために一貫性と完全性が犠牲にされていることは明らかです。このアプリケーションの現行のデータ・アーキテクチャーには一切の制約がありません。理論上は、同じオブジェクトのインスタンスをいくらでも作成することができます。Google App Engine のキー処理により、これらのインスタンスはいずれも一意のキーを持つことになりますが、キー以外はすべて同じです。さらに不都合なことに、カスケード削除というものは存在しないため、同じ手法で 1 対多の関係をモデル化した場合、親を削除すると、その子が親のないまま残されてしまうことになります。もちろん独自の完全性チェックを実装することもできますが、そこが鍵です。つまり、(他のすべてを自らの手で行っていたように) 自分で実装しなければなりません。
スキーマレスなデータ・ストアを使用するには規律が必要です。多種多様な Race を作成すると (例えば、名前があったりなかったり、あるいは使用するプロパティーが date であったり race_date であったりするなど)、自らの墓穴を掘ることに (そしてコードを使用する他の人にも迷惑をかけることに) なるだけです。
Google App Engine ではもちろん JDO と JPA を使用することができますが、そうは言っても、リレーショナル・モデルとスキーマレスなモデルの両方を複数のプロジェクトで使用した経験のある私が言えることは、Gaelyk の下位レベルの API が最も柔軟であり、最も楽しく作業できるということです。さらに、Gaelyk を使用した場合には、概して Bigtable とスキーマレスなデータ・ストアに関する理解がより深まるという利点もあります。
流行は一時的なものであり、時には流行を無視するのが無難な場合もあります (ワードローブがレジャー・スーツで溢れている一個人からの賢明な助言です)。しかし、NoSQL は流行というよりは、極めてスケーラブルな Web アプリケーションを開発するための新たな基盤といった感があります。NoSQL データベースが将来 RDBMS に置き換わることはありませんが、補完することにはなると思います。リレーショナル・データベースをベースとした数えきれないほどのツールとフレームワークが成功を収めているなか、RDBMS 自体の影が薄くなる可能性は考えられません。
NoSQL データベースの役割は突き詰めるところ、オブジェクト・リレーショナル・データ・モデルに代わる、時代に合った手段を提示することにあります。NoSQL データベースは、リレーショナル・データベース以外の手段も考えられること、そして極めて切迫した特定の状況ではリレーショナル・データベース以外の手段の方が有効なことを私たちに見せてくれています。スキーマレス・データベースは、データの取得速度とスケーラビリティーを必要とするマルチノード Web アプリケーションには最も適しています。そして素晴らしい副次効果として、開発者にリレーショナルの観点からではなく、ドメイン指向の観点からデータ・モデリングに取り組むことを教えてくれます。
学ぶために
- 「Java 開発 2.0」: この developerWorks 連載では Java 開発全体を定義し直している技術とツールを探っています。これまで、Gaelyk (2009年12月)、Google App Engine (2009年8月)、CouchDB (2009年11月) などを取り上げました。
- 「NoSQL Patterns」(Ricky Ho 著、Pragmatic Programming Techniques、2009年11月): NoSQL データベースの概要と具体的な例を紹介した後、NoSQL データ・ストアの共通アーキテクチャーを詳細に検討しています。
- 「Saying Yes to NoSQL; Going Steady with Cassandra」(John Quinn 著、Digg Blogs、2010年3月): Digg のエンジニアリング担当バイス・プレジデントが、MySQL から Cassandra に切り替えることにした理由を説明しています。
- 「Sharding with Max Ross」(JavaWorld podcast、2008年7月): Andrew Glover が Google の Max Ross と、sharding の手法および Hibernate Shardsの開発について対談しています。
- 「Is the Relational Database Doomed?」(Tony Bain 著、ReadWriteEnterprise、2009年2月): 非リレーショナル・データがクラウドの内外に登場しているなか、1 つのメッセージが明らかになってきています。「大規模なオンデマンドのスケーラビリティーを求めるのなら、非リレーショナル・データベースが必要です」。
- 「Google App Engine for Java: 第 3 回 永続化とリレーションシップ」(Richard Hightower 著、developerWorks、2009年8月): Rick Hightower が、Google App Engine の現状の Java 永続化フレームワークが抱える問題について説明し、その回避方法のいくつかを解説しています。
- 「Amazon Web サービスを利用したクラウド・コンピューティング: 第 5 回 SimpleDB によるクラウド内でのデータセット処理」(Prabhakar Chaganti 著、developerWorks、2009年10月): Amazon SimpleDB の概念を学び、SimpleDB とのインターフェースに使われるオープンソースの Python ライブラリー boto に用意された関数について調べてください。
- 「Bigtable: A Distributed Storage System for Structured Data」(Fay Chang 他による共著、Google、2006年11年): Bigtable は構造化データを管理するための分散ストレージ・システムです。極めて大規模に拡張するように設計された Bigtable は、何千もの商品サーバーにまたがる数ペタバイトのデータに対応できます。
- 「The Vietnam of Computer Science」(Ted Neward 著、2006年6月): オブジェクトとリレーショナル・モデルのマッピングに関連する問題を取り上げています。
- Technology bookstore で、この記事で取り上げた技術やその他の技術に関する本を探してください。
- developerWorks Java technology ゾーン: Java プログラミングのあらゆる側面を網羅した記事が豊富に用意されています。
製品や技術を入手するために
- Gaelyk: Google App Engine のための軽量な Groovy アプリケーション開発フレームワークを使い始めてください。
議論するために
- My developerWorks コミュニティーに加わってください。

Andrew Glover は、ビヘイビア駆動開発、継続的インテグレーション、アジャイル・ソフトウェア開発に情熱を持つ開発者であるとともに、著者、講演者、起業家でもあります。詳細は彼のブログにアクセスしてください。