該文章為網絡課 Introduction to MongoDB using the MEAN Stack學習筆記。
1.關于REST(Representational State Transfer)
1.1 什么是REST?
是一種編程模式,它通過HTTP來搭建客戶端瀏覽器與服務器之間的通信。
It is a paradigm for a browser with a server over HTTP.
關于HTTP request,可以看做是三個方面的組合:
- Verb:e.g. GET/POST
- Resource:e.g. /home
- Body (optional):e.g. JSON
當服務器收到HTTP request之后,會向客戶端返回HTTP request,這其中可以包含很多信息,比如status,JSON等等。
1.2 三個基本方面
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。創建REST API就是在創建能夠讓客戶端javascript代碼能夠使用的endpoint/routes。
1.5 什么是RESTful ?
如果一個架構符合REST原則,就稱它為RESTful架構。
總結一下什么是RESTful架構:
(1)每一個URI代表一種資源; -----> e.g. URLs
(2)客戶端和服務器之間,傳遞這種資源的某種表現層; -----> e.g. data type
(3)客戶端通過四個HTTP動詞,對服務器端資源進行操作,實現"表現層狀態轉化"。 -----> method: GET/POST/PUT/DELETE
2. Express
2.1 是什么?
是用于啟動一個HTTP Server的Node.js pakage。相當于是一個Web Application Framework允許設計者用Node.js來搭建server。
2.2 一個簡單的Hello World例子
- package.json
"dependencies": {
"express": "4.12.3"
}
- server.js
var express = require('express');
var app = express(); // 創建一個express web app server
app.get('/', function(req, res) { // 創建一個routes為"/"的HTTP GET響應
res.send('Hello, world!'); //返回一個"Hello, world!"頁面的響應
});
// 創建一個routes為"/user/:user"的HTTP GET響應
// :user表示user是一個可變的參數,可以利用req.params取到
// req.query.school可以取到HTTP中?之后的school對應的內容
app.get('/user/:user', function(req, res) {
res.send('Page for user ' + req.params.user + + ' with school ' + req.query.school);
});
app.listen(3000); //讓這個server監聽來自port3000的請求
console.log('Server listening on port 3000!');
- 結果
3. TDD (Test Driven Development)
思想:不需要瀏覽器來對server進行測試,利用一個叫做“superagent”的package來模擬瀏覽器發出的HTTP request,并接收HTTP response,然后利用類似常規mocha測試的方法進行測試。
package.json
{
"dependencies": {
"express": "4.12.3",
"mocha": "2.2.4",
"superagent": "1.2.0"
},
"scripts": {
"test": "mocha test.js"
}
}
- server.js
值得注意的是,由于此處重視的是測試,因此server上只寫express的邏輯,并以一個函數的方式export出去,此處并不需要寫對server的啟動,即不需要寫對port的監聽。
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
對server的啟動以及關閉由測試文件來執行。
var app = require('./server');
var assert = require('assert');
var superagent = require('superagent');
describe('server', function() {
var server;
beforeEach(function() {
server = app().listen(3000); //啟動server
});
afterEach(function() {
server.close(); //關閉server
});
it('prints out "Hello, world" when user goes to /', function(done) {
// 創建一個對"http://localhost:3000/"的GET請求,并對響應進行測試
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
思想:相當于一種code isolation。對作用是initialization的代碼與使用這些代碼的代碼進行隔離, 以此增加設計的靈活性。
例子
假設我們需要利用express中從客戶端取到的數據對數據庫進行操作,那么一個非常直觀的做法是:
- 把mongoDB initialization setup的代碼放在server.js的全局處,然后在routeHandler中使用從客戶端取到的數據對mongoDB進行操作。
//全局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來查user并以JSON可是作為響應
User.findOne({ _id: req.params.id }, function(error, user) {
res.json({ user: user });
});
});
app.listen(3000);
console.log('Server listening on port 3000!');
這樣做的弊端是:當需要用不同的數據庫是,我不僅需要改動全局scope中數據庫的setup代碼,我還需要改動express中的routeHandler代碼。當express中邏輯比較復雜的時候,這樣的改動就顯得很cumbersome。
- 利用一個名叫"wagner-core"的包裹來對數據庫setup工作進行封裝,寫成“service”。
var express = require('express');
var mongoose = require('mongoose');
var wagner = require('wagner-core');
// Dependency Injection: setupModels 和 setupApp的代碼被隔離開
setupModels(mongoose, wagner);
var app = express();
setupApp(app, wagner);
app.listen(3000);
console.log('Listening on port 3000!');
//對 model的set up不再是全局scope,而是在一個函數中的局部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中定義以個名叫“User”的Service
return User;
});
}
function setupApp(app, wagner) {
//雖然setupApp中的代碼與setupModel中的scope并不一樣
//但利用wagner.invoke來引入定義了的service,因此setupApp中的代碼可以在這個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是函數routeHandler的closure,相當于雖然User并不在routeHandler的scope里面,但是卻可以用它,想到與routeHandler closes User。
- 對setup代碼的改動可以反映到express的代碼中,而express得代碼并不需要動。比如要換一個數據庫,只需要改動setupModel中的代碼。