说明:以下获得的设计思考,是阅读《GAE编程指南》时的一些简单体会。主要针对的是一些设计细节(非架构)的考量。
简单与灵活。在对API进行设计决策时,需充分考虑API的简单性和全面性。此二者都是站在调用者的角度来思考,即需要提供一种默认的简单调用形式,也可以根据实际情况提供可选的方式。这是简单性和灵活性的共同体现。在Google App Engine中,对数据存储区的实体存储,可以在创建实体对象时指定主键;如果不指定主键,就会生成唯一的ID。
如下Python代码:
|
如果需要生成键名,在调用构造函数时,应加上key_name参数:
|
我想,之所以需要考虑这种简单性和灵活性的结合,在于API的特征,这种开放平台会面临不同消费者,在不同场景下做出不同的选择。二者的平衡保证了API的调用能够得到最大程度的需求满足。
封装。在设计时,我们应尽量避免使用基本类型。Martin Fowler在《重构》一书中将其称为“基本类型偏执”。例如在GAE中,就对key进行了合理的封装,包含了实体的类型和值。如果希望批量获取多个实体,可以将key放到列表中,作为get()方法的参数,返回实体对象列表。这种批量获取实体的性能比多次执行单次获取要好,或许是因为它能够减少对数据库IO操作的次数。根据key获得实体对象的python代码如下所示:
|
在上面的代码中,db.key()方法相当于key的工厂方法,负责创建key。如果需要根据多个key值获得多个实体对象,则代码为:
|
比较python而言,Java代码略显复杂:
|
对于Java的GAE API而言,似乎还可以提供一个类似KeyCollection的对象,用于封装key列表,并为DatastoreService的get()提供接收KeyCollection对象的重载方法。KeyCollection可以封装一些key列表的行为。但这要根据具体的API设计而定。
GAE为查询还提供了get_by_id()与get_by_key_name()方法。为何提供这两个方法呢?我想,首先因为它们满足最常见的查询要求;同时,这是一种高层次的抽象,无论实体属于什么类型,无论业务有何差异,数据结构有何区别,都需要这两种查询方式。此时,还能够充分体会到封装key的好处,因为这种封装能够在一定程度上消除不同实体(尤其是遗留代码实体)在key之间的差异。
一致性。在GAE中,创建实体的API与更新实体的API完全一致。数据存储区不会区分创建和更新操作。若保存实体时用到完整的键(例如带有类别kind和键名),而数据存储区恰好有这个实体,就会替换原有的实体。为避免更新有效的数据,可以事先测试该实体是否存在,但这种方式在GAE中,需要使用事务API。
Fluence Interface。GAE提供的查询API在Java和Python两种语言下,有一些细微的差别。比较而言,Python采用了Fluence Interface模式,使得它的调用显得更为流畅和简洁。以下是Python代码: Liquid error: invalid byte sequence in US-ASCII
Java的代码就略显冗长而不够流畅,可读性较低。这一方面与语言自身的特色有关,但倘若运用了fluence interface模式,相信能够比较好地提高代码的可读性:
|
在使用PreparedQuery对象访问结果之前,查询其实是没有执行的。
根据我对上面这一段python代码的直观感觉,认为fetch()方法似乎是一种命令模式,之前的Query对象作为命令对象,由内部的Builder对象构建起内部的组成部分,这里是由filter()与order()方法来体现。因为没有阅读源代码,所以只能停留在猜测。我认为在Query类的内部,可能会定义一个Builder方法(或者定义一个内部的Builder类,如果要考虑扩展,甚至是一个Builder接口)。它可能会持有一个QueryStatement对象,作为Collection Parameter传递给Builder。这些Builder会修改QueryStatement对象,使其生成符合命令要求的SQL语句。如果Query是针对数据库的查询,则QueryStatement就可以是一个SqlStatement对象。若有时间,希望能阅读一下源代码,以此印证我的揣测是否正确。或许,有更好的解决方案?