摘要:90 年代,LAMP 曾風靡一時,然而隨着需求的變遷和數據流量的激增,LAMP 已不可避免的走下神壇。近日,在 MongoDB Blog 中,Dana Groce 介紹了一個基於新時代架構的實踐 MEAN ,下面一塊兒走進。php
**【編者按】**在九十年代,Linux+Apache+Mysql+PHP 架構曾風靡一時,直到如今仍然是衆多 Web 應用程序的基本架構。然而隨着需求的變遷和數據流量的激增,LAMP 已不可避免的走下神壇。近日,在 MongoDB Blog 中,Dana Groce 介紹了一個基於新時代架構的實踐 —— MEAN,MongoDB/Mongoose.js、Express.js、Angular.js 和 Node.js 。html
如下爲譯文node
本系列博客的兩篇文章主要關注 MEAN 技術堆棧的使用 —— MongoDB/Mongoose.js 、Express.js、Angular.js 和 Node.js 。這些技術都使用了 JavaScript 以獲取更高的軟件性能和開發者生產效率。git
第一篇博文主要描述應用程序的基本結構和進行數據建模過程,而第二篇則會建立測試來驗證應用程序行爲,而後介紹如何設置並運行應用程序。github
本系列博文閱讀並不需求擁有這些技術的實踐經驗,全部技能等級的開發人員均可以從中獲益。若是在這以前你沒有使用過 MongoDB、JavaScript 或創建一個 REST API 的經驗,不用擔憂,這裏將用足夠的細節介紹這些主題,包括身份驗證、在多文件中構建代碼、編寫測試用例等。首先,從 MEAN stack 的定義開始。 ##什麼是 MEAN Stacksql
MEAN stack 可歸納爲:mongodb
MEAN stack 是 LAMP (Linux、Apache、MySQL,PHP / Python) stack 的一個現代替代者,在九十年代末,LAMP 曾是 Web 應用程序的主流構建方式。數據庫
在這個應用程序中並不會使用 Angular.js ,由於這裏並非要構建一個 HTML 用戶界面。相反,這裏建立的是一個沒有用戶界面的 REST API,但它卻能夠做爲任何界面的基礎,如一個網站、一個 Android 應用程序,或者一個 iOS 應用程序。也能夠說咱們正在 ME(a)N stack 上構建 REST API ,但這不是重點! ##REST API 是什麼?express
REST 表明 Representational State Transfer,是 SOAP 和 WSDL XML-based API 協議的一個更輕量級替代方案。npm
REST 使用客戶端-服務器模型,服務器是一個 HTTP 服務器,而客戶端發送 HTTP 行爲(GET、POST、PUT、DELETE),以及 URL 編碼的變量參數和一個 URL 。URL 指定了對象的做用範圍,而服務器則會經過結果代碼和有效的 JavaScript Object Notation (JSON) 進行響應。
由於服務器用 JSON 回覆,MongoDB 與 JSON 又能夠很好地交互,同時全部組件都使用了 JavaScript,所以 MEAN stack 很是適合本用例中的應用程序。在進入開始定義數據模型後,你會看到一些 JSON 的例子。
CRUD 縮略詞常被用來描述數據庫操做。CRUD 表明建立、讀取、更新和刪除。這些數據庫操做能很好地映射到 HTTP 動做:
在定義 API 後,這些操做將變得更加直觀。REST APIs 中一般會使用的一些常見 HTTP 結果代碼以下:
RFC 文檔中能夠找到一個完整的描述,這個在本博客末尾的參考資料中列出。上面這些結果代碼都會在本應用程序中使用,隨後就會展現一些例子。 爲何從 REST API 開始?
部署一個 REST API 能夠爲創建任何類型應用程序打下基礎。如前文所述,這些應用程序可能會基於網絡或者專門針對某些平臺設計,好比 Android 或者 iOS 。
時下,已經有許多公司在創建應用程序時再也不使用 HTTP 或者 Web 接口,好比 Uber、WhatsApp、Postmates 和 Wash.io 。從一個簡單的應用程序發展成一個強大的平臺,REST API 能夠大幅度簡化這個過程當中其餘接口和應用程序的實現。 ##創建 REST API
這裏會創建一個 RSS Aggregator,相似Google Reader,應用程序主要會包含兩個組件:
本系列博文都將聚焦這個 REST API 的打造,不會去關注 RSS feeds 的複雜性。如今,Feed Grabber 的代碼已經能夠在 github repository 中發現,詳情能夠見博文列出的資源。下面將介紹打造這個 API 所需的步驟。首先會根據具體需求來定義數據模型:
用戶則須要能夠完成下列操做:
##數據建模
這裏不會深刻討論 MongoDB 中的數據建模,詳細資料能夠在博文後的列舉的資料中發現。本用例須要 4 個 collections 來管理這個信息:
##Feed Collection
下面一塊兒進入一段代碼,Feed Collection 的建模能夠經過下述 JSON 文檔完成:
{ "_id": ObjectId("523b1153a2aa6a3233a913f8"), "requiresAuthentication": false, "modifiedDate": ISODate("2014-08-29T17:40:22Z"), "permanentlyRemoved": false, "feedURL": "http://feeds.feedburner.com/eater/nyc", "title": "Eater NY", "bozoBitSet": false, "enabled": true, "etag": "4bL78iLSZud2iXd/vd10mYC32BE", "link": "http://ny.eater.com/", "permanentRedirectURL": null, "description": "The New York City Restaurant, Bar, and Nightlife Blog」 }
若是你精通關係型數據庫技術,那麼你將瞭解數據庫、表格、列和行。在 MongoDB 中,大部分的關係型概念均可以映射。從高等級看,MongoDB 部署支持 1 個或者多個數據庫。1 個數據庫可能包含多個 collection,這個相似於傳統關係型數據庫中的表格。Collection 中會有多個 document,從高等級看,document 至關於關係型數據庫中的行。這裏須要注意的是,MongoDB 中的 document 並無預設的格式,取而代之,每一個 document 中均可以有 1 個或者多個的鍵值對,這裏的值多是簡單的,好比日期,也能夠是複雜的,好比 1 個地址對象數組。
上文的 JSON 文檔是一個 Eater Blog 的 RSS feed 示例,它會跟蹤紐約全部餐館信息。所以,這裏可能存在許多字段,而用例中主要關注的則是 feed 中的 URL 以及 description 。描述是很是重要的,所以在創建一個移動應用程序時,它會是 feed 一個很好的摘要。
JSON 中的其餘字段用於內部使用,其中很是重要的字段是 _id 。在 MongoDB 中,每一個 document 都須要擁有一個 _id 字段。若是你創建一個沒有 —— id 的 document,MongoDB 將爲你自動添加。在 MongoDB 中,這個字段就是主鍵的存在,所以 MongoDB 會保證這個字段值在 collection 範圍惟一。 ##Feed Entry Collection
在 feed 以後,用例中還指望追蹤 feed 記錄。下面是一個 Feed Entry Collection 文檔示例:
{ "_id": ObjectId("523b1153a2aa6a3233a91412"), "description": "Buzzfeed asked a bunch of people...」, "title": "Cronut Mania: Buzzfeed asked a bunch of people...", "summary": "Buzzfeed asked a bunch of people that were...」, "content": [{ "base": "http://ny.eater.com/", "type": "text/html", "value": 」LOTS OF HTML HERE ", "language": "en" }], "entryID": "tag:ny.eater.com,2013://4.560508", "publishedDate": ISODate("2013-09-17T20:45:20Z"), "link": "http://ny.eater.com/archives/2013/09/cronut_mania_41 .php", "feedID": ObjectId("523b1153a2aa6a3233a913f8") }
再次提醒,這裏一樣必須擁有一個 _id 字段,同時也能夠看到 description、title 和 summary 字段。對於 content 字段,這裏使用的是數組,數據中一樣儲存了一個 document。MongoDB 容許經過這種方式嵌套使用 document,同時這個用法在許多場景中也是很是必要的,由於用例每每需求將信息集中存儲。
entryID 字段使用了 tag 格式來避免複製 feed 記錄。這裏須要注意的是 feedID 和 ObjectId 的用法——值則是 Eater Blog document 的 _id 。這提供了一個參考模型,相似關係型數據庫中的外鍵。所以,若是指望查看這個 ObjectId 關聯的 feed document,能夠取值 523b1153a2aa6a3233a913f8,並在 _id 上查詢 feed collection,從而就會返回 Eater Blog document。 ##User Collection
這裏有一個用戶須要使用的 document :
{ "_id" : ObjectId("54ad6c3ae764de42070b27b1"), "active" : true, "email" : "testuser1@example.com", "firstName" : "Test", "lastName" : "User1", "sp_api_key_id" : "6YQB0A8VXM0X8RVDPPLRHBI7J", "sp_api_key_secret" : "veBw/YFx56Dl0bbiVEpvbjF」, "lastLogin" : ISODate("2015-01-07T17:26:18.996Z"), "created" : ISODate("2015-01-07T17:26:18.995Z"), "subs" : [ ObjectId("523b1153a2aa6a3233a913f8"), ObjectId("54b563c3a50a190b50f4d63b") ], }
用戶應該有 email 地址、first name 和 last name。一樣,這裏還存在 sp_api_key_id 和 sp_api_key_secret —— 在後續部分會結合 Stormpath(一個用戶管理 API )使用這兩個字段。最後一個字段 subs,是 1 個訂閱數組。subs 字段會標明這個用戶訂閱了哪些 feeds。 ##User-Feed-Entry Mapping Collection
{ "_id" : ObjectId("523b2fcc054b1b8c579bdb82"), "read" : true, "user_id" : ObjectId("54ad6c3ae764de42070b27b1"), "feed_entry_id" : ObjectId("523b1153a2aa6a3233a91412"), "feed_id" : ObjectId("523b1153a2aa6a3233a913f8") }
最後一個 collection 容許映射用戶到 feeds,並跟蹤哪些 feeds 已經讀取。在這裏,使用一個布爾類型(true/false)來標記已讀和未讀。 ##REST API 的一些功能需求
如上文所述,用戶須要能夠完成如下操做:
此外,用戶還需求能夠重置密碼。下表表示了這些操做是如何映射到 HTTP 路由和動做。
在生產環境中,HTTP(HTTPS)安全需求使用一個標準的途徑來發送敏感信息,好比密碼。 ##經過 Stormpath 實現現實世界中的身份驗證
在一個魯棒的現實世界應用程序中,提供用戶身份驗證不可避免。所以,這裏須要一個安全的途徑來管理用戶、密碼和密碼重置。
在本用例中,可使用多種方式進行身份驗證。其中一個就是使用 Node.js 搭配 Passport Plugin ,這個方式一般被用於社交媒體帳戶驗證中,好比 Facebook 或者 Twitter 。然而,Stormpath 一樣是一個很是不錯的途徑。Stormpath 是一個用戶管理即服務,支持身份驗證和經過 API keys 受權。根本上,Stormpath 維護了一個用戶詳情和密碼數據庫,從而客戶端應用程序 API 能夠調用 Stormpath REST API 來進行用戶身份驗證。
下圖顯示了使用 Stormpath 後的請求和響應流。
詳細來講,Stormpath 會爲每一個應用程序提供一個安全祕鑰,經過它們的服務來定義。舉個例子,這裏能夠定義一個應用程序做爲「Reader Production」或者「Reader Test」。若是一直對應用程序進行開發和測試,定義這兩個應用程序很是實用,由於增長和刪除測試用戶會很是頻繁。在這裏,Stormpath 一樣會提供一個 API Key Properties 文件。Stormpath 一樣容許基於應用程序的需求來定義密碼屬性,好比:
Stormpath 會跟蹤全部用戶,並分配他們的 API keys(用於 REST API 身份驗證),這將大幅度簡化應用程序創建過程,由於這裏再也不須要爲驗證用戶編寫代碼。 ##Node.js
Node.js 是服務器端和網絡應用程序的運行時環境。Node.js 使用 JavaScript 並適合多種不一樣的平臺,好比 Linux、Microsoft Windows 和 Apple OS X。
Node.js 應用程序須要經過多個庫模塊創建,當下社區中已經有了很是多的資源,後續應用程序創建中也會使用到。
爲了使用 Node.js,開發者須要定義 package.json 文件來描述應用程序以及全部庫的依賴性。
Node.js Package Manager 會安裝全部庫的副本到應用程序目錄的一個子目錄,也就是 node_modules/ 。這麼作有必定的好處,由於這樣作能夠隔離不一樣應用程序的庫版本,同時也避免了全部庫都被統一安裝到標準目錄下形成的代碼複雜性,好比 /usr/lib。
命令 npm 會創建 node_modules/ 目錄,以及全部須要的庫。
下面是 package.json 文件下的 JavaScript:
{ "name": "reader-api", "main": "server.js", "dependencies": { "express" : "~4.10.0", "stormpath" : "~0.7.5", "express-stormpath" : "~0.5.9", "mongodb" : "~1.4.26」, "mongoose" : "~3.8.0", "body-parser" : "~1.10.0」, "method-override" : "~2.3.0", "morgan" : "~1.5.0」, "winston" : "~0.8.3」, "express-winston" : "~0.2.9", "validator" : "~3.27.0", "path" : "~0.4.9", "errorhandler" : "~1.3.0", "frisby" : "~0.8.3", "jasmine-node" : "~1.14.5", "async" : "~0.9.0" } }
應用程序被命名爲 reader-api,主文件被命名爲 server.js,隨後會是一系列的依賴庫和它們的版本。這些庫其中的一些被設計用來解析 HTTP 查詢。在這裏,咱們會使用 frisby 做爲測試工具,而 jasmine-node 則被用來運行 frisby 腳本。
在這些庫中,async 尤其重要。若是你從未使用過 node.js,那麼請注意 node.js 使用的是異步機制。所以,任何阻塞 input/output (I/O) 的操做(好比從 socket 中讀取或者 1 個數據庫查詢)都會採用一個回調函數做爲最後的參數,而後繼續控制流,只有在阻塞操做結束後纔會繼續這個回調函數。下面看一個簡單的例子來理解這一點。
function foo() { someAsyncFunction(params, function(err, results) { console.log(「one」); }); console.log(「two」); }
在上面這個例子中,你想象中的輸出多是:
one two
但實際狀況的輸出是:
two one
形成這個結果的緣由就是 Node.js 使用的異步機制,打印 「one」 的代碼可能會在後續的回調函數中執行。之因此說可能,是由於這隻在必定的情景下發生。這種異步編程帶來的不肯定性被稱之爲 non-deterministic execution 。對於許多編程任務來講,這麼作能夠得到很高的性能,可是在順序性要求的場景則很是麻煩。而經過下面的用法則能夠得到一個理想中的順序:
actionArray = [ function one(cb) { someAsyncFunction(params, function(err, results) { if (err) { cb(new Error(「There was an error」)); } console.log(「one」); cb(null); }); }, function two(cb) { console.log(「two」); cb(null); } ] async.series(actionArray);
##總結
經過本篇文章,相信你們對 Node.js 和異步函數設置都有了必定的理解,所以下篇博文將會描述更深刻層次的一些知識。取代開始創建應用程序,這裏會進入創建測試以及驗證應用程序的行爲。這種方式則被稱爲 test-driven 開發,它會帶來兩大好處:
首先,它會幫助開發者弄清數據和函數的消費方式,同時也能夠幫助弄清一些奇怪的需求,好比數組中會儲存多個對象。
經過在創建應用程序以前編寫測試,模型會從「assumed to be working until a test fails」轉換成「broken / unimplemented until proven tested OK」。對於創建一個更健壯的應用程序來講,前者顯然更安全些。
未完待續。
原文連接:Building your first application with MongoDB: Creating a REST API using the MEAN Stack - Part 1
本文系 OneAPM 工程師編譯整理。OneAPM 是應用性能管理領域的新興領軍企業,能幫助企業用戶和開發者輕鬆實現:緩慢的程序代碼和 SQL 語句的實時抓取。想閱讀更多技術文章,請訪問 OneAPM 官方博客。