葉小艷,張 芒,李偉民
(廣州大學華軟軟件學院,廣東 廣州 510990)
在多終端的“互聯網+”時代,如何提高系統開發效率是許多企業一直探討的問題。經過多年的研究及嘗試,前后端分離架構技術成為了許多企業的選擇[1]。因為終端有Android、IOS、Web,它們有不同的展現形式,但是需要展示的數據是一樣的,所以只需要后臺提供接口,前端(終端)獲取數據并展示出來即可,并且不同的終端需要不同的開發人員,只有前后端分離的架構最適合當前多終端的時代,因此前后端分離架構便成了趨勢[2]。
傳統開發協作模式有兩種:一種是前端寫靜態頁面,后端套模板[3]。靜態頁面可以進行本地開發,只需實現視圖而不需考慮業務邏輯[4]。但因后端要套模板且瀏覽前端代碼,過程較為復雜。另一種是前端寫模板[5],但前端編寫過程中過于依賴后端環境,一旦后端沒完成,前端則無法操作。這兩種協作方式是目前市場上大量采用的“前后端分離”技術,但這兩種方式都存在問題,且沒有實現真正意義上的系統整體架構的前后端分離。
文中采用改進傳統的MVC框架來實現前后端分離,以此減少前后端的耦合性,提高用戶體驗,加快網頁加載速度,降低開發和維護成本,提升效率。
傳統的開發模式存在需求溝通不對稱、需求變更頻繁、需求不明確等問題,造成項目效率低下、延期甚至失敗等問題。近年來逐漸興起的快速開發技術,則是實現以業務為驅動的智能開發。形成的各種快速開發平臺是基于業務導向的設計理念,抽提所有管理系統運行的驅動共性形成的“業務驅動模型”,省去了復雜和重復的編碼過程,通過對智能報表、數據維護業務控制和其他參數的管理,可以快速、高效地開發各類業務系統。簡化了系統的運行機制,抽提構成系統的穩定元素和個性元素,解決各類管理類軟件的構筑元素,所以這種快速開發平臺既可以適用于開發任何類型管理軟件,又可以大幅度提高開發效率,減少技術瓶頸。
目前國內已有的Web快速開發框架有不少,僅在MVC方面,就有struts和webwork,還有將struts和webwork統一的struts2,以及tapestry、JSF、easyJWeb[6]。權限管理框架有Spring Security,異步調用技術有AJAX、DWR,RIA技術有extjs、jQuery、FLEX、GWT等[7]。現有的Web快速開發框架功能較強,但也有開發難度大和需要的知識復雜等缺點。這樣開發人員不僅要熟悉服務器端語言,還必須掌握Ajax相關的難以調試的前端技術。與此同時,現有的Web快速開發框架從不同層面解決了開發過程中的部分問題,偏重某一方面,但仍然包含有相應的模板設計和枯燥的代碼段。文中旨在整合并改進現有的MVC框架,并結合現有快速開發技術平臺快速搭建業務模塊的優點,構建一套生態式的開發方法。
MVC模式誕生于19世紀70年代,流行至今。MVC模式,即“模型—視圖—控制器”的框架技術,是將一個應用的處理流程按照這種方式進行分離[8]。這樣,一個應用流程體系分為模型、視圖和控制器三個核心模塊,分別在系統中承擔不同的功能和責任。這種框架技術使開發更加高效,代碼耦合度盡量減小,使應用程序各部分的職責更加清晰。
傳統MVC模式如圖1所示,其缺點如下:第一,視圖依賴于模型。如沒有模型,視圖亦無法呈現效果;第二,請求須經“控制器→模型→視圖”固定流程,用戶才可看到最終展現界面,過程過于復雜;第三,渲染視圖的過程在服務端完成,呈現給瀏覽器的是帶有模型的視圖頁面,性能無法更好地優化。

圖1 傳統MVC模型
為了克服這些缺點,對模型進行改進。從瀏覽器發送AJAX請求到控制器,服務端接受請求,然后返回JSON數據給瀏覽器,直接在瀏覽器中渲染視圖[9],如圖2所示。

圖2 改進的MVC模型
將服務器那一端視為后端,瀏覽器這一端視為前端,將以上改進后的MVC模式簡化為以下前后端分離模式。前端關注界面展現,后端關注業務邏輯,分工明確,職責清晰。
結合快速開發技術平臺和改進后的MVC模型,構建系統開發架構[10],如圖3所示。

圖3 PC系統開發架構
前端窗體通過各類組件組成,形成視圖層;利用規則、函數等組成規則鏈來對視圖層進行控制,并加入實體層,規則鏈可以對視圖層與實體層進行操作控制,使用規則鏈來處理數據,使用實體模型來綁定窗體里的組件。調用數據時從數據庫加載到實體,通過綁定實體的窗體呈現。
2.1.1 系統架構
系統以ERP系統中庫存、OA等模塊為例,搭建基于前后端分離的系統架構,以此搭建一個具有響應式布局的SPA后臺ERP管理系統。
基于圖3開發架構的控制層與模型層,將視圖層脫離出來,React+dva的前端技術組建系統的前端頁面,并通過訪問控制層公開的接口對后臺進行操作,最終形成了該系統架構,如圖4所示(V3指某快速開發平臺)。

