8/人天,小记一次 JAVA(APP后台) 项目改造 .NET 过程(后台代码已完整开源于 Github)...
Github:?https://github.com/iccb1013/Jade.Net
我們只消耗了8/人天的時間,完成了全部工作,基于我們 Jade.Net?的開源后臺代碼,任何小規(guī)模的后臺管理系統(tǒng),都可以在極短的時間內(nèi)完成。
這是我們在 2017?年早些時候開發(fā)的一個項目,甲方是一家工藝美術(shù)品企業(yè),需要開發(fā)一款 APP 展示產(chǎn)品,并引入會員(多級代理),在線下單,返點等功能。?在立項后由于一些原因,選擇了使用 Java 來開發(fā)后臺管理部分,面向 IOS?和 Android?版客戶端提供服務(wù)。
項目的前期調(diào)研、分析、設(shè)計工作,及推進過程這里不作過多討論,本文主要圍繞改造工作中的技術(shù)問題進行記錄和分析。
先簡單看一下,了解這是怎么樣的一個項目,終端 APP?如圖:
后臺管理端,有兩個職責:
一)向 APP?端提供功能接口,如商品接口,會員接口等;
二)純后臺管理功能,如商品管理,會員管理等;
總體而言并不復(fù)雜。除開業(yè)務(wù)邏輯之外,是很普通的管理后臺。
下面我對這次改造工作的過程進行回顧與說明。
這次改造重構(gòu)的難點是要保持 APP?接口的絕對兼容,不能影響生產(chǎn)環(huán)境的正常運行。
首先我們分析原 Java?版項目:
Java?版后臺代碼結(jié)構(gòu)引用關(guān)系比較混亂,后臺的基本權(quán)限、菜單、字典散落分布在各處:
原后臺使用的是 MySql?數(shù)據(jù)庫,我們這次改造要將數(shù)據(jù)庫換為 SQL Server ,并使用 Entity Framework?作為我們的數(shù)據(jù)庫訪問層。
引用關(guān)系比較混亂,沒有表結(jié)構(gòu)說明書,我們需要重新核對整個數(shù)據(jù)庫表結(jié)構(gòu)的設(shè)計:
?
數(shù)據(jù)表字段名與類屬性名的不合理,讓我們的核對工作不是很樂觀:
使用了?mybatis?做為數(shù)據(jù)訪問層,我們在改造過程中,需要逐一核對數(shù)據(jù)庫操作,以便在改造過程中保持百分之百的兼容:
?
下面開始我們的改造工作:
第一步:使用 SQL Server?重建數(shù)據(jù)庫
遷移數(shù)據(jù)庫分為兩個步驟,一是建庫,二是遷移數(shù)據(jù)。
理論上來說建庫工作可以導(dǎo)出原 MySQL 的建庫腳本調(diào)整后在 SQL Server?執(zhí)行來建立數(shù)據(jù)庫,但是為了后續(xù)工作更穩(wěn)妥的開展,我們采購了人工核對,手工建立的方式,重新梳理表結(jié)構(gòu)。
我們根據(jù) Java 代碼和原 MySQL?數(shù)據(jù)庫,分析出了詳細的的表結(jié)構(gòu)設(shè)計:
在這一過程中,也對原表結(jié)構(gòu)進行了修訂與細節(jié)調(diào)整,主要修訂以下存在的問題:
1)字段命名模糊,比如字段 modify_person ,或 person_id 。系統(tǒng)中存在?后臺用戶?和?客戶?兩個概念,一個用表 user?存儲,一個用表 customer?存儲,所以一些表中的 person_id?指向很模糊。
2)字段命名不統(tǒng)一,比如同樣是備注,有些表使用 description ,有些表則使用 remark諸如此類,我們在重構(gòu)中進行了完全的統(tǒng)一。
3)去除了原系統(tǒng)中不使用的表和字段,重新設(shè)計部分基礎(chǔ)結(jié)構(gòu)方面的表,去除了原開發(fā)人員復(fù)用的不知名項目相關(guān)的字段關(guān)聯(lián)關(guān)系。
4)一些單詞拼寫錯誤。
此外,優(yōu)化了一些表關(guān)聯(lián)結(jié)構(gòu)及業(yè)務(wù):
1)商品與商品分類的關(guān)系,由一個商品只能屬于一個分類,優(yōu)化為允許屬于多個分類,變?yōu)轭愃茦撕灥母拍睢?br />2)商品的圖片存儲,由關(guān)聯(lián)表的一對多存儲,優(yōu)化為在商品表通過一個字段存儲 json?數(shù)組來保存圖片URL,這一優(yōu)化可以使后臺相關(guān)代碼簡化許多。
3)若干字段存儲不合理的問題,可能是由于開發(fā)過程中的需求調(diào)整,開發(fā)人員對增加的字段存儲欠考慮,存儲的位置和結(jié)構(gòu)存在問題,我們在重構(gòu)中進行了修正。
表和字段的命名方式,不管合不合理,畢竟我們是重構(gòu),不是推倒重做,所以沒有太大變化,繼承了過去的規(guī)則和方式,但是去掉了“jade_”?的表名前綴。
梳理數(shù)據(jù)庫之后,我們?yōu)閿?shù)據(jù)庫建立了完整的外鍵關(guān)聯(lián)關(guān)系,使用 Entity Framework?生成了實體模型:
第二步:遷移數(shù)據(jù)
在改造之后,需要將原數(shù)據(jù)庫中的數(shù)據(jù)完全遷移過來,考慮到表結(jié)構(gòu)發(fā)生了一定的變化,已無法簡單的數(shù)據(jù)導(dǎo)出導(dǎo)入,我們專門編寫了一系列的遷移腳本在上線前完成數(shù)據(jù)遷移工作。
第三步:.NET?版本后臺框架的搭建
首先我們確立重構(gòu)所要達到的目標:
1)完全兼容?Java 版本向 APP?端提供的 API?接口,不影響線上 APP?的正常使用。
2)針對后臺管理功能所提供的 API?接口,使用更規(guī)范的方式獨立實現(xiàn),不繼承 Java?版的后臺 API?接口。
3)后臺的全部重新實現(xiàn),包括全部 UI,Java?版本的 UI?有很多缺陷一直被客戶詬病,體驗不佳。
4)以最小的代價完成此次重構(gòu),計劃 2?個人,2個周末,共 8?人天的時間完成全部工作。
對以上問題,我們在開工前進行了簡要的分析,難度并不大,但是工作量不小。接口的問題,我們定義兩套,一套后臺使用,一套 APP?使用,給 APP?使用的接口,只需參照 Java?版代碼定義 DTO?對象,實現(xiàn)與數(shù)據(jù)庫實體對象的映射關(guān)系即可,后臺接口和整個后臺管理端UI部分,在過去我做的 .NET Web 項目上進行大幅簡化復(fù)用即可。
為了便于說明,我畫了一張簡要的結(jié)構(gòu)圖來說明兩套 Api :
項目的實際規(guī)模并不大,且由于我們力求最小成本,所以如圖所見,項目的結(jié)構(gòu)也同樣簡單,本次改造工作無需追求技術(shù)體系的先進性,能夠滿足項目的要求即可。
左邊后臺到 Api ,再到 Core?有現(xiàn)成的代碼可以復(fù)用,實現(xiàn)了出入?yún)f(xié)議、鑒權(quán)等基本功能,只需要實現(xiàn)業(yè)務(wù)邏輯即可,右側(cè) AppApi?部分,則需要多一層協(xié)議轉(zhuǎn)換工作,將原 Java?版本的出入?yún)f(xié)議轉(zhuǎn)換為 Core?要求的協(xié)議格式,而原 Java?版的 Api?接口協(xié)議并不統(tǒng)一,需要一個一個接口核查復(fù)刻。?
后臺解決方案結(jié)構(gòu)如下:
CCPRestSDK
? ? 我們使用的短信平臺的 SDK。
Jade.Core
? ? 業(yè)務(wù)邏輯層
Jade.Model
? ? 數(shù)據(jù)庫實體模型
Jade.Model.Dto
? ? 用于前后臺數(shù)據(jù)傳輸?shù)膶ο蠖x,包括針對 Model?的傳輸對象定義和其它需要傳遞的對象定義。
Sheng.Kernal
? ? 基礎(chǔ)類庫,提供了如反射,HTTP請求,對象映射等等基礎(chǔ)功能。在復(fù)用到本項目時經(jīng)過了簡化。
Sheng.Web.Infrastructure
? ? 用于 Web?項目的基礎(chǔ)類庫,提供了通用 Api?協(xié)議定義,控制器、DTO等共通的基礎(chǔ)功能,在復(fù)用到本項目時經(jīng)過了簡化。
? ? 這里包括一個專門為此項目寫的友盟推送實現(xiàn),友盟官方?jīng)]有提供 C#?版 SDK。
考慮到這個項目比較簡單,我在這里不再對基本技術(shù)體系做太多贅述,而是通過兩個簡單的請求過程進行闡述,代碼已經(jīng)開源在了 Github?上,可以下載代碼后根據(jù)下文進行查看。
Github:?https://github.com/iccb1013/Jade.Net
Api?的請求過程
在 Areas?下提供了 Api?和 AppApi?兩個區(qū)域提供接口,我們以 Api?下的 ProductController?為例,它向后臺 UI 提供產(chǎn)品相關(guān)的接口:
以 UpdateProduct?接口進行說明,此接口用于更新產(chǎn)品,此接口接收前端傳入的商品信息,并更新數(shù)據(jù)庫中的商品信息:
此接口的 RequestArgs?方法是接口 Controller?的基礎(chǔ) ApiBaseController?所提供的,用于把前端 Post? 過來的內(nèi)容反序列化成指定的對象。
Product_Info product = Mapper.Map<Product_Info>(args);?
作用是將 Dto?對象映射為數(shù)據(jù)庫實體對象,這里我們使用了開源組件 AutoMapper。
有關(guān) AutoMapper? 可以訪問:http://automapper.org/
引用 AutoMapper?組件后,只需定義不同對象間的映射規(guī)則即可,可適用于絕大多數(shù)情況,Product_Info?的映射規(guī)則如下:
接口在完成對象映射后,調(diào)用 Core?中的方法來實現(xiàn)業(yè)務(wù):
基本的 Entity Framework?操作,不作贅述。
這里有一個細節(jié),是圖中標出的??ShengMapper.SetValuesWithoutProperties?部分,這個方法把?傳入的 product?中數(shù)據(jù)拷貝到從數(shù)據(jù)庫取出來的 dbProduct?中,但是跳過2個指定的屬性和所有的虛屬性。
AutoMapper?不能定義同一個對象類型的映射規(guī)則,也不能靈活的在不同場景使用不同的規(guī)則,所以我寫了 ShengMapper?用于處理這種情況。
ShengMapper?也是開源的:Github:?https://github.com/iccb1013/Sheng.Mapper
此外可以留意到方法的返回對象是?NormalResult,這是一個 Core?層使用的一般返回對象:
相當于一個邏輯上不需要返回值的方法,但我們需要知道它的執(zhí)行狀態(tài),如:
有一些開發(fā)人員愛用 Exception?來返回業(yè)務(wù)結(jié)果,這樣做是非常不合適的,比如這里的?商品編碼重復(fù),他是一個業(yè)務(wù)操作的結(jié)果,并且這個結(jié)果是在我們的預(yù)期之內(nèi)的,不是一個?程序異常。
用異常來返回業(yè)務(wù)操作結(jié)果有兩個非常大的弊端,一是拋異常時非常影響性能,二是要區(qū)別對待真的程序異常和業(yè)務(wù)結(jié)果,也是十分麻煩的事情。總之,沒有理由這么做。
NormalResult?還提供了一個重載,可以返回指定類型的對象結(jié)果:
當 Core?層完成業(yè)務(wù)操作時,Controller?層的 API?會通過一個 ApiResult?對象來封裝 Api?接口的返回結(jié)果:
此處的 return ApiResult()?方法,是?Sheng.Web.Infrastructure?中的 BaseController?所提供的,可以處理大多數(shù) Api?返回結(jié)果:
?
如果都不能滿足,也可以手工 new?一個?ApiResult?返回:
Hint?只在部分特殊情況下使用,并不會為每種操作結(jié)果安排一個錯誤碼,對于我們的項目來說,多傳一些字節(jié)回去沒有問題,但有些特殊場景,前端需要知道具體的情況針對性處理,這時我們才使用錯誤碼。
Sheng.Web.Infrastructure?還提供了對于請求分頁列表數(shù)據(jù)的通用協(xié)議:
GetListDataArgs?對象使用一個?ParametersContainer?來存儲查詢條件,它其實是一個鍵值對。避免為每一個查詢定義強類型的查詢?nèi)雲(yún)ο?#xff0c;實在是太過麻煩了,也沒有必要。
我們通過這樣的方式來處理查詢條件即可:
下面我們再看一個 AppApi?的說明,我們還以產(chǎn)品信息為例,它的 Api?定義:
在 AppApi?中,為了兼容既有的接口約定,做了許多轉(zhuǎn)換工作:
把原 APP?接口的查詢條件,轉(zhuǎn)換為上文提到的 ParametersContainer:
這里把 APP?接口的列表查詢?nèi)雲(yún)?#xff0c;轉(zhuǎn)換為我們過去定義好的 GetListDataArgs:
這里做一些字段轉(zhuǎn)換時的特殊標記:
此外,原 Java?版在向 APP?提供接口時,提供給 APP?的 DTO?對象,十分詭異的和數(shù)據(jù)庫模型不一致,比如數(shù)據(jù)庫字段名有下劃線,但是 DTO?傳輸模型沒有,還有一些字段的命名則是完全不一樣,我們利用 AutoMapper?來逐一映射:
至此,項目的結(jié)構(gòu)已經(jīng)完全清楚,剩下的全部是業(yè)務(wù)邏輯層的業(yè)務(wù)操作。?
重構(gòu)工作的完成的效果:
前端 UI?的實現(xiàn)方法:
前端 UI?及腳本庫復(fù)用了我之前寫過的 Web?項目, Asp.net MVC,結(jié)合使用了前后端分離和 Razor?兩種方式。
Razor?引擎具有極高的開發(fā)效率,在做頁面數(shù)據(jù)展示時非常的方便,借助 Razor?和 Asp.net MVC?的布局頁和分布頁技術(shù),可以快速而有效的搭建頁面框架。
如下圖,定義了一個用于一般列表頁面的布局頁(模版):
可以輕松的看出頁面定義了大體結(jié)構(gòu):標題,副標題,按鈕,查詢區(qū),表格容器和分頁容器。表格容器和分頁容器并沒有使用 Razor,而是在具體視圖頁通過一般前后端分離的方式用腳本進行處理。
這是一個一般列表頁視圖實現(xiàn)的例子,基于上面的布局頁,代碼量就只有不到100行,就實現(xiàn)了一個普通列表頁面。
只需要初始化table,主要是定義這個頁面中表格所具備的列,查詢參數(shù)即可,另外在定義一個查詢條件區(qū),這個列表頁面就完成了。所有共通的功能都寫在了布局頁和共享的腳本文件中。
編輯和查看頁面也使用了同樣的處理方式,不再贅述。?
基于開發(fā)效率和實際項目需要考慮,我們這里沒有使用重量級的前端開發(fā)框架,而是復(fù)用了過去我寫的 js?腳本,這些腳本基于 jQuery?完成一些共通的功能來提高開發(fā)效率,如處理數(shù)據(jù)加載綁定,發(fā)起 Api?請求等操作。
common.js:
這里的 __getDto?和 _setDto?方法,搭配頁面 HTML?的特殊標記,可以實現(xiàn)前端對象的自動生成和綁定:
在 HTML?標簽中用 dtoproperty?屬性標記出 DTO?對象的屬性名后,使用 __getDto?方法即可自動生成前端對象。
使用 __setDto?方法,則可以快速把 Api?返回的對象,加載到前端控件中。
如上圖所示,前端畫面簡單的保存,加載數(shù)據(jù)就完成了。
listViewCommon2.js,這是用于一般表頁的腳本,基于這里的共通腳本,加上Razor?引擎的布局頁功能,實現(xiàn)了不到 100?行 HTML?和 JS?代碼即可完成一個列表頁面,當然,如果追求技術(shù)上的更加完美,可以繼續(xù)抽象,繼續(xù)封裝達到更好的效果:
editViewCommon.js ,這是用于一般編輯頁面的腳本:
你可以從 Github?上下載代碼之后在?Jade.Shell?的?Scripts?目前下查閱這些腳本。
整個項目從純技術(shù)角度來說還有許多提高改進的空間,但我們現(xiàn)在是做項目,不是做研究做框架產(chǎn)品,我們可以在未來的項目中通過項目推進的方式一步一步的提煉和完善我們的開發(fā)模式和技術(shù)體系。
最終,2個人,2個周末,在大量復(fù)用過去代碼的基礎(chǔ)上,只消耗了8人天完成了本次改造工作。
Github:?https://github.com/iccb1013/Jade.Net
本次工作之后的一點心得體會,主要是幾個失誤的地方:
1)前期將項目交給過去的同事來做,沒有過多關(guān)注,導(dǎo)致了項目上線前遭遇許多問題,卻得不到妥善解決,我個人秉承誠以待人的原則,用人不疑,疑人不用,這一點我想沒有錯,錯的是我完全放手沒有投入精力把控工程,除了報銷吃喝費用外基本不參與,這是一個教訓(xùn),無論何種情況,應(yīng)該一定程度的參與并把控好各項主要工作。
2)立項時預(yù)估后臺工作量只需1個人月,為了分擔人員風險,我還是多付了一個人的費用安排2個人來做,但在人員使用上,沒有做到風險規(guī)避。
3)過早的結(jié)清了人員費用,導(dǎo)致工作無法順利推進,對于外包項目,費用結(jié)算一定要有計劃性,包括預(yù)留尾款。
最后我想談一談技術(shù)人員的“人設(shè)”問題,這是我從此項目上深刻認識到的一個問題。
我做了超過十年的技術(shù)研發(fā)工作,但同時許多朋友說我不像一個程序員,我并不認為程序員一定要雙肩包,開口只談技術(shù),相反隨著年齡和閱歷的增長,我一天比一天認識到業(yè)務(wù)、行業(yè)的重要性,我很早就知道做技術(shù)是為了什么,做技術(shù)不是為了做技術(shù),而是為了服務(wù)我們的客戶,服務(wù)社會,服務(wù)一切需要的人,我更關(guān)心的是我要做什么事情,我的目標是什么。一直以來我認為這是一個技術(shù)人員轉(zhuǎn)變和提高的核心觀念。
但是最近一到兩年,我慢慢意識到,有時我們需要讓自己契合對方心理上的某種“人設(shè)”,就這個玉雕工作室的項目來說,我和客戶談需求和業(yè)務(wù)比較多,吃飯喝酒比較多,加上不那么技術(shù)的形象(長頭發(fā),扎辮子),導(dǎo)致客戶始終不認為我是一個技術(shù)人員,當然我也不太在意這一點,但是,后來我意識到一些問題。
對于這種小型外包項目,特別是還沒有接下來之前,客戶最在意的還是我們有沒有技術(shù)實力做好,能不能給我們做,這里有一個角色帶入的問題,這時我不是供職于大公司的項目經(jīng)理服務(wù)于既有客戶,扎辮子花衣服做需求也不是不可以,但是當時我是一個要接項目的人,在互相不了解的情況下,如何快速建立信任?最簡單的辦法是讓自己契合對方心理上的“人設(shè)”:我就是你要找的人。
許多客戶包括企業(yè)領(lǐng)導(dǎo)層,對技術(shù)人員都有自己所理解的“人設(shè)”,客戶不懂技術(shù),領(lǐng)導(dǎo)也許也不那么懂技術(shù),他們要找有技術(shù)實力的人,怎么辦呢,說白了,憑感覺。
玉雕這個項目直到原來 Java?版后臺的兩個開發(fā)人員撂挑子,客戶都感嘆他們技術(shù)好,客戶是做工藝品的,和IT技術(shù)八桿子打不著邊,為什么?這件事讓我反思了很久,就是“人設(shè)”。
于是我剪了頭發(fā),換上襯衫,買一個瑞士軍刀電腦包(笑)。以便于我在不同的時候有不同的人設(shè)。
當然更重要的是對于技術(shù)和業(yè)務(wù)的種種理念,在和不同的人表達的時候,要特別注意表達的方式和技巧,以及表達到什么度。對于水平比自己高的人,可以隨意表達自己的想法,不要怕,但是遇到經(jīng)驗或能力不如自己的人時,要特別小心,因為對方可能無法體悟你的意思。
比如和同樣一個做過十年項目的老鳥說一句:技術(shù)不是那么重要,也許雙方可以會心一笑,重要的是彼此知道我們所說的技術(shù)不重要的點,技術(shù)不重要的度在哪里。但是如果和一個沒有太多各種項目經(jīng)驗的人說這樣的話,可能是不合適的,對方會不能理解,進行主觀的判斷你不行,因為你不技術(shù)。
唯一的辦法是不要太個性,要契合別人的人設(shè),不管他有沒有道理。"木秀于林風必摧之",瑞士軍刀電腦包該背還是要背(笑)。
本文聯(lián)合作者:
曹旭升
QQ:279060597
Email:cao.silhouette@msn.com
http://blog.shengxunwei.com
范大宏
QQ:237194340
Email:whoarefan@gmail.com
?
歡迎朋友們加入我們的微信群:?
?
?
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的8/人天,小记一次 JAVA(APP后台) 项目改造 .NET 过程(后台代码已完整开源于 Github)...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VeeValidate在vue项目里表单
- 下一篇: SSM项目使用GoEasy 实现web消