NodeJS 與 MongoDB 的邂逅

雖然 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 開發人生吧!

留言

  1. 版主有用過 CouchDB 嗎?個人兩套比較後覺得,CouchDB 與 Node.js 的 Cradle 配合也不錯。

    回覆刪除
  2. 抱歉,過去我沒有真做太多的 CouchDB 實測,所以沒有太多的評價。

    當初在決定使用哪套 Database 做為與 NodeJS 配合時,考慮的單純只是效能,並沒有考慮到容錯和同步的需求。

    CouchDB 採 MVCC 的架構,在分散式的情況下以及記錄零碎資料的使用場景下相當好用,所以我也在考慮在某些應用下使用他。如果,日後有什麼心得會再分享給大家。

    或是,如果可以,你也可以給一篇你的心得如何? :-)

    回覆刪除
  3. 效能與其他比較的話,MongoDB 官網與網路上有一些優缺點與適用場合的整理表。只是個人也不是很了解到底實際應用上的差別,所以想說問一下。

    而且其實我個人偏好 CouchDB 其時主要原因是 CouchDB 本身

    1. 純 JSON : BSON 的出現雖然是小改動,但我比較擔心這樣會不會代表又像自訂資料格式的風潮。像 XML 或 JSON 重要某方面而言就是因為它全部純文字,且格式開放使得各種應用都可以轉換共通(當然先不講故意使用複雜格式難以相通的情況)。

    較誇張的想像是會不會哪天每家 NoSQL 都來個自訂資料格式,到時候就慘了。

    2.(Really) No SQL + MapReduce:MongoDB 自訂了一套「類似」 SQL 的查詢方法。雖然這對於習慣 SQL 的開發者方便,但個人覺得像 CouchDB 那樣以 view + MapReduce 的作法將分佈式的特性更加發揮完全(或說我個人覺得如果都用到 NoSQL 的地步,就應該儘量發揮不同於傳統 RMDB,可以讓資料庫更加分佈式的特色)。

    3. RESTful + HTTP:MongoDB 在我嘗試的時候並沒有提供 RESTful 的介面,且採自定義的通訊協定。這相較於 CouchDB RESTful + 標準 HTTP 通訊協定,我覺得是很大的問題。當然 MongoDB 可以說自訂通訊協定是為了效率等等原因,但可能是我比較在乎保持開放與標準,且覺得犧牲這種開放介面換取效率等並不值得。

    不過我承認我也沒有很深入的了解與開發。而且如版主所說,或許 MongoDB 注重效能是一大大特色,因此我提出的特點對其使用客群而言並不重要。

    回覆刪除
  4. uuid安裝的套件名錯了

    npm install node-uuid

    回覆刪除

張貼留言

這個網誌中的熱門文章

有趣的邏輯問題:是誰在說謊

Web 技術中的 Session 是什麼?

淺談 USB 通訊架構之定義(一)

淺談 USB 通訊架構之定義(二)

Reverse SSH Tunnel 反向打洞實錄