目錄:
一、什么是前后端分離?
大部分人認同開發微信小程序或SPA(WEB單頁應用)是實現“前后端分離”的。所有業務數據都是“后端應用”通過HTTP接口提供給“前端應用”。一個“完整的應用”被物理隔離為兩個獨立的應用,并由擅長的開發者負責,從而實現職責分離:
前端開發者:負責 View 和 Controller 層。
后端開發者:只負責 Model 層,業務處理/數據等。
“前后端分離”是強調“職責分離”,上面應用分離的例子是為了方便理解接受,可以不應用分離。通常應用分離能大量減少不小心的越界行為。
前后端分離后的樣子:
二、為什么要前后端分離?
根據上圖,只有“web網站”和“React Isomorphic”兩種形態應用沒有自然物理隔離前端和后端。我們存在很多沒有前后端分離的web網站應用(這也是痛苦的根源),而React Isomorphic應用是前后端分離流行后的開發模式(代替web網站應用)。
2.1 清晰前后端職責
分離前:前端開發者提供靜態頁面,后端開發者完成 Controller 和 View 層代碼。但前端開發者經常會插一腳修改 Controller 和 View 層代碼。
分離后:面向用戶的 Controller 和 View 層都由前端開發者做;后端開發者只需復雜 Model 層,提供業務數據接口。
前后端分離后基本不需要相互修改對方代碼。
2.2 提高開發效率
分離前:前端和后端溝通/約定頁面模板的每個字段、字段值的格式、適合模板的數據結構。
分離后:前段和后端溝通需要的 Model 接口。
前后端分離能明顯減少溝通成本;約定的內容也少很多,減少大量聯調時間。
2.3 前端能做更多,后端能更專一
前端可以服務端開發,而且可以不再局限WEB開發,同一套API,前端可以實現SPA、微信小程序、支付寶小程序等。
后端能更專一API開發,不用為UI要求的時間格式、模板布局要求的數據結構發愁。
三、基于Node.js做前后端分離
目前存在很多web網站應用,我們正在用PHP實踐前后端分離。前端同事對PHP不熟悉,成本比較高,所以最終會考慮使用Node.js實踐前后端分離。
這里只討論web網站和React Isomorphic應用,因為其他形態的應用已經物理分離了前后端。
React Isomorphic應用比web網站應用更“先進”,而且Node.js生態已有成熟的方案,所以新項目也會更傾向選擇React Isomorphic應用。
3.1 為什么選擇Node.js而不繼續用PHP?
3.1.1 招聘成本
招會Node.js的前端比招會PHP的前端容易。
3.1.2 學習成本
前端學習PHP語言。語法學習成本不高,但熟悉度還是要時間成本的。
3.1.3 開發成本
開發過程頻繁切換語言。經常會在PHP中寫JS語法,這點比較煩。
3.1.4 服務器性能
在自己服務器測試兩種場景。
服務器配置:
- 騰訊云服務器(Ubuntu 16.04.1 LTS)
- CPU: 2核
- 內存:4GB
- 帶寬:2M
Node.js環境:
- Node.js版本:v8.12.0(Alinode v3.12.0)
- 框架:Egg.js
- worker進程數:2
PHP環境:
- PHP版本:7.2.10
- 框架:Lumen
- php-fpm最大子進程數:5
兩種場景測試代碼:
場景一,直接輸出字符串,不做任何IO和計算。
PHP效率比Node.js高
Node.js:
// egg-4
async function test(ctx) {
ctx.body = '<h1>goddess.daifee.com</h1><p>Egg程序</p>';
}
PHP:
// php-4
$router->get('/test', function () use ($router) {
return '<h1>goddess-php.daifee.com</h1><p>這是PHP服務</p>';
});
場景二,模擬200ms的網絡請求。
Node.js效率比PHP高。相對“場景一”PHP效率下降明顯,Node.js不明顯。
Node.js:
// egg-4—test2
async function test2(ctx) {
const start = Date.now();
await new Promise(resolve => {
setTimeout(() => {
resolve(true);
}, 200);
});
const offset = Date.now() - start;
ctx.body = `<h1>goddess.daifee.com</h1><p>Egg程序 ${offset}</p>`;
}
PHP:
// ./WeTest_[php-4—test2]_20181014172129.pdf
$router->get('/test2', function () use ($router) {
$start = microtime(true);
usleep(200000);
$offset = microtime(true) - $start;
return "<h1>goddess-php.daifee.com</h1><p>這是PHP服務 {$offset}</p>";
});
3.2 基于Egg.js框架開發(編碼)
下面用Egg.js框架開發一個頁面為例(瀏覽器端開發模式不需要改變)。
開發一個新頁面(用戶主頁頁),只需要下面4個步驟:
- 模板:創建模板文件。
<!-- app/views/user.ejs -->
這里是 ejs 模板
- 數據接口:幾行代碼封裝數據接口。
// app/service/user.js
const { Service } = require('egg');
module.exports = class UserService extends Service {
async get(userId) {
const url = `https://api.gateway.com/xxx/${userId}`;
// http請求/ajax
const response = await this.app.curl(url);
return response;
}
}
- 控制器:過濾/驗證用戶輸入、驗證權限、調用數據接口、渲染模板。
// app/controller/user.js
const BaseController = require('./base-controller');
module.exports = class UserController extends BaseController {
async profile() {
const { params, service } = this.ctx;
// 只有自己才能訪問自己主頁。不是自己就拋出 403 異常
this.assertUser(params.userId);
user = await service.user.get(params.userId);
await this.render('user', {user: user});
}
}
- 路由:一行代碼聲明路由。
// app/router.js
module.exports = app => {
const { router, controller, middleware } = app;
const { authorize } = middleware;
const { user } = controller;
// 已登錄用戶才能訪問
router.get('/users/:userId', authorize.user, user.profile);
}
只負責 Controller 和 View 層的Node.js服務端開發非常簡單。一個企業應用除了開發,還有其他環節需要做好。
3.3 我們還缺什么?
前端開發者可以在服務端寫 Controller 和 View 層代碼,但整個生產環節還缺什么?
3.3.1 前端開發者
既然前端開發者需要做更多,能做更多,所以工作量增加。所以缺前端開發者。
- 同一個項目,編碼的工作量增加了。
- 前端同事需要關注、分析、協助維護Node.js服務器。
- 肯定會有更多的xxx小程序需求。
3.3.2 適合我們的框架和項目腳手架
- 確定了web網站應用使用Egg框架,還需為不同業務場景的項目創建拿來即用的腳手架。
- 未確定React Isomorphic應用的框架。我體驗過next.js,是一個不錯的選擇。
3.3.3 構建/部署服務
目前還沒有Node.js項目的構建/部署服務。
基本需求:
- 構建:開發者只需關注源碼,“構建服務”自動構建項目。
- 發布:支持選擇git tag構建,部署到“生產環境”。
- 發布:支持選擇git branch構建,部署到“測試環境”。
- 回滾:支持指定版本回滾。
- 重啟:支持重啟Node.js服務器。
- 發布:允許測試人員發布“測試版本”。
3.3.4 嚴謹的git工作流
需要依據“構建/部署服務”定制嚴謹的git工作流。
3.3.5 風險監控和日志采集
- 風險監控可以考慮Alinode。
- 完全免費
- 支持性能監控、安全提醒、故障排查、性能優化等
- 支持手動下載性能數據
- 日志采集接入大數據團隊的服務?
關于React Isomrophic
當時用React全家桶做了一個SPA Demo項目,然后用next.js框架再做一個React Isomorphic版。發現可以復用(copy)98%的代碼,然后花一點時間將 React Router 替換為 next.js 自帶的 Router 即可
基于 next.js 框架 React Isomorphic 應用與 SPA 的開發體驗差不多。
畢竟一套代碼運行于兩個不同環境,React Isomorphic 還是帶來了一些坑:
- 需要意識到哪些代碼在2個環境都執行,哪些代碼只在其中一個環境執行。
- 立即執行的代碼在2個環境都執行。
- React組件生命周期的代碼只在Browser環境執行。
-
Component.getInitialProps()
方法在2個環境都執行。 - 小心使用運行環境的“接口”
-
next.js
自帶Router
,與“其他Router”肯定存在差異。 - 某些組件需要用唯一自增ID或隨機數,這種做法會使服務端于客戶端存在差異。
- “服務端”與“客戶端”基本只能用cookie共享“狀態”,要珍惜cookie資源。