“参赛者” 对象没有太多不同
现在我已能够创建并找到 Race的实例,现在可以创建一个快速的 Runner对象了。这个过程与创建初始的 Race实例一样简单,只需如清单 8 所示扩展 Model:
清单 8. 创建一个参赛者很简单
class Runner extends Model{
public Runner(params){
super(params)
}
}
看看 清单 8,我感觉自己几乎完成工作了。但是,我还需创建参赛者和比赛之间的链接。当然,我将把它建模为一个 “多对多” 关系,因为我希望我的参赛者可以参加多项比赛。
没有架构的域建模
Google App Engine 在 Bigtable 上面的抽象不是一个面向对象的抽象;即,我不能原样存储关系,但可以共享键。因此,为建模多个 Race和多个 Runner之间的关系,我将在每个 Race实例中存储一列 Runner键,并在每个 Runner实例中存储一列 Race键。
我必须对我的键共享机制添加一点逻辑,但是,因为我希望生成的 API 比较自然 —我不想询问一个 Race以获取一列 Runner键,因此我想要一列 Runner。幸运的是,这并不难实现。
在清单 9 中,我已经添加了两个方法到 Race实例。但一个 Runner实例被传递到 addRunner方法时,它的对应 id被添加到底层 entity的 runners属性中驻留的 id的 Collection。如果有一个现成的 runners的 collection,则新的 Runner实例键将添加到它;否则,将创建一个新的 Collection,且这个 Runner的键(实体上的 id属性)将添加到它。
清单 9. 添加并检索参赛者
class Runner extends Model{
public Runner(params){
super(params)
}
}
当清单 9 中的 getRunners方法调用时,一个 Runner实例集合将从底层的 id集合创建。这样,一个新方法(getEntity)将在 Model类中创建,如清单 10 所示:
清单 10. 从一个 id 创建一个实体
class Runner extends Model{
public Runner(params){
super(params)
}
}
getEntity方法使用 Google 的 KeyFactory类来创建底层键,它可以用于查找数据存储中的一个单独实体。


最后,定义一个新的构造函数来接受一个实体类型,如清单 11 所示:
清单 11. 一个新添加的构造函数
this.@entity = entity
}
如清单 9、10和 11、以及 图 1的对象模型所示,我可以将一个 Runner添加到任一 Race,也可以从任一 Race获取一列 Runner实例。在清单 12 中,我在这个等式的 Runner方上创建了一个类似的联系。清单 12 展示了 Runner类的新方法。
清单 12. 参赛者及其比赛
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) )
}
}
这样,我就使用一个无模式数据存储创建了两个域对象。
通过一些参赛者完成这个比赛
此前我所做的是创建一个 Runner实例并将其添加到一个 Race。如果我希望这个关系是双向的,如 图 1中我的对象模型所示,那么我也可以添加一些 Race实例到一些 Runner,如清单 13 所示:
清单 13. 参加多个比赛的多个参赛者
runner.save()
race.addRunner(runner)
race.save()
runner.addRace(race)
runner.save()
将一个新的 Runner添加到 race并添加对 Race的 save的调用后,这个数据存储已使用一列 ID 更新,如图 3 中的屏幕快照所示:
图 3. 查看一项比赛中的多个参赛者的新属性

通过仔细检查 Google App Engine 中的数据,可以看到,一个 Race实体现在拥有了一个 Runners 的 list,如图 4 所示。
图 4. 查看新的参赛者列表

同样,在将一个 Race添加到一个新创建的 Runner实例之前,这个属性并不存在,如图 5 所示。
图 5. 一个没有比赛的参赛者

但是,将一个 Race关联到一个 Runner后,数据存储将添加新的 races ids 的 list。
图 6. 一个参加比赛的参赛者

无模式数据存储的灵活性正在刷新 —属性按照需要自动添加到底层存储。作为开发人员,我无须更新或更改架构,更谈不上部署架构了!