2012年1月6日 星期五

NodeJS 與 MongoDB 的邂逅

Standard
雖然 NodeJS 的模組和開發資源相當多,但相關文件卻非常不足或是不完整,多半文獻都只著重於基礎的使用和片斷的說明,如果不去看原始程式碼,使用 NodeJS 來完成實用的網站,會有很大的困難度。對於已經有過 Web 開發經驗的人,轉換使用 NodeJS 不免也需要花一番功夫,過去經驗中許多的常用的功能,都仍要一一花大量時間嘗試才得以解決。而這樣的情況,對於開發者來說相當的糟,也是很多人重新再評估是否使用 NodeJS 的重點因素之一。有道是『一人得道,雞犬升天』,因此筆者未來將嘗試將自己的實際經驗,寫成一篇篇重點功能實作的文章和隨 Copy 即用的範例,減少其他人浪費同樣的時間再摸索。

開發一個 Web 應用程式,最重要的莫過於資料庫的使用,過去 PHP 有 MySQL 當最佳夥伴,而現在 NodeJS 有 MongoDB 做最佳的組合。MongoDB 是 NoSQL 的代表之一,其採用 JSON/BSON 當做資料儲存和溝通的格式,亦使用 JavaScript 做為 Server-side 的執行程序語言(相當於傳統 RDBMS 的預儲程序),一切設計和習慣與 NodeJS 搭配使用起來,簡直絕配。若你對 MongoDB 的一些基本操作有疑問,可以先參考舊文『MongoDB 快速筆記』。


使用 MongoDB

MongoDB 擁有 NoSQL 的普遍特色,不用預先定義 Schema,所有的 database 和 collection(相當於傳統 RDBMS 的 Table),都會在新增資料後,自動被建立,我們只要專注於使用 NodeJS 操作資料庫即可。

要在 NodeJS 裡使用 MongoDB,可以安裝 mongodb native driver,若透過 npm 來安裝:
npm install mongodb

然後可以使用 NodeJS 建立 MongoDB connection pool ,做一些基礎的操作:
var mongodb = require('mongodb');

var mongodbServer = new mongodb.Server('localhost', 27017, { auto_reconnect: true, poolSize: 10 });
var db = new mongodb.Db('mydb', mongodbServer);

/* open db */
db.open(function() {
    /* Select 'contact' collection */
    db.collection('contact', function(err, collection) {
        /* Insert a data */
        collection.insert({
            name: 'Fred Chien',
            email: 'cfsghost@gmail.com',
            tel: [
                '0926xxx5xx',
                '0912xx11xx'
            ]
        }, function(err, data) {
            if (data) {
                console.log('Successfully Insert');
            } else {
                console.log('Failed to Insert');
            }
        });

        /* Querying */
        collection.find({ name: 'Fred Chien' }, function(err, data) {
            /* Found this People */
            if (data) {
                console.log('Name: ' + data.name + ', email: ' + data.email);
            } else {
                console.log('Cannot found');
            }
        });
    });
});
註:這是 MongoDB 的基本常識,每當新增一筆資料,MongoDB 會自動幫該筆資料加上 _id 欄位,並給與一個唯一值(格式是 ObjectId),所以我們不需要像過去 使用 SQL Server 一般,自己刻意去定義一個 ID 欄位。


預設的 ObjectId 範圍太小,改用 UUID 來當資料的唯一 ID

如果你過去有過 Web 開發經驗,到這邊肯定會開始有一些疑問欲求解,第一個問題肯定是『ObjectId 的數量極限?』。筆者在此不會回答這問題,因為這答案並不重要,想要準確知道答案,可以去『mongodb.org』尋找答案。

比起上述問題,相信你應該更想問:
預設的 ObjectId 適用的範圍?
如果日後資料庫要擴展(Scale),是否有其他的 ID 解決方案可使用?

一般情況, MongoDB 預設的 ObjectId 就相當夠用了,但如果你是要建構大型的 Web Service 或是保留未來的擴充性,可使用 UUID 去代替 ObjectId。不過,因為 MongoDB 本身並不生成 UUID,若是要使用 UUID,就必需先自行產生好 UUID,然後在新增資料時指定生成好的 UUID 給 _id 欄位,讓 MongoDB 改用我們給的 ID 而不使用預設生成的 ObjectId。

因為要自行產生 UUID,必需先為 NodeJS 安裝模組 node-uuid:
npm install uuid

然後生成 UUID 並在 insert 時使用:
var uuid = require('node-uuid');
var mongodb = require('mongodb');

var mongodbServer = new mongodb.Server('localhost', 27017, { auto_reconnect: true, poolSize: 10 });
var db = new mongodb.Db('mydb', mongodbServer);

/* open db */
db.open(function() {
    /* Select 'contact' collection */
    db.collection('contact', function(err, collection) {

        /* Generate UUID(16 Bytes) and convert to BinaryData object for mongodb */
        var uuidBinary = new Buffer(uuid.v1({}, []));
        var id = mongodb.BSONPure.Binary(uuidBinary, mongodb.BSONPure.Binary.SUBTYPE_UUID);

        /* Insert a data with uuid */
        collection.insert({
            _id: id,
            name: 'Fred Chien',
            email: 'cfsghost@gmail.com'
        }, function(err, data) {
            if (data) {
                console.log('Successfully Insert');
            } else {
                console.log('Failed to Insert');
            }
        });
    });
});

你可能會發現,在上面的範例程式中,我們將 UUID 轉成 MongoDB BSON 的 BinaryData 格式,這是為了效能考量,因為用純字串當做 Unique ID,在資料庫搜尋上會比 BinaryData Object 慢很多。


儲存時間戳(Timestamp)

關於儲存時間的問題,如果你去各大 MongoDB 討論區詢問或查詢,通常大家都會告訴你不必做這件事,因為每一筆資料被建立後,自動產生的 ObjectId 就包含了建立的時間訊息,我們只要去學習如何從中去解析時間即可。但是,不單只是建立時間,有時我們會為資料加上各種不同的時間戳,如:更新時間等,所以,儲存時間戳還是必要的。

雖然網路上相關 NodeJS 範例並不多,但 MongoDB 確實有 Timestamp 的資料結構可以用,我們可以這樣使用:
var uuid = require('node-uuid');
var mongodb = require('mongodb');

var mongodbServer = new mongodb.Server('localhost', 27017, { auto_reconnect: true, poolSize: 10 });
var db = new mongodb.Db('mydb', mongodbServer);

/* open db */
db.open(function() {
    /* Select 'contact' collection */
    db.collection('contact', function(err, collection) {

        /* Generate Timestamp and convert for mongodb */
        var ts = new Date().getTime();
        var i = ts % 1000;
        var t = new mongodb.BSONPure.Timestamp(i, Math.floor(ts * 0.001));

        /* Insert a data with uuid */
        collection.insert({
            name: 'Fred Chien',
            email: 'cfsghost@gmail.com',
            created: t
        }, function(err, data) {
            if (data) {
                console.log('Successfully Insert');
            } else {
                console.log('Failed to Insert');
            }
        });
    });
});


建立資料庫索引(Index)

過去有接觸過資料庫的人應該都很清楚,索引(Index)是能優化資料查詢速度的重要功能,MongoDB 同樣也有索引的設計。

可以在 NodeJS 中,這樣為 name 欄位加上索引:
collection.createIndex({ name: 1 });


後記

到此為止,你應該能開始盡情使用 NodeJS + MongoDB 開發 Web Service 了,開始享受完全的 JavaScript 開發人生吧!