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 來安裝:
然後可以使用 NodeJS 建立 MongoDB connection pool ,做一些基礎的操作:
註:這是 MongoDB 的基本常識,每當新增一筆資料,MongoDB 會自動幫該筆資料加上 _id 欄位,並給與一個唯一值(格式是 ObjectId),所以我們不需要像過去 使用 SQL Server 一般,自己刻意去定義一個 ID 欄位。
預設的 ObjectId 範圍太小,改用 UUID 來當資料的唯一 ID
如果你過去有過 Web 開發經驗,到這邊肯定會開始有一些疑問欲求解,第一個問題肯定是『ObjectId 的數量極限?』。筆者在此不會回答這問題,因為這答案並不重要,想要準確知道答案,可以去『mongodb.org』尋找答案。
比起上述問題,相信你應該更想問:
一般情況, MongoDB 預設的 ObjectId 就相當夠用了,但如果你是要建構大型的 Web Service 或是保留未來的擴充性,可使用 UUID 去代替 ObjectId。不過,因為 MongoDB 本身並不生成 UUID,若是要使用 UUID,就必需先自行產生好 UUID,然後在新增資料時指定生成好的 UUID 給 _id 欄位,讓 MongoDB 改用我們給的 ID 而不使用預設生成的 ObjectId。
因為要自行產生 UUID,必需先為 NodeJS 安裝模組 node-uuid:
然後生成 UUID 並在 insert 時使用:
你可能會發現,在上面的範例程式中,我們將 UUID 轉成 MongoDB BSON 的 BinaryData 格式,這是為了效能考量,因為用純字串當做 Unique ID,在資料庫搜尋上會比 BinaryData Object 慢很多。
儲存時間戳(Timestamp)
關於儲存時間的問題,如果你去各大 MongoDB 討論區詢問或查詢,通常大家都會告訴你不必做這件事,因為每一筆資料被建立後,自動產生的 ObjectId 就包含了建立的時間訊息,我們只要去學習如何從中去解析時間即可。但是,不單只是建立時間,有時我們會為資料加上各種不同的時間戳,如:更新時間等,所以,儲存時間戳還是必要的。
雖然網路上相關 NodeJS 範例並不多,但 MongoDB 確實有 Timestamp 的資料結構可以用,我們可以這樣使用:
建立資料庫索引(Index)
過去有接觸過資料庫的人應該都很清楚,索引(Index)是能優化資料查詢速度的重要功能,MongoDB 同樣也有索引的設計。
可以在 NodeJS 中,這樣為 name 欄位加上索引:
後記
到此為止,你應該能開始盡情使用 NodeJS + MongoDB 開發 Web Service 了,開始享受完全的 JavaScript 開發人生吧!
開發一個 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'); } }); }); });
預設的 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 開發人生吧!
版主有用過 CouchDB 嗎?個人兩套比較後覺得,CouchDB 與 Node.js 的 Cradle 配合也不錯。
回覆刪除抱歉,過去我沒有真做太多的 CouchDB 實測,所以沒有太多的評價。
回覆刪除當初在決定使用哪套 Database 做為與 NodeJS 配合時,考慮的單純只是效能,並沒有考慮到容錯和同步的需求。
CouchDB 採 MVCC 的架構,在分散式的情況下以及記錄零碎資料的使用場景下相當好用,所以我也在考慮在某些應用下使用他。如果,日後有什麼心得會再分享給大家。
或是,如果可以,你也可以給一篇你的心得如何? :-)
效能與其他比較的話,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 注重效能是一大大特色,因此我提出的特點對其使用客群而言並不重要。
uuid安裝的套件名錯了
回覆刪除npm install node-uuid