2012年9月2日 星期日

夢想偉大,但步伐短小的 DBHouse

Standard
數個月前開始做一個計劃『AppHouse』,實作如 Google App Engine(GAE) 般的 PaaS,其志在打造自己的 Node.js 雲端軟體平台。然後發現,除了讓雲端服務可以在平台上跑起來外,資料庫管理也必需有個便於使用的機制和規劃,仔細想想,一個沒有資料庫配合的雲端服務,可沒有什麼太大的價值,於是,『DBHouse』便應運而生。

你可以在 github 上找到這個專案:
https://github.com/Mandice/node-dbhouse

DBHouse 起初的開發目的,是讓使用 AppHouse 架設以及開發自己雲端服務的人,可以很容易存取資料庫。此外,對我們而言更便於管理資料庫資源,面對許多不同的服務,不需要特別為他們開設資料庫權限,亦或是買許多硬體和主機,建立起許多 VM 並做各種安全性規劃。其實,如果把 DBHouse 的用途,想像成 Google 在做的事,就很容易明白:『在 GAE 上你可以使用統一的 Database APIs,存取 Google 提供的資料庫系統(BigTable)。』,同理,我們也是在做同樣的事。

只不過,學 Google 開發自己的一套資料庫太過於困難,不是一個可以達成的目標,所以我們仍然選用 MongoDB 當做 PaaS 的資料庫底層。僅管資料庫不是自己開發的,我們還是可以提供統一的 API,讓開發者存取。統一的 API 有個好處,若能做到當開發者在使用的時候,不需要知道自己在使用什麼資料庫,日後就可以在這 API 之後串接或替換不同的資料庫系統,有很大的彈性可以擴充。

當然,更遠大的目標是希望在一個 Table(Collection) 內,因應不同的欄位需求,而交由不同資料庫處理,更進一步發揮不同資料庫的特色。但是,這夢想遠大,技術上也有很多盲點待討論,所以能不能實現那是另外一回事,至少,短期內在我們的能力範圍和經濟狀況下,暫時無法達成這一步。

雖然 DBHouse 有這樣的初衷和夢一般的計劃,但不代表 DBHouse 一定得和 AppHouse 配合使用,更準確的說,他們本來就是獨立各自發展的專案,各自可獨立運作。說穿了,DBHouse 本身就只是一個 Database API,你可以在 Node.js 裡使用 DBHouse API 去操作自己的 MongoDB(目前只有支援 MongoDB,其他驅動還待開發),也提供了一套 ORM 供開發者使用。

目前 DBHouse 的特色:
  • 支援簡易的 ORM (包括:多層資料結構)
  • 支援 MongoDB v1.6+
  • 已實作驅動層以便支援其他資料庫
  • 盡可能貼近 SQL 語法習慣(如:select、insert、update、delete、replace)
  • 以 Mongo Query Language 為主要查詢語言
  • 根據 ORM 設定,自動轉換欄位類型(如:讀寫 MongoDB 時,自動轉換 BinObject)
    ex, 讀出 UUID 時轉成字串,以便 JavaScript 操作或網頁間傳遞,存入或查詢時,自動轉成 MongoDB 的 BinObject

目前仍缺少的功能:
  • Cursor
  • 索引(Index)管理
  • 完整的文件

使用 NPM 安裝:
npm install dbhouse

展示如何使用 DBHouse 新增一筆資料:
var DBHouse = require('dbhouse');

var dbHouse = new DBHouse;

/* Define schema */
var Address = new DBHouse.Schema({
        company: { type: 'String' },
        home: { type: 'String' },
        updated_time: { type: 'Date' }
});

var Contact = new DBHouse.Schema({
        _id: { type: 'UUID' },
        name: { type: 'String' },
        email: { type: 'String' },
        tel: { type: 'String' },
        created: { type: 'Date' },
        address: { type: 'Schema', schema: Address }
});

/* Create connection with database server */
dbHouse.connect('mongodb', { host: 'localhost', port: 27017 }, function() {

        /* Create a database operator */
        var db = new DBHouse.Database(dbHouse);
        db.open('dbhouse')
                .collection('contact')
                .model(Contact)
                .insert({
                        name: 'Fred Chien',
                        email: 'cfsghost@gmail.com',
                        tel: '0926123456',
                        created: new Date().getTime(),
                        address: {
                                company: 'Taipei',
                                home: 'Taiwan',
                                updated_time: new Date().getTime()
                        }
                }, function(err, data) {
                        if (err)
                                throw err;

                        console.log(data);
                });
});
註:『collection()』可使用『table()』替換,兩者功能一樣,只是為了同時符合 SQL 和 NoSQL 的習慣。

如果只是想做簡單的資料庫操作,也可以不使用 ORM 機制:
var DBHouse = require('dbhouse');

/* Create connection with database server */
var dbHouse = new DBHouse;
dbHouse.connect('mongodb', { host: 'localhost', port: 27017 }, function() {

        /* Create a database operator */
        var db = new DBHouse.Database(dbHouse);
        db.open('dbhouse')
                .collection('contact')
                .select({
                        email: 0
                })
                .where({
                        '$or': [ { name: 'Fred Chien'}, { name: 'Stacy Li' } ]
                })
                .limit(1)
                .query(function(err, data) {
                        if (err)
                                throw err;

                        console.log(data);
                });
});

如果你使用過 MongoDB ,也熟悉傳統 SQL 資料庫,你一定會發現兩者對於 update 的定義相當不一樣,因為在 MongoDB 中預設方法是直接取代掉整筆資料,而傳統 SQL 中只是修改欄位而已。但是在 DBHouse 中,對 update 的處理是採用 SQL 的動作,也就是修改欄位,若是要整筆取代,則另外使用 replace()。

修改 tel 欄位的資料:
var DBHouse = require('dbhouse');

/* Create connection with database server */
var dbHouse = new DBHouse;
dbHouse.connect('mongodb', { host: 'localhost', port: 27017 }, function() {

        /* Create a database operator */
        var db = new DBHouse.Database(dbHouse);
        db.open('dbhouse')
                .collection('contact')
                .where({
                        name: 'Fred Chien'
                })
                .update({ tel: '0926333572' }, function(err) {
                        if (err)
                                throw err;
                });
});

既然 Mongo Query Language 是特色,當然可以吃特殊命令,以下程式將把 reviewed 欄位值加一:
var DBHouse = require('dbhouse');

/* Create connection with database server */
var dbHouse = new DBHouse;
dbHouse.connect('mongodb', { host: 'localhost', port: 27017 }, function() {

        /* Create a database operator */
        var db = new DBHouse.Database(dbHouse);
        db.open('dbhouse')
                .collection('contact')
                .where({
                        name: 'Fred Chien'
                })
                .update({ $inc: { reviewed: 1 } }, function(err) {
                        if (err)
                                throw err;
                });
});

後記

大致上來說,DBHouse 已經足夠使用,但真要挑惕的話,還是有很多細節功能待補齊。而目前開發模式,是依照我們手邊專案所需,為其添加新功能,如果遲遲沒有加入您想要的功能,還請見諒。

此外,如果有人喜歡 DBHouse,歡迎投入開發並提交 Patch。:-)