摘要:為應(yīng)用增加新的特性和處理新的情況可能都會(huì)改變文件的結(jié)構(gòu)。寫一個(gè)模板的最佳實(shí)踐是,不要在模板中處理數(shù)據(jù)。在上面這四個(gè)文件夾中,主要的測(cè)試代碼將是單元測(cè)試,這意味著你需要將被測(cè)試的代碼與應(yīng)用分離開來。
前言
Node和Express并不嚴(yán)格要求它的應(yīng)用的文件結(jié)構(gòu)。你可以以任意的結(jié)構(gòu)來組織你的web應(yīng)用。這對(duì)于小應(yīng)用來說,通常是不錯(cuò)的,十分易于學(xué)習(xí)和實(shí)驗(yàn)。
但是,當(dāng)你的應(yīng)用在體積和復(fù)雜性上都變得越來越高時(shí),情況就變得復(fù)雜了。你的代碼可能會(huì)變得凌亂。當(dāng)你的團(tuán)隊(duì)人數(shù)增加時(shí),向在同一個(gè)代碼庫內(nèi)寫代碼變得愈發(fā)困難,每次合并代碼時(shí)都可能會(huì)出現(xiàn)各種各樣的沖突。為應(yīng)用增加新的特性和處理新的情況可能都會(huì)改變文件的結(jié)構(gòu)。
一個(gè)好的文件結(jié)構(gòu),應(yīng)該是每一個(gè)不同的文件或文件夾,都分別負(fù)責(zé)處理不同的任務(wù)。這樣,在添加新特性時(shí)才會(huì)變得不會(huì)有沖突。
最佳實(shí)踐這里所推薦的結(jié)構(gòu)是基于MVC設(shè)計(jì)模式的。這個(gè)模式在職責(zé)分離方面做得非常好,所以讓你的代碼更具有可維護(hù)性。在這里我們不會(huì)去過多地討論MVC的優(yōu)點(diǎn),而是更多地討論如果使用它來建立你的Express應(yīng)用的文件結(jié)構(gòu)。
例子:
讓我們來看下面這個(gè)例子。這是一個(gè)用戶可以登錄,注冊(cè),留下評(píng)論的應(yīng)用。以下是他的文件結(jié)構(gòu)。
project/ controllers/ comments.js index.js users.js helpers/ dates.js middlewares/ auth.js users.js models/ comment.js user.js public/ libs/ css/ img/ views/ comments/ comment.jade users/ index.jade tests/ controllers/ models/ comment.js middlewares/ integration/ ui/ .gitignore app.js package.json
這看上去可能有點(diǎn)復(fù)雜,但不要擔(dān)心。在讀完這篇文章之后,你將會(huì)完完全全地理解它。它本質(zhì)上是十分簡(jiǎn)單的。
以下是對(duì)這個(gè)應(yīng)用中的根文件(夾)的作用的簡(jiǎn)介:
controllers/ – 定義你應(yīng)用的路由和它們的邏輯
helpers/ – 可以被應(yīng)用的其他部分所共享的代碼和功能
middlewares/ – 處理請(qǐng)求的Express中間件
models/ – 代表了實(shí)現(xiàn)了業(yè)務(wù)邏輯的數(shù)據(jù)
public/ – 包含了如圖片,樣式,javascript這樣的靜態(tài)文件
views/ – 提供了供路由渲染的頁面模板
tests/ – 用于測(cè)試其他文件夾的代碼
app.js – 初始化你的應(yīng)用,并將所以部分聯(lián)接在一起
package.json – 記錄你的應(yīng)用的依賴庫以及它們的版本
需要提醒的是,除了文件的結(jié)構(gòu)本身,它們所代表的職責(zé)也是十分重要的。
Models你應(yīng)該在modules中處理與數(shù)據(jù)庫的交互,里面的文件包含了處理數(shù)據(jù)所有方法。它們不僅提供了對(duì)數(shù)據(jù)的增,刪,改,查方法,還提供了額外的業(yè)務(wù)邏輯。例如,如果你有一個(gè)汽車model,它也應(yīng)該包含一個(gè)mountTyres方法。
對(duì)于數(shù)據(jù)庫中的每一類數(shù)據(jù),你都至少應(yīng)該創(chuàng)建一個(gè)對(duì)應(yīng)的文件。對(duì)應(yīng)到我們的例子里,有用戶以及評(píng)論,所以我們至少要有一個(gè)user model和一個(gè)comment model。當(dāng)一個(gè)model變得過于臃腫時(shí),基于它的內(nèi)部邏輯將它拆分成多個(gè)不同的文件通常是一個(gè)更好的做法。
你應(yīng)該保持你的各個(gè)model之間保持相對(duì)獨(dú)立,它們應(yīng)相互不知道對(duì)方的存在,也不應(yīng)引用對(duì)方。它們也不需要知道controllers的存在,也永遠(yuǎn)不要接受HTTP請(qǐng)求和響應(yīng)對(duì)象,和返回HTTP錯(cuò)誤,但是,我們可以返回特定的model錯(cuò)誤。
這些都會(huì)使你的model變得更容易維護(hù)。由于它們之間相互沒有依賴,所以也容易進(jìn)行測(cè)試,對(duì)其中一個(gè)model進(jìn)行改變也不會(huì)影響到其他model。
以下是我們的評(píng)論model:
var db = require("../db") // Create new comment in your database and return its id exports.create = function(user, text, cb) { var comment = { user: user, text: text, date: new Date().toString() } db.save(comment, cb) } // Get a particular comment exports.get = function(id, cb) { db.fetch({id:id}, function(err, docs) { if (err) return cb(err) cb(null, docs[0]) }) } // Get all comments exports.all = function(cb) { db.fetch({}, cb) } // Get all comments by a particular user exports.allByUser = function(user, cb) { db.fetch({user: user}, cb) }
它并不引用用戶model。它僅僅需要一個(gè)提供用戶信息的user參數(shù),可能包含用戶ID或用戶名。評(píng)論model并不關(guān)心它到底是什么,它只關(guān)心這可以被存儲(chǔ)。
var db = require("../db") , crypto = require("crypto") hash = function(password) { return crypto.createHash("sha1").update(password).digest("base64") } exports.create = function(name, email, password, cb) { var user = { name: name, email: email, password: hash(password), } db.save(user, cb) } exports.get = function(id, cb) { db.fetch({id:id}, function(err, docs) { if (err) return cb(err) cb(null, docs[0]) }) } exports.authenticate = function(email, password) { db.fetch({email:email}, function(err, docs) { if (err) return cb(err) if (docs.length === 0) return cb() user = docs[0] if (user.password === hash(password)) { cb(null, docs[0]) } else { cb() } }) } exports.changePassword = function(id, password, cb) { db.update({id:id}, {password: hash(password)}, function(err, affected) { if (err) return cb(err) cb(null, affected > 0) }) }
除了創(chuàng)建和管理用戶的方法,用戶的model還應(yīng)該提供身份驗(yàn)證和密碼管理的方法。再次重申,這些model之間必須相互不知道對(duì)方的存在。
Views這個(gè)文件夾內(nèi)包含了所有你的應(yīng)用需要渲染的模板,通常都是由你團(tuán)隊(duì)內(nèi)的設(shè)計(jì)師設(shè)計(jì)的。
當(dāng)選擇模板語言時(shí),你可能會(huì)有些困難,因?yàn)楫?dāng)下可選擇的模板語言太多了。我最喜歡的兩個(gè)模板語言是Jade和Mustache。Jade非常擅于生成HTML,它相對(duì)于HTML更簡(jiǎn)短以及更可讀。它對(duì)JavaScript的條件和迭代語法也有強(qiáng)大的支持。Mustache則完全相反,它更專注于模板渲染,而不是很關(guān)心邏輯操作。
寫一個(gè)模板的最佳實(shí)踐是,不要在模板中處理數(shù)據(jù)。如果你需要在模板中展示處理后的數(shù)據(jù),你應(yīng)該在controller處理它們。同樣地,多余的邏輯操作也應(yīng)該被移到controller中。
doctype html html head title Your comment web app body h1 Welcome and leave your comment each comment in comments article.Comment .Comment-date= comment.date .Comment-text= comment.textControllers
在這個(gè)文件夾里的是所有你定義的路由。它們處理web請(qǐng)求,處理數(shù)據(jù),渲染模板,然后將其返回給用戶。它們是你的應(yīng)用中的其他部分的粘合劑。
通常情況下,你應(yīng)該至少為你應(yīng)用的每一個(gè)邏輯部分寫一個(gè)路由。例如,一個(gè)路由來處理評(píng)論,另一個(gè)路由來處理用戶,等等。同一類的路由最好有相同的前綴,如/comments/all,/comments/new。
有時(shí),什么代碼該寫進(jìn)controller,什么代碼該寫進(jìn)model是容易混淆的。最佳的實(shí)踐是,永遠(yuǎn)不要在controller里直接調(diào)用數(shù)據(jù)庫,應(yīng)該使用model提供方法來代替之。例如,如果你有一個(gè)汽車model,然后想要在某輛車上安上四個(gè)輪子,你不應(yīng)該直接調(diào)用db.update(id, {wheels: 4}),而是應(yīng)該調(diào)用類似car.mountWheels(id, 4)這樣的model方法。
以下是關(guān)于評(píng)論的controller代碼:
var express = require("express") , router = express.Router() , Comment = require("../models/comment") , auth = require("../middlewares/auth") router.post("/", auth, function(req, res) { user = req.user.id text = req.body.text Comment.create(user, text, function (err, comment) { res.redirect("/") }) }) router.get("/:id", function(req, res) { Comment.get(req.params.id, function (err, comment) { res.render("comments/comment", {comment: comment}) }) }) module.exports = router
通常在controller文件夾中,有一個(gè)index.js。它用來加載其他的controller,并且定義一些沒有常規(guī)前綴的路由,如首頁路由:
var express = require("express") , router = express.Router() , Comment = require("../models/comment") router.use("/comments", require("./comments")) router.use("/users", require("./users")) router.get("/", function(req, res) { Comments.all(function(err, comments) { res.render("index", {comments: comments}) }) }) module.exports = router
這個(gè)文件的router加載了你的所有路由。所以你的應(yīng)用在啟動(dòng)時(shí),只需要引用它既可。
Middlewares所有的Express中間件都會(huì)保存在這個(gè)文件夾中。中間件存在的目的,就是提取出一些controller中,處理請(qǐng)求和響應(yīng)對(duì)象的共有的代碼。
和controller一樣,一個(gè)middleware也不應(yīng)該直接調(diào)用數(shù)據(jù)庫方法。而應(yīng)使用model方法。
以下例子是middlewares/users.js,它用來在請(qǐng)求時(shí)加載用戶信息。
User = require("../models/user") module.exports = function(req, res, next) { if (req.session && req.session.user) { User.get(req.session.user, function(err, user) { if (user) { req.user = user } else { delete req.user delete req.session.user } next() }) } else { next() } }
這個(gè)middleware使用了用戶model,而不是直接操作數(shù)據(jù)庫。
下面,是一個(gè)身份認(rèn)證中間件。通過它來阻止未認(rèn)證的用戶進(jìn)入某些路由:
module.exports = function(req, res, next) { if (req.user) { next() } else { res.status(401).end() } }
它沒有任何外部依賴。如果你看了上文controller中的例子,你一定已經(jīng)明白它是如何運(yùn)作的。
Helpers這個(gè)文件夾中包含一些實(shí)用的工具方法,被用于model,middleware或controller中。通常對(duì)于不同的任務(wù),這些工具方法會(huì)保存在不同的文件中。
Public這個(gè)文件只用于存放靜態(tài)文件。通常是css, 圖片,JS庫(如jQuery)。提供靜態(tài)文件的最佳實(shí)踐是使用Nginx或Apache作為靜態(tài)文件服務(wù)器,在這方面,它們通常比Node更出色。
Tests所有的項(xiàng)目都需要有測(cè)試,你需要將所有的測(cè)試代碼放在一個(gè)地方。為了方便管理,你可能需要將這些測(cè)試代碼放于幾個(gè)不同的子文件夾中。
controllers
helpers
models
middleware
integration
ui
controllers,helpers,models和middlewares都十分清晰。這些文件夾里的文件都應(yīng)該與源被測(cè)試的文件夾中的文件一一對(duì)應(yīng),且名字相同。這樣更易于維護(hù)。
在上面這四個(gè)文件夾中,主要的測(cè)試代碼將是單元測(cè)試,這意味著你需要將被測(cè)試的代碼與應(yīng)用分離開來。但是,integration文件夾內(nèi)的代碼則主要用來測(cè)試你的各部分代碼是否被正確得粘合。例如,是否在正確的地方,調(diào)用了正確的中間件。這些代碼通常會(huì)比單元測(cè)試更慢一些。
ui文件夾內(nèi)包含了則是UI測(cè)試,它與integration文件夾內(nèi)的測(cè)試代碼相似,因?yàn)樗鼈兊哪繕?biāo)都是保證各個(gè)部分被正確地粘合。但是,UI測(cè)試通常運(yùn)行在瀏覽器上,并且還需要模擬用戶的操作。所以,通常它比集成測(cè)試更慢。
在前四個(gè)文件夾的測(cè)試代碼,通常都需要盡量多包含各種邊際情況。而集成測(cè)試則不必那么細(xì)致,你只需保證功能的正確性。UI測(cè)試也一樣,它也只需要保證每一個(gè)UI組件都正確工作即可。
Other files還剩下app.js和package.json這兩個(gè)文件。
app.js是你應(yīng)用的起始點(diǎn)。它加載其他的一切,然后開始接收用戶的請(qǐng)求。
var express = require("express") , app = express() app.engine("jade", require("jade").__express) app.set("view engine", "jade") app.use(express.static(__dirname + "/public")) app.use(require("./middlewares/users")) app.use(require("./controllers")) app.listen(3000, function() { console.log("Listening on port 3000...") })
package.son文件的主要目的,則是記錄你的應(yīng)用的各個(gè)依賴庫,以及它們的版本號(hào)。
{ "name": "Comments App", "version": "1.0.0", "description": "Comments for everyone.", "main": "app.js", "scripts": { "start": "node app.js", "test": "node_modules/.bin/mocha tests/**" }, "keywords": [ "comments", "users", "node", "express", "structure" ], "author": "Terlici Ltd.", "license": "MIT", "dependencies": { "express": "^4.9.5", "jade": "^1.7.0" }, "devDependencies": { "mocha": "^1.21.4", "should": "^4.0.4" } }
你的應(yīng)用也可以通過正確地配置package.json,然后使用npm start和nam test來啟動(dòng)和測(cè)試你的代碼。詳情參閱:http://browsenpm.org/package.json
最后原文鏈接:https://www.terlici.com/2014/08/25/best-practices-express-structure.html
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/86240.html
摘要:前言這將是一個(gè)分為兩部分,內(nèi)容是關(guān)于在生產(chǎn)環(huán)境下,跑應(yīng)用的最佳實(shí)踐。潛在的攻擊者可以通過它們進(jìn)行針對(duì)性的攻擊。 前言 這將是一個(gè)分為兩部分,內(nèi)容是關(guān)于在生產(chǎn)環(huán)境下,跑Express應(yīng)用的最佳實(shí)踐。第一部分會(huì)關(guān)注安全性,第二部分最會(huì)關(guān)注性能和可靠性。當(dāng)你讀這篇文章時(shí),假設(shè)你已經(jīng)對(duì)Node.js和web開發(fā)有所了解,并且對(duì)生產(chǎn)環(huán)境有了概念。 概覽 生產(chǎn)環(huán)境,指的是軟件生命循環(huán)中的某個(gè)階段。...
摘要:前言這將是一個(gè)分為兩部分,內(nèi)容是關(guān)于在生產(chǎn)環(huán)境下,跑應(yīng)用的最佳實(shí)踐。第一部分會(huì)關(guān)注安全性,第二部分則會(huì)關(guān)注性能和可靠性。關(guān)于第一部分,請(qǐng)參閱在生產(chǎn)環(huán)境下的最佳實(shí)踐安全性。 前言 這將是一個(gè)分為兩部分,內(nèi)容是關(guān)于在生產(chǎn)環(huán)境下,跑Express應(yīng)用的最佳實(shí)踐。第一部分會(huì)關(guān)注安全性,第二部分則會(huì)關(guān)注性能和可靠性。當(dāng)你讀這篇文章時(shí),會(huì)假設(shè)你已經(jīng)對(duì)Node.js和web開發(fā)有所了解,并且對(duì)生產(chǎn)環(huán)...
摘要:寫好一個(gè)模板的最佳實(shí)踐是避免在模板中做任何處理。一個(gè)最好的實(shí)踐是應(yīng)該永遠(yuǎn)不會(huì)直接訪問數(shù)據(jù)庫。中間件的目的是為了提取常見的代碼,它將會(huì)在多個(gè)請(qǐng)求中執(zhí)行,并且通常會(huì)修改請(qǐng)求響應(yīng)對(duì)象。它的目的是加載發(fā)出請(qǐng)求的用戶。 Models 是你與你的數(shù)據(jù)庫交互的一些文件。它們包含了你處理你的數(shù)據(jù)的所有方法和功能。它們不僅僅包含了創(chuàng)建、讀取、更新和刪除的方法,還包含了業(yè)務(wù)邏輯。例如,如果你有一個(gè) car...
摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...
閱讀 1085·2023-04-26 02:56
閱讀 23605·2021-11-23 09:51
閱讀 1980·2021-09-26 10:14
閱讀 3072·2019-08-29 13:09
閱讀 2211·2019-08-26 13:29
閱讀 639·2019-08-26 12:02
閱讀 3626·2019-08-26 10:42
閱讀 3065·2019-08-23 18:18