該文章為網(wǎng)絡(luò)課 Introduction to MongoDB using the MEAN Stack學(xué)習(xí)筆記。
1.關(guān)于REST(Representational State Transfer)
1.1 什么是REST?
是一種編程模式,它通過(guò)HTTP來(lái)搭建客戶端瀏覽器與服務(wù)器之間的通信。
It is a paradigm for a browser with a server over HTTP.
關(guān)于HTTP request,可以看做是三個(gè)方面的組合:
- Verb:e.g. GET/POST
- Resource:e.g. /home
- Body (optional):e.g. JSON
當(dāng)服務(wù)器收到HTTP request之后培愁,會(huì)向客戶端返回HTTP request,這其中可以包含很多信息,比如status钉凌,JSON等等。
1.2 三個(gè)基本方面
3 fundamental aspects of REST design pattern: client, server, resource
1.3 CRUD (Creat/Read/Update/Delete)
POST -> Create
GET -> Read
1.4 什么是endpoint/routes捂人?
HTTP中使用的GET/user/42或者POST/user等被稱之為endpoint/routes御雕。創(chuàng)建REST API就是在創(chuàng)建能夠讓客戶端javascript代碼能夠使用的endpoint/routes。
1.5 什么是RESTful 滥搭?
如果一個(gè)架構(gòu)符合REST原則酸纲,就稱它為RESTful架構(gòu)。
總結(jié)一下什么是RESTful架構(gòu):
(1)每一個(gè)URI代表一種資源瑟匆; -----> e.g. URLs
(2)客戶端和服務(wù)器之間闽坡,傳遞這種資源的某種表現(xiàn)層; -----> e.g. data type
(3)客戶端通過(guò)四個(gè)HTTP動(dòng)詞愁溜,對(duì)服務(wù)器端資源進(jìn)行操作疾嗅,實(shí)現(xiàn)"表現(xiàn)層狀態(tài)轉(zhuǎn)化"。 -----> method: GET/POST/PUT/DELETE
2. Express
2.1 是什么冕象?
是用于啟動(dòng)一個(gè)HTTP Server的Node.js pakage代承。相當(dāng)于是一個(gè)Web Application Framework允許設(shè)計(jì)者用Node.js來(lái)搭建server。
2.2 一個(gè)簡(jiǎn)單的Hello World例子
- package.json
"dependencies": {
"express": "4.12.3"
}
- server.js
var express = require('express');
var app = express(); // 創(chuàng)建一個(gè)express web app server
app.get('/', function(req, res) { // 創(chuàng)建一個(gè)routes為"/"的HTTP GET響應(yīng)
res.send('Hello, world!'); //返回一個(gè)"Hello, world!"頁(yè)面的響應(yīng)
});
// 創(chuàng)建一個(gè)routes為"/user/:user"的HTTP GET響應(yīng)
// :user表示user是一個(gè)可變的參數(shù)渐扮,可以利用req.params取到
// req.query.school可以取到HTTP中?之后的school對(duì)應(yīng)的內(nèi)容
app.get('/user/:user', function(req, res) {
res.send('Page for user ' + req.params.user + + ' with school ' + req.query.school);
});
app.listen(3000); //讓這個(gè)server監(jiān)聽(tīng)來(lái)自port3000的請(qǐng)求
console.log('Server listening on port 3000!');
- 結(jié)果
3. TDD (Test Driven Development)
思想:不需要瀏覽器來(lái)對(duì)server進(jìn)行測(cè)試论悴,利用一個(gè)叫做“superagent”的package來(lái)模擬瀏覽器發(fā)出的HTTP request,并接收HTTP response墓律,然后利用類似常規(guī)mocha測(cè)試的方法進(jìn)行測(cè)試意荤。
package.json
{
"dependencies": {
"express": "4.12.3",
"mocha": "2.2.4",
"superagent": "1.2.0"
},
"scripts": {
"test": "mocha test.js"
}
}
- server.js
值得注意的是,由于此處重視的是測(cè)試只锻,因此server上只寫(xiě)express的邏輯玖像,并以一個(gè)函數(shù)的方式export出去,此處并不需要寫(xiě)對(duì)server的啟動(dòng),即不需要寫(xiě)對(duì)port的監(jiān)聽(tīng)捐寥。
var express = require('express');
module.exports = function() {
var app = express();
app.get('/', function(req, res) {
res.send('Hello, world!');
});
app.get('/user/:user', function(req, res) {
res.send('Page for user ' + req.params.user + ' with option ' +
req.query.option);
});
return app;
};
- test.js
對(duì)server的啟動(dòng)以及關(guān)閉由測(cè)試文件來(lái)執(zhí)行笤昨。
var app = require('./server');
var assert = require('assert');
var superagent = require('superagent');
describe('server', function() {
var server;
beforeEach(function() {
server = app().listen(3000); //啟動(dòng)server
});
afterEach(function() {
server.close(); //關(guān)閉server
});
it('prints out "Hello, world" when user goes to /', function(done) {
// 創(chuàng)建一個(gè)對(duì)"http://localhost:3000/"的GET請(qǐng)求,并對(duì)響應(yīng)進(jìn)行測(cè)試
superagent.get('http://localhost:3000/', function(error, res) {
assert.ifError(error);
assert.equal(res.status, 200);
assert.equal(res.text, "Hello, world!");
done();
});
});
});
4. Dependency Injection
思想:相當(dāng)于一種code isolation握恳。對(duì)作用是initialization的代碼與使用這些代碼的代碼進(jìn)行隔離, 以此增加設(shè)計(jì)的靈活性瞒窒。
例子
假設(shè)我們需要利用express中從客戶端取到的數(shù)據(jù)對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作,那么一個(gè)非常直觀的做法是:
- 把mongoDB initialization setup的代碼放在server.js的全局處乡洼,然后在routeHandler中使用從客戶端取到的數(shù)據(jù)對(duì)mongoDB進(jìn)行操作崇裁。
//全局scope: mongoDB setup
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test');
var userSchema = new mongoose.Schema({
name: String
});
var User = mongoose.model('User', userSchema);
//express
var express = require('express');
var app = express();
app.get('/user/:user', function(req, res) {
//利用id來(lái)查user并以JSON可是作為響應(yīng)
User.findOne({ _id: req.params.id }, function(error, user) {
res.json({ user: user });
});
});
app.listen(3000);
console.log('Server listening on port 3000!');
這樣做的弊端是:當(dāng)需要用不同的數(shù)據(jù)庫(kù)是,我不僅需要改動(dòng)全局scope中數(shù)據(jù)庫(kù)的setup代碼束昵,我還需要改動(dòng)express中的routeHandler代碼拔稳。當(dāng)express中邏輯比較復(fù)雜的時(shí)候,這樣的改動(dòng)就顯得很cumbersome锹雏。
- 利用一個(gè)名叫"wagner-core"的包裹來(lái)對(duì)數(shù)據(jù)庫(kù)setup工作進(jìn)行封裝巴比,寫(xiě)成“service”。
var express = require('express');
var mongoose = require('mongoose');
var wagner = require('wagner-core');
// Dependency Injection: setupModels 和 setupApp的代碼被隔離開(kāi)
setupModels(mongoose, wagner);
var app = express();
setupApp(app, wagner);
app.listen(3000);
console.log('Listening on port 3000!');
//對(duì) model的set up不再是全局scope礁遵,而是在一個(gè)函數(shù)中的局部scope
function setupModels(mongoose, wagner) {
mongoose.connect('mongodb://localhost:27017/test');
var userSchema = new mongoose.Schema({
name: String
});
var User = mongoose.model('User', userSchema);
wagner.factory('User', function() { // 在wagner中定義以個(gè)名叫“User”的Service
return User;
});
}
function setupApp(app, wagner) {
//雖然setupApp中的代碼與setupModel中的scope并不一樣
//但利用wagner.invoke來(lái)引入定義了的service轻绞,因此setupApp中的代碼可以在這個(gè)scope中得到利用
var routeHandler = wagner.invoke(function(User) {
return function(req, res) {
User.findOne({ _id: req.params.id }, function(error, user) {
res.json({ user: user });
});
};
});
app.get('/user/:id', routeHandler);
}
這樣的好處在于:
- setup的代碼不需要在全局scope也可以在express的routeHandler中得到使用。這是一種closure的概念佣耐,User是函數(shù)routeHandler的closure政勃,相當(dāng)于雖然User并不在routeHandler的scope里面,但是卻可以用它兼砖,想到與routeHandler closes User奸远。
- 對(duì)setup代碼的改動(dòng)可以反映到express的代碼中,而express得代碼并不需要?jiǎng)右从恪1热缫獡Q一個(gè)數(shù)據(jù)庫(kù)然走,只需要改動(dòng)setupModel中的代碼援制。