前言
入手Node.js半年,從用Express開發自己的部落格到用Sails開發公司專案,深深被Sails震撼了。Sails是Balderdash團隊的產品,快速的專案構建、優秀的框架結構還有眾多的擴充套件,讓我有種相見恨晚的感覺。在Koa流行之前,個人認為Sails的使用者量還是挺可觀的。今天,我想寫一寫Sails那些讓我感動的地方,順便理順一下Sails的架構。
目錄
一步搭建專案
專案架構
ORM
MVC的實現
路由
安全
日誌
單元測試
WebSocket
一步搭建專案
在安裝了Node.js 和 Sails的環境下,只需要一條命令,就能夠搭建一個擁有完整架構的專案,儘管這很簡單,我還是覺得有必要說一下。
在已經安裝了Node.js和npm的前提下,首先你需要全域性下安裝Sails$ sudo npm install sails -g
其次在一個空路徑下,新建一個專案$ sails new newApp
最後,只需要前往專案路徑,把專案執行起來$ cd testProject
$ sails lift
訪問 http://localhost:1337
就能看到一個新的專案
專案架構
.
├── api
│ ├── controllers
│ ├── models
│ ├── policies
│ ├── responses
│ └── services
├── views
├── assets
├── config
├── tasks
├── node_modules
├── package.json
├── Gruntfile.js
├── README.md
└── app.js
api/
api
目錄下是你要構建應用的核心所在,常說的MVC的設計結構就體現在這裡api/controllers
:控制層,該層是Http請求的入口。Sails官方建議該層只處理請求的轉發和頁面的渲染,具體的邏輯實現應該交給Service層。api/models
:模型層,在Sails中,對於Model採用的是充血模型,除了可以在模型中定於屬性之外,還可以定義包含邏輯處理的函式。在Sails中,所有Model都可以全域性性訪問。api/policies
:過濾層,該層在Controller層之前對Http請求做處理,在這一層中,可以定於一些規則來過濾Http請求,比如身份認證什麼的。api/responses
:http響應的方法都放這裡,例如伺服器錯誤、請求錯誤、404錯誤等,定義在responses資料夾裡面的方法,都會賦值到controller層的req物件中。api/services
:服務層,該層包含邏輯處理的方法,在Sails中,所有Service都可以全域性性訪問。
views/
檢視層,存放檢視模版檔案的地方,Sails預設是提供ejs模版引擎的,如果你願意,你可以換成jade、handlebars或者任何你喜歡的模版引擎。
assets/
資原始檔夾,在Sails啟動的時候,會啟動某一個Grunt任務,把assets資料夾裡的內容或壓縮或編譯或複製到根目錄下的.tmp
目錄,這是前端可以直接通過路由訪問的資源,HTML、JS、CSS以及圖片等靜態資源都放在這裡了。
config/
配置資料夾,在Sails啟動的時候,會載入該資料夾裡的檔案,並賦值在全域性物件sails.config
中,所以能夠在任何一個地方都能用到。在用Sails開發,會經常跟這個資料夾裡的檔案打交道,從config的構成很容易知道Sails都提供哪方面的功能。
tasks/
Sails自帶的專案自動化工具是Grunt
,而Grunt的配置和任務註冊都放在這個資料夾裡了。這裡已經提供了通常會用到的CSS編譯、JS壓縮、檔案合併,更改檢測等等任務,當然如果沒有自己需要的,還能擴充套件。
app.js
Sails的啟動檔案,無論是$ sails lift
命令或者$ npm start
命令都會執行該檔案。
ORM
開發了Sails的團隊Balderdash,還開發了一套ORM框架:Waterline。
Waterline在Sails主要的舞臺是在/api/models
目錄裡,在這裡定義的模型檔案,在Sails啟動的時候,都要經由Waterline的洗禮。
Waterline 是通過Adapter關聯資料庫的,不同的Adapter關聯不同的資料庫。
Waterline 能適配絕對部分資料庫,大致分類兩類,一類是官方團隊開發的 Adapter適配的,一類是民間開發者開發的Adapter適配的:
官方支援的:
PostgreSQL
MySQL
MongoDB
Redis
Disk
Memory
民間開發的:
SQLServer
OrientDB
Oracle
Cassandra
關於Waterline的更多資訊可以關注:
github:waterline
github:waterline-docs
MVC的實現
在這一段我想不僅僅要談論到Model層、View層和Controller層,我認為還有必要談到Service層、和Policy層。
Model層
模型檔案定義到/api/models
中,由Waterline驅動,所有model都能全域性訪問。Sails提供命令列建立model的命令:$ sails generate model MODEL_NAME
View層
實現在/views
中,除了預設提供的ejs模版引擎之外,還能更換成jade、handlebars等模版引擎
Controller層
在/api/controller
目錄裡,Sails中提供建立controller的命令:$ sails generate controller CONTROLLER_NAME
。Sails也提供同時建立model和對應的Controller的命令:$ sails generate api API_NAME
。
Service層
在/api/services
目錄裡,存放自定義的服務,所有service都能夠全域性訪問,Sails官方的建議是把邏輯處理都放在該層中,Controller層只做路由的分發和輕邏輯的處理。
Policy層
在/api/policies
目錄裡,存放自定義的過濾器。該層是一條請求在到達Controller之前根據需求過濾請求的中間層。在/api/policies
目錄中定義的檔案,還需要在config/policies.js
檔案中為需求應用到某一過濾器的Action配置。
路由
Sails中要理解路由,首先要記得這個名詞blueprint
,中文翻譯為:藍圖。我不知道官方是否解釋過為什麼要用個單詞,但以我的理解,Sails的blueprint是負責指揮每一條客戶端請求應該分配到伺服器端的哪個Action去,所以叫藍圖吧。
blueprint主要分為三種:RESTful routes
、Shortcut routes
、Action routes
。
RESTful routes
當路徑諸如:/:modelIdentity
或者 /:modelIdentity/:id
的時候,blueprint會根據HTTP的動作(GET、POST、DELETE、PUT等)來分配到相應的Controller下相應的Action來處理。例如一個POST請求/user
會建立一個使用者,一個DELETE請求/user/123
會刪除id
為123的使用者。
Shortcut routes
這種路由主要是方便開發,請求的引數可以直接寫在請求路徑中,例如/user/create?name=joe
會建立一個新的使用者,/user/update/1?name=mike
會更新id
為1的使用者的名字。shortcut routes在開發環境很便利,但是在生產環境下需要關閉。
Action routes
這種路由會自動的為Controller層的每一個Action建立一個路由,例如你的Controller層有一個FooController.js
,裡面有一個Actionbar
,那麼請求/foo/bar
就會分配到bar
Action。
當然Sails也會提供自定義的路由,使用者可以在config/routes.js
和config/polices.js
這兩個配置檔案中選擇關閉或者開啟blueprint提供的路由,和定義自己的路由。
安全
要確保產品的安全性,要對幾種常見的攻擊和安全策略瞭如指掌,諸如CORS、CSRF、DDOS、XSS等。Sails對於常見的安全策略都有提供支援,且只需要通過相關的配置檔案就可以控制安全策略的等級。深入探討Web的安全策略,並不在本文的範疇內,日後我會以這個為題寫一篇文章聊聊Web的安全。
想要了解更多Sails的安全策略可以看看這裡:sails: security
日誌
Sails提供了一個全域性物件sails.log
用來處理日誌資訊的輸出,日誌是分level的,在config/log.js
中配置日誌輸出的level,而level的作用看下錶:
Priority | level | Log fns visible |
---|---|---|
0 | silent | N/A |
1 | error | .error() |
2 | warn | .warn(), .error() |
3 | debug | .debug(), .warn(), .error() |
4 | info | .info(), .debug(), .warn(), .error() |
5 | verbose | .verbose(), .info(), .debug(), .warn(), .error() |
6 | silly | .silly(), .verbose(), .info(), .debug(), .warn(), .error() |
Sails的日誌管理預設是info層的,既會輸出.info(), .debug(), .warn(), .error()的資訊。
單元測試
Sails使用了mocha進行單元測試,在新建Sails專案的時候,沒有建立單元測試的資料夾,需要自己手動構造單元測試目錄,官方建議的目錄是這樣的:
.
├── api
├── assets
├── ...
├── test
│ ├── unit
│ │ ├── controllers
│ │ │ └── UsersController.test.js
│ │ ├── models
│ │ │ └── Users.test.js
│ │ └── ...
│ ├── fixtures
│ ├── ...
│ ├── bootstrap.test.js
│ └── mocha.opts
└── views
而我在單元測試常用的組合是:mocha、should、supertest、 istanbul
其中should是提供斷言,supertest是用於測試Controller層的時候偽造http請求的,而istanbul則是提供測試程式碼覆蓋率的。
關於怎麼在Sails中編寫測試程式碼,可以參考 sails:testing
WebSocket
對於有即時性通訊需求的Web應用,我們會用Socket,Sails也為這方面提供了支援。在客戶端提供js檔案:sails.io.js
,而在伺服器端提供全域性物件:sails.sockets
。通過這兩個物件,就可以進行客戶端和伺服器端即時性通訊的開發了。
Sails預設會啟動WebSocket功能,在客戶端訪問伺服器端的時候,會自動嘗試在同域名下連線socket。
值得注意的是,這樣會對AngularJS、EmberJS等前端MVVC開發產生一些障礙。
比如進行AngularJS開發的時候,我們在http://localhost:9000
跑AngularJS專案,而伺服器端卻跑在http://localhost:1337
。
當訪問http://localhost:9000
的時候,sails.io.js
會嘗試於當前路徑下進行socket連線,也就是http://localhost:9000
,這時會出錯,因為伺服器是跑在http://localhost:1337
的。
在開發的時候要解決這樣的問題的時候,我們只需要在AngularJS這邊引入sails.io.js
之後定義連線路徑就行了:
<script src="scripts/lib/sails.io.js"></script>
<script>io.sails.url = "http://localhost:1337";</script>
結語
可以說Sails涵蓋了Web開發中會遇到的絕大部分需求和問題,如果深入研究Sails的話,是受益匪淺的。
如果本文對您有用
請不要吝嗇你們的Follow與Start
這會大大支援我們繼續創作
「Github」
MZMonster :@MZMonster
JC_Huang :@JerryC8080