圖4 系統架構
系統分為前后端兩部分:
(1)前端向云服務器發起靜態文件請求,如HTML、CSS、JS、Image等,由Nginx服務器搭建的HTTP服務會訪問本地資源,得到HTML、CSS、JS、Image等文件,并返回到前端,然后展示通過瀏覽器編譯成用戶可以看到的頁面。
(2)Nginx通過反向代理將V3執行系統的地址代理到80端口,比如訪問http://www.mitarl.com/api時,通過反向代理后最終訪問的是http://www.mitarl.com:8080/webapi/。
(3)前端通過AJAX(GET、POST)向Nginx服務器發出請求,訪問V3的WebAPI接口,WebAPI通過規則鏈對數據進行處理,最后通過訪問數據庫并得到相應的數據,然后以JSON格式的數據返回到前端,前端拿到數據后對其進行處理,并展示在用戶的瀏覽器上。
2.1.2 系統技術架構
系統技術架構如圖5所示。

圖5 系統技術架構
①State表示Model的狀態數據;
②Action是一個帶type屬性指明具體行為的對象,它是View層改變State的唯一途徑,通過調用dispatch方法,傳遞Action到Model層,根據Reducer改變State;
③異步的Action,與同步的Action在dispatch時是一樣的,但是到達Model層,Model層會先根據type先觸發Reducer再觸發Effect;
④dispatch是一個用于觸發Action的函數,可以看作是觸發這個行為的方式[11];
⑤Reducer是描述如何改變數據的[12];
⑥Effect被稱為副作用,dva底層引入redux-saga做異步流程控制[12],運用ES6的generator的相關概念將異步轉成同步寫法;
⑦Subscription訂閱,然后根據條件dispatch需要的Action,可以監聽路由、鍵盤輸入等的變化;
⑧connect將React與Redux數據連通,通過映射State里面的數據,以React中props數據的方式傳遞給組件。
2.1.3 系統框架整合
先訂閱監聽瀏覽器的路由變化,當地址匹配成功后,會觸發向服務器獲取數據的異步請求,獲取到數據后,對狀態容器進行賦值,狀態容器發生改變后映射到了對應的組件,組件就會進行渲染處理[13]。
(1)Subscriptions訂閱代碼。
配置(){監聽路由(路由地址){如果路由地址==/home或者==/則觸發getOwnerTasks方法}}
(2)Effect代碼(主要處理異步請求)。
getOwnersTask(){先從state樹里獲取當前狀態的app樹下的user和Home樹下的query,再創建傳遞給接口的數據對象,然后調用寫好的service接口并傳入參數。等待call的結果,判斷結果是否正確,正確就put觸發對應的Reducer去修改狀態樹}
(3)Service服務接口代碼。
一個異步函數,提供給effect調用,以JSON格式的數據傳遞params參數到服務端接口,最終收到接口的返回數據并傳遞給正在等待的effect。
(4)Reducer和state樹。
Reducer接收action,即put時帶的參數,這里是把接口傳過來的參數(todo、done兩個數組)合并到當前的state樹里。
(5)Connect將Redux與React連接起來。
將state樹中Home節點,app節點下的isNavbar、user映射到Home組件,使得Home組件的prors屬性存在這幾個屬性。
(6)Home組件下的Table組件。
數據源是通過state樹傳過來React的props屬性下的todo,滾動條是根據瀏覽器大小決定的。使用React進行組件編寫,通過dva(Redux+React-Route+Redux-saga)提供的connect組件對React和Redux進行通信,從而React組件可以獲取Redux里面的store數據,React通過發送action去觸發effect(異步)請求V3WebAPI接口或Reducer(同步),從而Redux改變store里面的數據,React組件的props屬性綁定了Redux的store,store改變props也會改變,React組件就會自動刷新狀態。
基于篇幅,本節只介紹系統開發過程中用到的框架技術的典型示例。
2.2.1 登錄功能
登錄功能主要是對用戶身份進行驗證,核對用戶輸入的用戶名、密碼和驗證碼的數據合法性和一致性。登錄功能采用異步請求到快速開發平臺的登錄構件,實現過程如下:
點擊登錄按鈕會觸發action,然后action觸發effect(帶*的是ES6的異步函數),接收兩個參數。第一個參數:接收action傳過來的數據,這里是把這個參數里面的payload拿出來;第二個參數:獲取dva框架提供的兩種異步方法,call是去請求service(定義請求接口,類似于Ajax)并傳入payload,通過yield關鍵字去等待異步數據,當異步數據獲取回來后,賦值給res,res里面的data就是從V3WebAPI獲取過來的數據,再判斷數據是否正確,如果正確則put一個Reducer,將傳過來的數據對應修改store里面的數據,React再刷新組件狀態。
2.2.2 流程定義功能
快速開發平臺支持完整流程定義和擴展,提供高度可視化流程設計器,集流程圖設計、規則定制和代碼擴展、調試于一體,流程設計開發快捷高效[14]。使用iframe連通快速開發平臺的窗體,使快速開發平臺的窗體能在自己的系統中使用,將自己寫的頁面與快速開發平臺的窗體結合起來。
2.2.3 組織機構管理功能
組織機構管理也是使用iframe連接快速開發平臺的窗體,因為快速開發平臺已經提供了一套組織機構管理的標準的窗體與規則,完全不用自己去做任何的業務編寫,所以利用快速開發平臺的這些窗體來對系統進行擴展。
總之,使用iframe來連接快速開發平臺提供的窗體,簡化系統的一些必要業務,如OA、組織機構管理等模塊,可以直接引入快速開發平臺的窗體來對系統進行擴展,從而節約開發時間和簡化開發過程。
2.3.1 Model層測試
Model層使用Mocha測試框架與第三方斷言模塊expect進行測試。Model層測試代碼如下:
import expect from 'expect';//引入斷言模塊
import {effects} from 'dva/saga';//引入effects
import Home from '../src/models/Home';//引入需要測試的model
//Home測試用例
describe('Model的Home測試用例',()=>{
it('加載',()=>{expect(Home).toExist();});
describe('測試reducers',()=>{
it('getOwnerTasksReducer',()=>{
const reducers=Home.reducers;
const reducer=reducers.getOwnerTaskReducer;
const state={
todo:[],
done:[],
};
expect(reducer(state,
{payload:{todo:[{id:1,state:'Running'}],done:[{id:2,state:'Complete'}]}})
)
.toEqual({ todo:[{ id:1, state:'Runing'}],done:[{ id:2, state:'Complete' }]});
});
});
describe('測試effects',()=>{
it('getOwnerTasks',()=> {
const{call,put}=effects;
const sagas=Home.effects;
const saga={sagas.getOwnerTasks;
const generator=saga({type:.'Home/getOwnerTasks'},{call,put});
let next=generator.next();
//訪問接口成功后,接口會返回是否成功的參數,正常情況下是返回成功
expect(next.value).toEqual({ success: true });
});
});
});
Model層需要測試的是Reducer與Effect,Reducer是普通函數,并且是純函數,職責單一,對于固定輸入,就有固定輸出,所以很容易測試。Effect的測試主要是驗證這件事是否發起了對某個服務的調用,這個服務是否在執行,無關本模塊的正確性。一個Effect實際上是轉化為同步邏輯的測試,因為它是一個generator函數,只需對這個Effect一路next,就能跑完整個邏輯。
2.3.2 表現層測試
表現層主要是React組件,利用Enzyme庫結合mocha與expect斷言,去測試React組件的虛擬DOM創建情況、虛擬DOM的渲染情況等。表現層測試代碼如下:
import React from 'react';
import{shallow,mount,render} from 'enzyme';
import Home from '../../src/routes/Home';
import MinimzeCard from '../../src/components/common/MinimzeCard'
describe('Home測試虛擬DOM',function(){
it('Home下的第一個MinimzeCard的title屬性是待辦',function(){
let app=shallow(
//在Home的虛擬DOM中尋找MinimzeCard組件,找到它的title屬性,它的斷言為待辦
expect(app.find(MinimzeCard).at(@).props('titled)).to.equal('待辦');
});
});
describe ('Home測試渲染情況',function (){
it('渲染Home組件,渲染出兩個MinimzeCard', function(){
let app=render(
//渲染Home組件后,尋找MinimzeCard組件的長度大小,斷言為2
expect(app.find(MinimzeCard).length).to.equal(2);
});
});
總體而言,使用mocha測試框架編寫測試用例去測試dva框架與React框架,最終執行測試用例,并通過所有的測試用例,最后系統未發現嚴重的缺陷,系統業務邏輯無錯誤,保證了系統的完整無漏洞。
該系統PC端,使用某公司快速開發平臺,實現以業務為驅動的智能開發,更多考慮用戶的需求,實現復雜的企業級管理系統軟件的開發,極大地提升了軟件的開發效率和開發質量,大幅縮短了開發周期[15]。
系統移動端采用前后端分離技術以及響應式布局,并使用流行前端框架React開發了ERP系統,經過詳細的設計和測試,最終實現了整個開發框架。React的組件化思想,使得系統更加模塊化,更容易維護、拓展。用戶可以較快地熟悉系統,可以利用PC端和移動端操作系統,從而提高工作效率,縮短開發流程。
利用快速開發技術,以業務為驅動快速開發系統,將重點放在系統更完善的功能上而非代碼,結合現有移動端的開發框架,逐步完善移動端框架,并且對移動端擴展更多的功能。推廣這種快速、擴展性強的框架方法,使信息系統開發更加便捷多元。