摘要:近日,在中,介紹了一個基于新時代架構(gòu)的實(shí)踐,和。是的一個現(xiàn)代替代者,在九十年代末,曾是應(yīng)用程序的主流構(gòu)建方式。指定了對象的作用范圍,而服務(wù)器則會通過結(jié)果代碼和有效的進(jìn)行響應(yīng)。代表創(chuàng)建讀取更新和刪除。下圖顯示了使用后的請求和響應(yīng)流。
摘要:90 年代,LAMP 曾風(fēng)靡一時,然而隨著需求的變遷和數(shù)據(jù)流量的激增,LAMP 已不可避免的走下神壇。近日,在 MongoDB Blog 中,Dana Groce 介紹了一個基于新時代架構(gòu)的實(shí)踐 MEAN ,下面一起走進(jìn)。
【編者按】在九十年代,Linux+Apache+Mysql+PHP 架構(gòu)曾風(fēng)靡一時,直到現(xiàn)在仍然是眾多 Web 應(yīng)用程序的基本架構(gòu)。然而隨著需求的變遷和數(shù)據(jù)流量的激增,LAMP 已不可避免的走下神壇。近日,在 MongoDB Blog 中,Dana Groce 介紹了一個基于新時代架構(gòu)的實(shí)踐 —— MEAN,MongoDB/Mongoose.js、Express.js、Angular.js 和 Node.js 。
以下為譯文
本系列博客的兩篇文章主要關(guān)注 MEAN 技術(shù)堆棧的使用 —— MongoDB/Mongoose.js 、Express.js、Angular.js 和 Node.js 。這些技術(shù)都使用了 JavaScript 以獲取更高的軟件性能和開發(fā)者生產(chǎn)效率。
第一篇博文主要描述應(yīng)用程序的基本結(jié)構(gòu)和進(jìn)行數(shù)據(jù)建模過程,而第二篇則會創(chuàng)建測試來驗(yàn)證應(yīng)用程序行為,然后介紹如何設(shè)置并運(yùn)行應(yīng)用程序。
本系列博文閱讀并不需求擁有這些技術(shù)的實(shí)踐經(jīng)驗(yàn),所有技能等級的開發(fā)人員都可以從中獲益。如果在這之前你沒有使用過 MongoDB、JavaScript 或建立一個 REST API 的經(jīng)驗(yàn),不用擔(dān)心,這里將用足夠的細(xì)節(jié)介紹這些主題,包括身份驗(yàn)證、在多文件中構(gòu)建代碼、編寫測試用例等。首先,從 MEAN stack 的定義開始。
什么是 MEAN StackMEAN stack 可概括為:
M = MongoDB/Mongoose.js 。流行的數(shù)據(jù)庫,對 node . js 來說是一個優(yōu)雅的 ODM 。
E = Express.js :一個輕量級 Web 應(yīng)用程序框架。
A = Angular.js :一個健壯的框架用于創(chuàng)建 HTML5 和 JavaScript-rich Web 應(yīng)用程序。
N = Node.js 服務(wù)器端 JavaScript interpreter 。
MEAN stack 是 LAMP (Linux、Apache、MySQL,PHP / Python) stack 的一個現(xiàn)代替代者,在九十年代末,LAMP 曾是 Web 應(yīng)用程序的主流構(gòu)建方式。
在這個應(yīng)用程序中并不會使用 Angular.js ,因?yàn)檫@里并不是要構(gòu)建一個 HTML 用戶界面。相反,這里創(chuàng)建的是一個沒有用戶界面的 REST API,但它卻可以作為任何界面的基礎(chǔ),如一個網(wǎng)站、一個 Android 應(yīng)用程序,或者一個 iOS 應(yīng)用程序。也可以說我們正在 ME(a)N stack 上構(gòu)建 REST API ,但這不是重點(diǎn)!
REST API 是什么?REST 代表 Representational State Transfer,是 SOAP 和 WSDL XML-based API 協(xié)議的一個更輕量級替代方案。
REST 使用客戶端-服務(wù)器模型,服務(wù)器是一個 HTTP 服務(wù)器,而客戶端發(fā)送 HTTP 行為(GET、POST、PUT、DELETE),以及 URL 編碼的變量參數(shù)和一個 URL 。URL 指定了對象的作用范圍,而服務(wù)器則會通過結(jié)果代碼和有效的 JavaScript Object Notation (JSON) 進(jìn)行響應(yīng)。
因?yàn)榉?wù)器用 JSON 回復(fù),MongoDB 與 JSON 又可以很好地交互,同時所有組件都使用了 JavaScript,因此 MEAN stack 非常適合本用例中的應(yīng)用程序。在進(jìn)入開始定義數(shù)據(jù)模型后,你會看到一些 JSON 的例子。
CRUD 縮略詞常被用來描述數(shù)據(jù)庫操作。CRUD 代表創(chuàng)建、讀取、更新和刪除。這些數(shù)據(jù)庫操作能很好地映射到 HTTP 動作:
POST:客戶想要插入或創(chuàng)建一個對象。
GET:客戶端想要讀取一個對象。
PUT:客戶想要更新一個對象。
DELETE:客戶想要刪除一個對象。
在定義 API 后,這些操作將變得更加直觀。REST APIs 中通常會使用的一些常見 HTTP 結(jié)果代碼如下:
200 ——“OK”。
201 ——“Created”(和POST一起使用)。
400 ——“Bad Request”(可能丟失所需參數(shù))。
401 ——“Unauthorized”(身份驗(yàn)證參數(shù)丟失)。
403 ——“Forbidden”(已驗(yàn)證,但是權(quán)限不夠)。
404 ——“Not Found”。
RFC 文檔中可以找到一個完整的描述,這個在本博客末尾的參考資料中列出。上面這些結(jié)果代碼都會在本應(yīng)用程序中使用,隨后就會展示一些例子。
為什么從 REST API 開始?
部署一個 REST API 可以為建立任何類型應(yīng)用程序打下基礎(chǔ)。如前文所述,這些應(yīng)用程序可能會基于網(wǎng)絡(luò)或者專門針對某些平臺設(shè)計(jì),比如 Android 或者 iOS 。
時下,已經(jīng)有許多公司在建立應(yīng)用程序時不再使用 HTTP 或者 Web 接口,比如 Uber、WhatsApp、Postmates 和 Wash.io 。從一個簡單的應(yīng)用程序發(fā)展成一個強(qiáng)大的平臺,REST API 可以大幅度簡化這個過程中其他接口和應(yīng)用程序的實(shí)現(xiàn)。
建立 REST API這里會建立一個 RSS Aggregator,類似Google Reader,應(yīng)用程序主要會包含兩個組件:
REST API
Feed Grabber(類似 Google Reader)
本系列博文都將聚焦這個 REST API 的打造,不會去關(guān)注 RSS feeds 的復(fù)雜性?,F(xiàn)在,F(xiàn)eed Grabber 的代碼已經(jīng)可以在 github repository 中發(fā)現(xiàn),詳情可以見博文列出的資源。下面將介紹打造這個 API 所需的步驟。首先會根據(jù)具體需求來定義數(shù)據(jù)模型:
在用戶賬戶中儲存用戶信息
跟蹤需要被監(jiān)視的RSS feeds
將feed記錄pull到數(shù)據(jù)庫
跟蹤用戶feed訂閱
跟蹤用戶會閱讀哪個訂閱的feed
用戶則需要可以完成下列操作:
建立一個賬戶
到feed的訂閱或者退訂
閱讀feed記錄
標(biāo)記feed/記錄的閱讀狀態(tài)(已讀/未讀)
數(shù)據(jù)建模這里不會深入討論 MongoDB 中的數(shù)據(jù)建模,詳細(xì)資料可以在博文后的列舉的資料中發(fā)現(xiàn)。本用例需要 4 個 collections 來管理這個信息:
Feed collection
Feed entry collection
User collection
User-feed-entry mapping collection
Feed Collection下面一起進(jìn)入一段代碼,F(xiàn)eed 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” }
如果你精通關(guān)系型數(shù)據(jù)庫技術(shù),那么你將了解數(shù)據(jù)庫、表格、列和行。在 MongoDB 中,大部分的關(guān)系型概念都可以映射。從高等級看,MongoDB 部署支持 1 個或者多個數(shù)據(jù)庫。1 個數(shù)據(jù)庫可能包含多個 collection,這個類似于傳統(tǒng)關(guān)系型數(shù)據(jù)庫中的表格。Collection 中會有多個 document,從高等級看,document 相當(dāng)于關(guān)系型數(shù)據(jù)庫中的行。這里需要注意的是,MongoDB 中的 document 并沒有預(yù)設(shè)的格式,取而代之,每個 document 中都可以有 1 個或者多個的鍵值對,這里的值可能是簡單的,比如日期,也可以是復(fù)雜的,比如 1 個地址對象數(shù)組。
上文的 JSON 文檔是一個 Eater Blog 的 RSS feed 示例,它會跟蹤紐約所有餐館信息。因此,這里可能存在許多字段,而用例中主要關(guān)注的則是 feed 中的 URL 以及 description 。描述是非常重要的,因此在建立一個移動應(yīng)用程序時,它會是 feed 一個很好的摘要。
JSON 中的其他字段用于內(nèi)部使用,其中非常重要的字段是 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 字段,這里使用的是數(shù)組,數(shù)據(jù)中同樣儲存了一個 document。MongoDB 允許通過這種方式嵌套使用 document,同時這個用法在許多場景中也是非常必要的,因?yàn)橛美枨髮⑿畔⒓写鎯Α?/p>
entryID 字段使用了 tag 格式來避免復(fù)制 feed 記錄。這里需要注意的是 feedID 和 ObjectId 的用法——值則是 Eater Blog document 的 id 。這提供了一個參考模型,類似關(guān)系型數(shù)據(jù)庫中的外鍵。因此,如果期望查看這個 ObjectId 關(guān)聯(lián)的 feed document,可以取值 523b1153a2aa6a3233a913f8,并在 id 上查詢 feed collection,從而就會返回 Eater Blog document。
User Collection這里有一個用戶需要使用的 document :
{ "_id" : ObjectId("54ad6c3ae764de42070b27b1"), "active" : true, "email" : "[email protected]", "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") ], }
用戶應(yīng)該有 email 地址、first name 和 last name。同樣,這里還存在 spapikeyid 和 spapikeysecret —— 在后續(xù)部分會結(jié)合 Stormpath(一個用戶管理 API )使用這兩個字段。最后一個字段 subs,是 1 個訂閱數(shù)組。subs 字段會標(biāo)明這個用戶訂閱了哪些 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 已經(jīng)讀取。在這里,使用一個布爾類型(true/false)來標(biāo)記已讀和未讀。
REST API 的一些功能需求如上文所述,用戶需要可以完成以下操作:
建立一個賬戶
到 feed 的訂閱或者退訂
閱讀 feed 記錄
標(biāo)記 feed / 記錄的閱讀狀態(tài)(已讀 / 未讀)
此外,用戶還需求可以重置密碼。下表表示了這些操作是如何映射到 HTTP 路由和動作。
在生產(chǎn)環(huán)境中,HTTP(HTTPS)安全需求使用一個標(biāo)準(zhǔn)的途徑來發(fā)送敏感信息,比如密碼。
在一個魯棒的現(xiàn)實(shí)世界應(yīng)用程序中,提供用戶身份驗(yàn)證不可避免。因此,這里需要一個安全的途徑來管理用戶、密碼和密碼重置。
在本用例中,可以使用多種方式進(jìn)行身份驗(yàn)證。其中一個就是使用 Node.js 搭配 Passport Plugin ,這個方式通常被用于社交媒體賬戶驗(yàn)證中,比如 Facebook 或者 Twitter 。然而,Stormpath 同樣是一個非常不錯的途徑。Stormpath 是一個用戶管理即服務(wù),支持身份驗(yàn)證和通過 API keys 授權(quán)。根本上,Stormpath 維護(hù)了一個用戶詳情和密碼數(shù)據(jù)庫,從而客戶端應(yīng)用程序 API 可以調(diào)用 Stormpath REST API 來進(jìn)行用戶身份驗(yàn)證。
下圖顯示了使用 Stormpath 后的請求和響應(yīng)流。
詳細(xì)來說,Stormpath 會為每個應(yīng)用程序提供一個安全秘鑰,通過它們的服務(wù)來定義。舉個例子,這里可以定義一個應(yīng)用程序作為「Reader Production」或者「Reader Test」。如果一直對應(yīng)用程序進(jìn)行開發(fā)和測試,定義這兩個應(yīng)用程序非常實(shí)用,因?yàn)樵黾雍蛣h除測試用戶會非常頻繁。在這里,Stormpath 同樣會提供一個 API Key Properties 文件。Stormpath 同樣允許基于應(yīng)用程序的需求來定義密碼屬性,比如:
不低于 8 個字符
必須包含大小寫
必須包含數(shù)字
必須包含 1 個非字母字符
Stormpath 會跟蹤所有用戶,并分配他們的 API keys(用于 REST API 身份驗(yàn)證),這將大幅度簡化應(yīng)用程序建立過程,因?yàn)檫@里不再需要為驗(yàn)證用戶編寫代碼。
Node.jsNode.js 是服務(wù)器端和網(wǎng)絡(luò)應(yīng)用程序的運(yùn)行時環(huán)境。Node.js 使用 JavaScript 并適合多種不同的平臺,比如 Linux、Microsoft Windows 和 Apple OS X。
Node.js 應(yīng)用程序需要通過多個庫模塊建立,當(dāng)下社區(qū)中已經(jīng)有了非常多的資源,后續(xù)應(yīng)用程序建立中也會使用到。
為了使用 Node.js,開發(fā)者需要定義 package.json 文件來描述應(yīng)用程序以及所有庫的依賴性。
Node.js Package Manager 會安裝所有庫的副本到應(yīng)用程序目錄的一個子目錄,也就是 node_modules/ 。這么做有一定的好處,因?yàn)檫@樣做可以隔離不同應(yīng)用程序的庫版本,同時也避免了所有庫都被統(tǒng)一安裝到標(biāo)準(zhǔn)目錄下造成的代碼復(fù)雜性,比如 /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" } }
應(yīng)用程序被命名為 reader-api,主文件被命名為 server.js,隨后會是一系列的依賴庫和它們的版本。這些庫其中的一些被設(shè)計(jì)用來解析 HTTP 查詢。在這里,我們會使用 frisby 作為測試工具,而 jasmine-node 則被用來運(yùn)行 frisby 腳本。
在這些庫中,async 尤為重要。如果你從未使用過 node.js,那么請注意 node.js 使用的是異步機(jī)制。因此,任何阻塞 input/output (I/O) 的操作(比如從 socket 中讀取或者 1 個數(shù)據(jù)庫查詢)都會采用一個回調(diào)函數(shù)作為最后的參數(shù),然后繼續(xù)控制流,只有在阻塞操作結(jié)束后才會繼續(xù)這個回調(diào)函數(shù)。下面看一個簡單的例子來理解這一點(diǎn)。
function foo() { someAsyncFunction(params, function(err, results) { console.log(“one”); }); console.log(“two”); }
在上面這個例子中,你想象中的輸出可能是:
one two
但實(shí)際情況的輸出是:
two one
造成這個結(jié)果的原因就是 Node.js 使用的異步機(jī)制,打印 「one」 的代碼可能會在后續(xù)的回調(diào)函數(shù)中執(zhí)行。之所以說可能,是因?yàn)檫@只在一定的情景下發(fā)生。這種異步編程帶來的不確定性被稱之為 non-deterministic execution 。對于許多編程任務(wù)來說,這么做可以獲得很高的性能,但是在順序性要求的場景則非常麻煩。而通過下面的用法則可以獲得一個理想中的順序:
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);總結(jié)
通過本篇文章,相信大家對 Node.js 和異步函數(shù)設(shè)置都有了一定的理解,因此下篇博文將會描述更深入層次的一些知識。取代開始建立應(yīng)用程序,這里會進(jìn)入建立測試以及驗(yàn)證應(yīng)用程序的行為。這種方式則被稱為 test-driven 開發(fā),它會帶來兩大好處:
首先,它會幫助開發(fā)者弄清數(shù)據(jù)和函數(shù)的消費(fèi)方式,同時也可以幫助弄清一些奇怪的需求,比如數(shù)組中會儲存多個對象。
通過在建立應(yīng)用程序之前編寫測試,模型會從「assumed to be working until a test fails」轉(zhuǎn)換成「broken / unimplemented until proven tested OK」。對于建立一個更健壯的應(yīng)用程序來說,前者顯然更安全些。
未完待續(xù)。
原文鏈接:Building your first application with MongoDB: Creating a REST API using the MEAN Stack - Part 1
本文系 OneAPM 工程師編譯整理。想閱讀更多技術(shù)文章,請?jiān)L問 OneAPM 官方博客。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/18765.html
摘要:下一步,關(guān)閉數(shù)據(jù)庫。出于多個原因,這個文件不應(yīng)該被置入源代碼控制。主中間件被稱為路由器,它會監(jiān)聽,并將動作傳遞到個指定的處理函數(shù)。當(dāng)索引不存在于數(shù)據(jù)庫中時,將會負(fù)責(zé)索引的建立。唯一性約束保障將去除重復(fù)出現(xiàn)的可能。 在本系列文章的第一部分旨在介紹一些應(yīng)用程序的基礎(chǔ)技術(shù)細(xì)節(jié)和如何進(jìn)行數(shù)據(jù)建模,而這個部分文章將著手建立驗(yàn)證應(yīng)用程序行為的測試,并會指出如何啟動和運(yùn)行應(yīng)用程序。 首先,編寫測試 ...
閱讀 1686·2021-11-23 09:51
閱讀 2696·2021-11-22 09:34
閱讀 1330·2021-10-14 09:43
閱讀 3672·2021-09-08 09:36
閱讀 3217·2019-08-30 12:57
閱讀 2039·2019-08-30 12:44
閱讀 2528·2019-08-29 17:15
閱讀 3024·2019-08-29 16:08