2010/10/28

Google App Engine - Data Store

GAE在Java的Data Store上,是透過Java Data Object (JDO)的方式,另外也有支援Java Persistent API (JPA)。在實驗的過程也是第一次使用JDO的方法來model資料,這種方式的database好像比較接近object-oriented database,跟傳統上的relational database不太一樣。

這次的實驗是想要model一個directed graph,也就是傳統的有向圖,希望之後能在上面做traversal的動作。

假設每個節點都有一個ID,一開始的想法是從relational database schema出發,很自然的是用每一個row代表一個邊,所以每個邊有兩個column (fromID, toID),而primary key是一個ordered composite key,即fromID + toID。不過因為沒有簡單的方法可以指定composite key,所以key的建構變得有點不直接,得由使用者來產生。
@PersistenceCapable
public class Edge {

    @Primary
    @Persistent
    private String key;   // invariant: fromID + toID

    @Persistent
    private String fromID;

    @Persistent
    private String toID;

}
後來有看到有KeyBuilder可以來建立Entity Group的Key,不過不知道是否有可以建立composite key的方法。
後來發現其實persistent property可以是一個collection,所以很直覺的有了下面第二種modeling。就是把同樣起點的edge都存在同一個object中。
@PersistenceCapable
public class Edge {

    @Primary
    @Persistent
    private String key;      // only for fromID

    @Persistent
    private String fromID;

    @Persistent
    private LinkedList<String> toIDs;

}
繼續閱讀tutorial後發現這樣會是一個owned one-to-many relationship,這麼一來如果每個node還有其他的properties,就會被迫被存很多次,一旦需要更新或是修改都會是大工夫。主要的原因就是node並不是只能被一個edge own,可能很多edge都連到同一個點。

所以其實需要的關係是一種unowned one-to-many relationship。實作的方法則是在collection內不直接擺放node,而是要擺放key當reference用就好了。

@PersistenceCapable
public class Edge {

    @Primary
    @Persistent
    private String key;    // only for fromID

    @Persistent
    private String fromID;

    @Persistent
    private LinkedList<Key> toIDs;

}

當需要traverse這個graph的時候,要先拿出toIDs的Key,再去query對應的node出來。
在tutorial中也提到了各式各樣的relationship和entity group的概念,特別是entity group的概念會影響到資料儲存的位置與transaction執行的方式,有空再來詳細了解一下。

2010/10/23

HTML5 Web Storage

最近參加了一個HTML5的讀書會,所以有空的時候就想玩玩新的API,順便瞭解一下未來的web application可以怎麼設計。

為了更佳支援Rich Internet Application(RIA),Web Storage API也被含蓋在HTML5的標準中。到目前為止,大部分的web client都是利用cookie的方式來跟web server溝通,不過在實作上,在每一次client & server的傳輸,cookie都會被來來回回的跟著被傳輸,而且在HTTP傳輸的過程中都是沒有加密的,也增加被竊聽或修改的風險。另外一個擾人的限制則是一個cookie的size不能超過4 KB,所以比較大型的content都不能直接放在裡面,可能必須透過利用ID來當reference的方法。有了Web Storage,很多cookie的限制都可以獲得一定程度的抒解。

在Web Storage API中,總共支援兩種storage:

  • sessionStorage
    儲存在sessionStorage裡的data只能被同一個browser window或是tab操作,當那個window或是tab被關掉後,data也會跟著消失。
  • localStorage
    儲存在localStorage裡的data則可以在不同的browser window或是tab裡操作,只要是在同一個origin下即可,然後即使browser被關閉,data也一直能存在,待下次再使用。
API的設計也相當簡單,兩種storage都直接在window object之下,要判斷瀏覽器有沒有支援該功能,只要判斷window.sessionStoragewindow.localStorage有沒有被define即可。

在storage中data都被存成key/value pair的格式,要加入data只需要(以sessionStorage為例):
window.sessionStorage.setItem('my_key','my_value');  //或是
window.sessionStorage.my_key = 'my_value';
讀取data則是
window.sessionStorage.getItem('my_key');   //或是
window.sessionStorage.my_key;

兩個storage都有以下的interface:

interface Storage {
  readonly attribute unsigned long length;
  getter DOMString key(in unsigned long index);
  getter any getItem(in DOMString key);
  setter creator void setItem(in DOMString key, in any data);
  deleter void removeItem(in DOMString key);
  void clear();
};

在cookie的時代可能發生所謂的data leak,也就是可能一個使用者同時開好幾個tab操作同origin下的網頁,因為cookie是by origin的,所以這些tabs都會共用同一個cookie,很有可能某的tab改的結果存在cookie裡,另外一個tab會不小心拿去用,即使畫面還是停留在比較早的選項,容易造成像訂錯票的窘境。在使用web storage的時候,雖然一樣的origin還是共用一份storage,只要把不同功能的key訂成不同的名字,讓app跟server溝通時用對的key/value pair,就可以解決這個問題。

雖然web storage可以存比較多東西,不過每個瀏覽器還是有個size的限制,一旦超過size,據說現在每家瀏覽器的行為還不太一致,有些可能會自動加大限制,有些則是直接丟出exception QUOTA_EXCEEDED_ERR。在測試了Chrome的sessionStorage後,如果只有一個key,放入約2MB的data就會丟出這個error了。另外試過2048個key,就是1~2048,在每個配上1KB的data,也會丟出error。不知道是不是上限就大概是2MB。只是W3C的spec中是建議每個origin給5MB。改天來試試localStorage好了。

另外在用Google Chrome實驗的時候,發現storage裡只能儲存string類別,如果要儲存一個javascript object,並不能直接setItem進去,必須要先用JSON.stringify(object)變成JSON string才行,取出來的時候也要用JSON.parse(object)才會變回原來的javascript object。

另外,Web Storage也支援event,在有data變更的時候會產生storage event。詳請請參閱W3C document

除了上述的兩種key/value storage之外,有些browser也開始support indexed storage,也就是像透過SQL command存取的database。Implementation則是會採用sqlite並提供javascript API。這部分就等有空的時候再來玩玩看。