[AppEngine] 實戰 Datastore (1)

其實這些都可以在官方文件及 Google IO 的 slides 裡找到,不過我就幫大家整理一下(並且用中文說 XD)。

Google App Engine 帶給大家一個新的資料儲存概念,就是 Datastore 這個東西,它跟一般的關聯式資料庫(RDB)的概念相差不少,從官方文件或是 Google 工程師的演講中都不斷強調要大家用新的思維去想這個資料儲存方式,而不要一直侷限於怎麼把 RDB 的那一套搬過來。

不過好在 Datastore API 在描述資料的 Model 或 Expando 的資料欄位上提供了不少好用的資料型別(data type),所以一些常用的關聯還是很容易可以完成。這篇就介紹幾個常用的關聯,在 Datastore 上要怎麼下手。

One-to-One & One-to-Many

通常會用到這個關聯,就是 A model 中有個 column 的值可能是一個串列,在 RDB 中我們可能就會再建立一個 B model,然後在 B 中設下一個 A_id 的 column 用來指向 A 的某個 row,但是在 Datastore 中沒有 join 運算,所以得換個方法來做。

假設我在建立一個通訊錄的 Model,一個人可能會有很多 E-Mail,我的 model (在 Datastore 中)就可以寫成這樣:


class User(db.Model):
name = db.StringProperty(required=True)

class Email(db.Model):
user = db.ReferenceProperty(User, collection_name='emails')
addr = db.EmailProperty(required=True)
mail_type = db.StringProperty()

在 Email model 中,用了一個 Reference 型別的欄位,用來表示這個欄位是 reference 到 User 這個 model,並且在 User 下建立一個集合,名為 emails,用來表示所有的 references。

所以我在建立 Entity 的時候就可以這樣做:


ericsk = User(name='Lin-Chieh Shangkuan')
ericsk.put()

mail1 = Email(user=ericsk,
addr='foo@bar.to',
type='example')
mail1.put()

mail2 = Email(user=ericsk,
addr='bar@foo.to',
type='blog')
mail2.put()

這樣建立之後,要列出一個 user 的所有 email 也很簡單:


for email in ericsk.emails:
print 'type: %s, addr: %s' % (email.mail_type, email.addr)

因為我們在建立 Email model 時,有指定說要在 Reference 的 User 中建立一個 emails 欄位,直接對這個集合作 iteration 拿出所有的內容。這樣一個簡單的 One-to-Many 就算大功告成了。

而同時你拿到了一個 email entity 也可以使用 user 這個欄位關聯到對應的 user entity,這樣也很快就完成 One-to-One 的關聯。

Many-to-Many

Many-to-Many 常被出現在指定分類的應用中,舉例來說,我的部落格可能有很多分類(Category),每個分類下會有很多篇文章,而對一篇文章來說,它也可能屬於不同的分類下,這樣就產生了一個 Many-to-Many 的關聯。

廢話不多說,寫成 Datastore 的 Model 就可以寫成這樣:


class Post(db.Model):
title = db.StringProperty(required=True)
body = db.TextProperty(required=True)
post_at = db.DateTimeProperty(auto_now_add=True)
categories = db.ListProperty(db.Key)

class Category(db.Model):
name = db.StringProperty(required=True)
description = db.TextProperty()

@property
def posts(self):
return Post.gql('WHERE categories = :1', self.key())

在 Post model 中我建立了一個 categories 欄位,它是一個 List 型別,裡面的資料都是 db.Key 這個型別,每一個放進 Datastore 的 entity 都有一個唯一的 key,所以打算用這個 list 來表示某篇文章究竟有多少分類。而 Category model 中,我們自己寫了一個 posts 的 property,裡面只是要根據 key 來取出對應的 Posts。

這樣在建立 entity 的時候就可以寫成這樣:


cat_comp = Category(name='Computer Science')
cat_comp.put()
cat_prog = Category(name='Programming')
cat_prog.put()

post1 = Post(title='Blah', body='First Post')
post1.categories.append(cat_comp.key())
post1.categories.append(cat_prog.key())
post1.put()

這樣我的這篇文章便同時有 Computer Science 分類,也在 Programming 分類下。

所以我要從 Post 取得它所有分類時,就寫成這樣:


categories = db.get(post1.categories)
for cat in categories:
print 'Category: %s' % cat.name

而要看某個分類下有什麼文章則是寫成:


for post in cat_comp.posts.order('-post_at'):
print 'Title: %s' % post.title

這樣是不是也很簡單就達到 Many-to-Many 的效果了呢,只要好好想一下 Datastore 中 Reference 及 List 資料型態的用法就可以做不少關聯了喔。

參考文獻:

  • 你真的很強
    太棒了

  • 安打王

    Datastore真的是滿特別的資料儲存概念。
    從GDD2008聽Brett的簡報時就覺得Datastore會改變目前流行的資料儲存方式,只是將原本的關聯是資料庫完全改成Hash Table將會遇到很多程式撰寫上的問題。光是當天提的例子之一 – Paging 就必須花好大的工夫來處理。不過雖然花了很多腦力處理,但相信實際run起來的效能應該會比原本的模式來的好。

    btw
    平常常看你的blog,但第一次留言。
    看了你的GDD2008照片才發現,原來當天你就坐在我隔壁的隔壁,在lab那張還有拍到我的手呢…呵

  • @安打王
    不過開發人員的訓練還有好長的路要走啊…

    (下次歡迎來跟我打招呼啊~讓我知道有人在支持我寫 blog XD)

  • 範例介紹非常好,實際作業時,小弟淺見:
    1.datastore 規劃時儘量簡單,所以範例一,User,Email 合一,
    直接 name,adr,mail_type 即可,datastore 怕繁,不怕多
    2.範例二 Post ,Category 合一,Post內之categories 改為 db.StringListProperty(),省略 Category之description,
    datastore之List系列,功能非常強,搜尋時直接 categories=:1,cat即可
    3.當然,如果您的資料結構太複雜,就必須用一對多或多對多

  • 收下了, 蠻受用的; 晚點再來玩玩BigTable. 🙂

  • Pingback: (依主題重組):電腦技術領域 « eweibookmark()

  • 受益匪浅阿,继续看另2篇

  • Pingback: GAE 中 Datastore 的小陷阱 « 比爾工具箱()

  • 谢谢分享。

  • jiun

    我想請問ericsk
    關聯後的資料庫要怎麼去where Reference下的資料庫欄位

  • frank

    想請問一下,你所使用的關聯是python的吧
    那請問一下,如果是用java去開發的話,
    該怎麼撰寫程式呢,麻煩指點一下

  • hsuan

    ericsk您好,拜讀您的GAE開發實戰有一段時間了,近來因為GAE1.4.0有一Channel API 的新功能,試著讀了官方技術文件(os:英文實在是不好念阿)所以對它的函數使用不甚了解,如果ericsk方便的話,能不能為我們這些英文不好的人出個簡單教學呢?若有打擾之處,還請見諒
    PS.GAE真的是很棒的概念,希望能夠透過大家的力量推廣給國內更多的人