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