Java生鲜电商平台-App系统架构开发与设计
Java生鮮電商平臺(tái)-App系統(tǒng)架構(gòu)開(kāi)發(fā)與設(shè)計(jì)
?
說(shuō)明:閱讀此文,你可以學(xué)習(xí)到以下的技術(shù)分享
?
1.Java生鮮電商平臺(tái)-App架構(gòu)設(shè)計(jì)經(jīng)驗(yàn)談:接口的設(shè)計(jì)
2.Java生鮮電商平臺(tái)-App架構(gòu)設(shè)計(jì)經(jīng)驗(yàn)談:技術(shù)選型
3.Java生鮮電商平臺(tái)-App架構(gòu)設(shè)計(jì)經(jīng)驗(yàn)談:數(shù)據(jù)層的設(shè)計(jì)
4.Java生鮮電商平臺(tái)-App架構(gòu)設(shè)計(jì)經(jīng)驗(yàn)談:業(yè)務(wù)層的設(shè)計(jì)
5.Java生鮮電商平臺(tái)-App架構(gòu)設(shè)計(jì)經(jīng)驗(yàn)談:展示層的設(shè)計(jì)
?
接口設(shè)計(jì):
?
App與服務(wù)器的通信接口如何設(shè)計(jì)得好,需要考慮的地方挺多的,在此根據(jù)我的一些經(jīng)驗(yàn)做一些總結(jié)分享,旨在拋磚引玉。
安全機(jī)制的設(shè)計(jì)
現(xiàn)在,大部分App的接口都采用RESTful架構(gòu),RESTFul最重要的一個(gè)設(shè)計(jì)原則就是,客戶端與服務(wù)器的交互在請(qǐng)求之間是無(wú)狀態(tài)的,也就是說(shuō),當(dāng)涉及到用戶狀態(tài)時(shí),每次請(qǐng)求都要帶上身份驗(yàn)證信息。實(shí)現(xiàn)上,大部分都采用token的認(rèn)證方式,一般流程是:
用戶用密碼登錄成功后,服務(wù)器返回token給客戶端;
客戶端將token保存在本地,發(fā)起后續(xù)的相關(guān)請(qǐng)求時(shí),將token發(fā)回給服務(wù)器;
服務(wù)器檢查token的有效性,有效則返回?cái)?shù)據(jù),若無(wú)效,分兩種情況:
token錯(cuò)誤,這時(shí)需要用戶重新登錄,獲取正確的token
token過(guò)期,這時(shí)客戶端需要再發(fā)起一次認(rèn)證請(qǐng)求,獲取新的token
然而,此種驗(yàn)證方式存在一個(gè)安全性問(wèn)題:當(dāng)?shù)卿浗涌诒唤俪謺r(shí),黑客就獲取到了用戶密碼和token,后續(xù)則可以對(duì)該用戶做任何事情了。用戶只有修改密碼才能奪回控制權(quán)。
如何優(yōu)化呢?第一種解決方案是采用HTTPS。HTTPS在HTTP的基礎(chǔ)上添加了SSL安全協(xié)議,自動(dòng)對(duì)數(shù)據(jù)進(jìn)行了壓縮加密,在一定程序可以防止監(jiān)聽(tīng)、防止劫持、防止重發(fā),安全性可以提高很多。不過(guò),SSL也不是絕對(duì)安全的,也存在被劫持的可能。另外,服務(wù)器對(duì)HTTPS的配置相對(duì)有點(diǎn)復(fù)雜,還需要到CA申請(qǐng)證書(shū),而且一般還是收費(fèi)的。而且,HTTPS效率也比較低。一般,只有安全要求比較高的系統(tǒng)才會(huì)采用HTTPS,比如銀行。而大部分對(duì)安全要求沒(méi)那么高的App還是采用HTTP的方式。
我們目前的做法是給每個(gè)接口都添加簽名。給客戶端分配一個(gè)密鑰,每次請(qǐng)求接口時(shí),將密鑰和所有參數(shù)組合成源串,根據(jù)簽名算法生成簽名值,發(fā)送請(qǐng)求時(shí)將簽名一起發(fā)送給服務(wù)器驗(yàn)證。類(lèi)似的實(shí)現(xiàn)可參考OAuth1.0的簽名算法。這樣,黑客不知道密鑰,不知道簽名算法,就算攔截到登錄接口,后續(xù)請(qǐng)求也無(wú)法成功操作。不過(guò),因?yàn)楹灻惴ū容^麻煩,而且容易出錯(cuò),只適合對(duì)內(nèi)的接口。如果你們的接口屬于開(kāi)放的API,則不太適合這種簽名認(rèn)證的方式了,建議還是使用OAuth2.0的認(rèn)證機(jī)制。
我們也給每個(gè)端分配一個(gè)appKey,比如Android、iOS、微信三端,每個(gè)端分別分配一個(gè)appKey和一個(gè)密鑰。沒(méi)有傳appKey的請(qǐng)求將報(bào)錯(cuò),傳錯(cuò)了appKey的請(qǐng)求也將報(bào)錯(cuò)。這樣,安全性方面又加多了一層防御,同時(shí)也方便對(duì)不同端做一些不同的處理策略。
另外,現(xiàn)在越來(lái)越多App取消了密碼登錄,而采用手機(jī)號(hào)+短信驗(yàn)證碼的登錄方式,我在當(dāng)前的項(xiàng)目中也采用了這種登錄方式。這種登錄方式有幾種好處:
不需要注冊(cè),不需要修改密碼,也不需要因?yàn)橥浢艽a而重置密碼的操作了;
用戶不再需要記住密碼了,也不怕密碼泄露的問(wèn)題了;
相對(duì)于密碼登錄其安全性明顯提高了。
接口數(shù)據(jù)的設(shè)計(jì)
接口的數(shù)據(jù)一般都采用JSON格式進(jìn)行傳輸,不過(guò),需要注意的是,JSON的值只有六種數(shù)據(jù)類(lèi)型:
Number:整數(shù)或浮點(diǎn)數(shù)
String:字符串
Boolean:true 或 false
Array:數(shù)組包含在方括號(hào)[]中
Object:對(duì)象包含在大括號(hào){}中
Null:空類(lèi)型
所以,傳輸?shù)臄?shù)據(jù)類(lèi)型不能超過(guò)這六種數(shù)據(jù)類(lèi)型。以前,我們?cè)?jīng)試過(guò)傳輸Date類(lèi)型,它會(huì)轉(zhuǎn)為類(lèi)似于”2016年1月7日 09時(shí)17分42秒 GMT+08:00”這樣的字符串,這在轉(zhuǎn)換時(shí)會(huì)產(chǎn)生問(wèn)題,不同的解析庫(kù)解析方式可能不同,有的可能會(huì)轉(zhuǎn)亂,有的可能直接異常了。要避免出錯(cuò),必須做特殊處理,自己手動(dòng)去做解析。為了根除這種問(wèn)題,最好的解決方案是用毫秒數(shù)表示日期。
另外,以前的項(xiàng)目中還出現(xiàn)過(guò)字符串的”true”和”false”,或者字符串的數(shù)字,甚至還出現(xiàn)過(guò)字符串的”null”,導(dǎo)致解析錯(cuò)誤,尤其是”null”,導(dǎo)致App奔潰,后來(lái)查了好久才查出來(lái)是該問(wèn)題導(dǎo)致的。這都是因?yàn)榉?wù)端對(duì)數(shù)據(jù)沒(méi)處理好,導(dǎo)致有些數(shù)據(jù)轉(zhuǎn)為了字符串。所以,在客戶端,也不能完全信任服務(wù)端傳回的數(shù)據(jù)都是對(duì)的,需要對(duì)所有異常情況都做相應(yīng)處理。
服務(wù)器返回的數(shù)據(jù)結(jié)構(gòu),一般為:
{
code:0,
message: "success",
data: { key1: value1, key2: value2, ... }
}
code: 返回碼,0表示成功,非0表示各種不同的錯(cuò)誤
message: 描述信息,成功時(shí)為”success”,錯(cuò)誤時(shí)則是錯(cuò)誤信息
data: 成功時(shí)返回的數(shù)據(jù),類(lèi)型為對(duì)象或數(shù)組
不同錯(cuò)誤需要定義不同的返回碼,屬于客戶端的錯(cuò)誤和服務(wù)端的錯(cuò)誤也要區(qū)分,比如1XX表示客戶端的錯(cuò)誤,2XX表示服務(wù)端的錯(cuò)誤。這里舉幾個(gè)例子:
0:成功
100:請(qǐng)求錯(cuò)誤
101:缺少appKey
102:缺少簽名
103:缺少參數(shù)
200:服務(wù)器出錯(cuò)
201:服務(wù)不可用
202:服務(wù)器正在重啟
錯(cuò)誤信息一般有兩種用途:一是客戶端開(kāi)發(fā)人員調(diào)試時(shí)看具體是什么錯(cuò)誤;二是作為App錯(cuò)誤提示直接展示給用戶看。主要還是作為App錯(cuò)誤提示,直接展示給用戶看的。所以,大部分都是簡(jiǎn)短的提示信息。
data字段只在請(qǐng)求成功時(shí)才會(huì)有數(shù)據(jù)返回的。數(shù)據(jù)類(lèi)型限定為對(duì)象或數(shù)組,當(dāng)請(qǐng)求需要的數(shù)據(jù)為單個(gè)對(duì)象時(shí)則傳回對(duì)象,當(dāng)請(qǐng)求需要的數(shù)據(jù)是列表時(shí),則為某個(gè)對(duì)象的數(shù)組。這里需要注意的就是,不要將data傳入字符串或數(shù)字,即使請(qǐng)求需要的數(shù)據(jù)只有一個(gè),比如token,那返回的data應(yīng)該為:
// 正確
data: { token: 123456 }
// 錯(cuò)誤
data: 123456
接口版本的設(shè)計(jì)
接口不可能一成不變,在不停迭代中,總會(huì)發(fā)生變化。接口的變化一般會(huì)有幾種:
數(shù)據(jù)的變化,比如增加了舊版本不支持的數(shù)據(jù)類(lèi)型
參數(shù)的變化,比如新增了參數(shù)
接口的廢棄,不再使用該接口了
為了適應(yīng)這些變化,必須得做接口版本的設(shè)計(jì)。實(shí)現(xiàn)上,一般有兩種做法:
每個(gè)接口有各自的版本,一般為接口添加個(gè)version的參數(shù)。
整個(gè)接口系統(tǒng)有統(tǒng)一的版本,一般在URL中添加版本號(hào),比如http://api.domain.com/v2。
大部分情況下會(huì)采用第一種方式,當(dāng)某一個(gè)接口有變動(dòng)時(shí),在這個(gè)接口上疊加版本號(hào),并兼容舊版本。App的新版本開(kāi)發(fā)傳參時(shí)則將傳入新版本的version。
如果整個(gè)接口系統(tǒng)的根基都發(fā)生變動(dòng)的話,比如微博API,從OAuth1.0升級(jí)到OAuth2.0,整個(gè)API都進(jìn)行了升級(jí)。
有時(shí)候,一個(gè)接口的變動(dòng)還會(huì)影響到其他接口,但做的時(shí)候不一定能發(fā)現(xiàn)。因此,最好還要有一套完善的測(cè)試機(jī)制保證每次接口變更都能測(cè)試到所有相關(guān)層面。
?
技術(shù)選型:
?
當(dāng)你做架構(gòu)設(shè)計(jì)時(shí),必然會(huì)面臨技術(shù)選型的抉擇,不同的技術(shù)方案,架構(gòu)也可能完全不同。有哪些技術(shù)選型需要做決策呢?比如,App是純?cè)_(kāi)發(fā),還是Web App,抑或Hybrid App?iOS開(kāi)發(fā),語(yǔ)言上是選擇Objective-C還是Swift?架構(gòu)模式用MVC,還是MVP,或者M(jìn)VVM?下面根據(jù)我的一些經(jīng)驗(yàn)對(duì)某些方面做點(diǎn)總結(jié)分享。
原生/H5
關(guān)于用原生好,還是用H5好的爭(zhēng)論從沒(méi)間斷過(guò)。但我覺(jué)得,脫離了實(shí)際場(chǎng)景來(lái)討論孰好孰壞意義不大。就說(shuō)我們目前正在做的項(xiàng)目,先說(shuō)明下背景:
不止要做Android和iOS App,也要做微信公眾號(hào);
H5人員缺乏,只有一兩個(gè)兼職的可用,而且不可控因素很高;
我們對(duì)原生比較熟;
開(kāi)發(fā)時(shí)間只有半個(gè)月。
首先,需求上來(lái)說(shuō),大部分頁(yè)面用H5實(shí)現(xiàn),可以減少很多工作量。但因?yàn)椴豢煽匾蛩靥?#xff0c;而時(shí)間又短,風(fēng)險(xiǎn)太大。而我們對(duì)原生比較熟,開(kāi)發(fā)效率比較高,很多東西我也控制得了,風(fēng)險(xiǎn)相對(duì)比較低。而且,我們的主推產(chǎn)品是App,微信屬于輔助性產(chǎn)品,所以,微信要求也沒(méi)那么高。因此,我決定以原生為主,H5為輔,App大部分頁(yè)面用原生完成,小部分用WebView加載H5。
另外,WebView加載H5也有兩種模式,一種是加載服務(wù)器的H5頁(yè)面,一種是加載本地的H5頁(yè)面。加載服務(wù)器的H5頁(yè)面比較簡(jiǎn)單,WebView只要load一下URL就可以了。加載本地的H5頁(yè)面,則需要將H5文件存放在本地,包括關(guān)聯(lián)的CSS和JS文件。這種方式相對(duì)比較復(fù)雜,不過(guò),加載速度會(huì)比第一種快很多。我們當(dāng)前項(xiàng)目基于上面考慮,只能選擇第一種方案。
如果人員和時(shí)間資源充足的話,那又如何選型呢?毫無(wú)疑問(wèn),我會(huì)以H5為主,微信和App都有的頁(yè)面統(tǒng)一用H5,App專(zhuān)有的部分,比如導(dǎo)航欄、標(biāo)題欄、登錄等,才用原生實(shí)現(xiàn)。另外,WebView里的H5有點(diǎn)擊事件時(shí),也許是URL鏈接,也許是調(diào)用JS的,都不會(huì)讓它直接在該WebView里做跳轉(zhuǎn),需要攔截下來(lái)做些原生處理后跳轉(zhuǎn)到一個(gè)新的原生頁(yè)面,原生頁(yè)面也許嵌入另一個(gè)WebView,用來(lái)展示新的H5頁(yè)面。這是簡(jiǎn)單的例子,關(guān)于Hybrid App詳細(xì)的設(shè)計(jì),以后再講。另外,關(guān)于H5,絕對(duì)是大趨勢(shì),強(qiáng)烈建議所有App開(kāi)發(fā)人員都去學(xué)習(xí)。
Objective-C/Swift
我在項(xiàng)目中選擇了Swift,主要基于三個(gè)原因:
Swift真的很簡(jiǎn)潔,生產(chǎn)效率很高;
Swift取代Objective-C是必然的趨勢(shì);
目前iOS只有我一個(gè)人開(kāi)發(fā),不需要顧慮到團(tuán)隊(duì)里沒(méi)人懂Swift。
如果你的團(tuán)隊(duì)里沒(méi)人懂Swift,那還是乖乖用Objective-C吧;如果有一兩個(gè)懂Swift的,那可以混合開(kāi)發(fā),并讓不懂的人盡快學(xué)會(huì)Swift;如果都懂了,不用想了,直接上Swift吧。
當(dāng)語(yǔ)言上選擇了Swift,相應(yīng)的一些第三方庫(kù)也面臨著選型。比如,依賴庫(kù)管理,Objective-C時(shí)代大部分用CocoaPods,Swift時(shí)代,我更喜歡Carthage。Carhage是用Swift寫(xiě)的,和CocoaPods相比,輕耦合,也更靈活。我個(gè)人也不太喜歡CocoaPods,使用起來(lái)比較麻煩,耦合性也較高,我使用過(guò)程中也經(jīng)常出問(wèn)題,而且還總是不知道該怎么解決,要移除時(shí)也是非常麻煩。
再推薦幾個(gè)關(guān)于Swift的第三方庫(kù):
Alamofire:Swift版本的網(wǎng)絡(luò)基礎(chǔ)庫(kù),和AFNetworking是同一個(gè)作者
AlamofireImage:基于Alamofire的圖片加載庫(kù)
ObjectMapper:Swift版本的Json和Model轉(zhuǎn)換庫(kù)
AlamofireObjectMapper:Alamofire的擴(kuò)展庫(kù),結(jié)合了ObjectMapper,自動(dòng)將JSON的Response數(shù)據(jù)轉(zhuǎn)換為了Swift對(duì)象
MVC/MVP/MVVM
先分別簡(jiǎn)單介紹下這三個(gè)架構(gòu)模式吧:
MVC:Model-View-Controller,經(jīng)典模式,很容易理解,主要缺點(diǎn)有兩個(gè):
View對(duì)Model的依賴,會(huì)導(dǎo)致View也包含了業(yè)務(wù)邏輯;
Controller會(huì)變得很厚很復(fù)雜。
MVP:Model-View-Presenter,MVC的一個(gè)演變模式,將Controller換成了Presenter,主要為了解決上述第一個(gè)缺點(diǎn),將View和Model解耦,不過(guò)第二個(gè)缺點(diǎn)依然沒(méi)有解決。
MVVM:Model-View-ViewModel,是對(duì)MVP的一個(gè)優(yōu)化模式,采用了雙向綁定:View的變動(dòng),自動(dòng)反映在ViewModel,反之亦然。
架構(gòu)模式上,我不會(huì)推崇說(shuō)哪種模式好,每種模式都各有優(yōu)點(diǎn),也各有極限性。越高級(jí)的模式復(fù)雜性越高,實(shí)現(xiàn)起來(lái)也越難。最近火熱的微服務(wù)架構(gòu),比起MVC,復(fù)雜度不知增加了多少倍。
我在實(shí)際項(xiàng)目中思考架構(gòu)時(shí),也不會(huì)想著要用哪種模式,我只思考現(xiàn)階段,以現(xiàn)有的人力資源和時(shí)間資源,如何才能更快更好地完成需求,適當(dāng)考慮下如何為后期擴(kuò)展或重構(gòu)做準(zhǔn)備。就說(shuō)我前段時(shí)間分享的Android項(xiàng)目重構(gòu)之路系列中講的那個(gè)架構(gòu),確切地說(shuō),都不屬于上面三種架構(gòu)模式之一。
?
數(shù)據(jù)層的設(shè)計(jì)
一個(gè)App,從根本上來(lái)說(shuō),就是對(duì)數(shù)據(jù)的處理,包括數(shù)據(jù)從哪里來(lái)、數(shù)據(jù)如何組織、數(shù)據(jù)怎么展示,從職責(zé)上劃分就是:數(shù)據(jù)管理、數(shù)據(jù)加工、數(shù)據(jù)展示。相對(duì)應(yīng)的也就有了三層架構(gòu):數(shù)據(jù)層、業(yè)務(wù)層、展示層。本文就先講講數(shù)據(jù)層的設(shè)計(jì)。
數(shù)據(jù)層,是三層架構(gòu)中的最底層,負(fù)責(zé)數(shù)據(jù)的管理。它主要的任務(wù)就是:
調(diào)用網(wǎng)絡(luò)API,獲取數(shù)據(jù);
將數(shù)據(jù)緩存到本地;
將數(shù)據(jù)交付給上一層。
根據(jù)這三個(gè)任務(wù),數(shù)據(jù)層可以再拆分為三層:網(wǎng)絡(luò)層、本地?cái)?shù)據(jù)層、交付層。
網(wǎng)絡(luò)層
網(wǎng)絡(luò)層主要就是對(duì)網(wǎng)絡(luò)API的封裝。關(guān)于API的設(shè)計(jì),該系列的第一篇文章接口的設(shè)計(jì)已經(jīng)講過(guò)一些。關(guān)于如何封裝,可以參考Android項(xiàng)目重構(gòu)之路系列的架構(gòu)篇和實(shí)現(xiàn)篇,其中接口層和本文的網(wǎng)絡(luò)層是一樣的。
還有一些在前面的文章中沒(méi)有提及到的,在此做一些補(bǔ)充。
首先是不同網(wǎng)絡(luò)狀態(tài)的處理。當(dāng)網(wǎng)絡(luò)不可用時(shí),則不應(yīng)該再去調(diào)用API;當(dāng)網(wǎng)絡(luò)可用,但不是WIFI時(shí),有些比較耗流量的操作也應(yīng)該禁止,比如上傳和下載大文件;當(dāng)網(wǎng)絡(luò)狀態(tài)不同時(shí),還可以采用不同的網(wǎng)絡(luò)策略,比如,當(dāng)網(wǎng)絡(luò)為WIFI時(shí),當(dāng)前API可以返回更多更全面的數(shù)據(jù),還可以預(yù)先加載相關(guān)聯(lián)的其他API。
其次,為了節(jié)省流量,接口的設(shè)計(jì)上可以對(duì)數(shù)據(jù)進(jìn)行簡(jiǎn)化。例如,對(duì)于一些列表類(lèi)的接口,可以這么設(shè)計(jì):只返回更新的部分,比如,上一次請(qǐng)求返回了10條按時(shí)間排序的數(shù)據(jù),第一條數(shù)據(jù)為最新的,id為101,當(dāng)發(fā)起下一次請(qǐng)求時(shí),將101的id作為參數(shù)調(diào)用API,API查到該id,發(fā)現(xiàn)該id之后又新增了兩條數(shù)據(jù),API則只返回新增的這兩條數(shù)據(jù)。
另外,為了保證程序的健壯性,調(diào)用API時(shí),對(duì)入?yún)⒌暮戏ㄐ詸z查也是很有必要的。而且,也應(yīng)該定義好本地的錯(cuò)誤碼和錯(cuò)誤信息,保證每個(gè)錯(cuò)誤都能正常解析。
本地?cái)?shù)據(jù)層
本地?cái)?shù)據(jù)層主要就是做緩存處理,這需要設(shè)計(jì)好一套緩存策略。設(shè)計(jì)緩存策略時(shí),有幾個(gè)問(wèn)題需要考慮清楚:
哪些需要緩存?哪些不需要緩存?
緩存在哪里?數(shù)據(jù)庫(kù)?文件?還是內(nèi)存?
緩存時(shí)間多長(zhǎng)?
哪些需要緩存?
將所有數(shù)據(jù)都緩存是不明智的,不同的數(shù)據(jù)應(yīng)該有不同的緩存策略,比如一個(gè)電商App,首頁(yè)的商品列表數(shù)據(jù)應(yīng)該緩存,而且緩存時(shí)間應(yīng)該比較長(zhǎng),而每個(gè)商品的詳情數(shù)據(jù)就沒(méi)必要緩存或緩存時(shí)間很短。對(duì)于一份數(shù)據(jù)需不需要緩存,判斷標(biāo)準(zhǔn)可以是:用戶查看該數(shù)據(jù)的頻率高不高?首頁(yè)商品列表是用戶每次啟動(dòng)都會(huì)看到的,而每個(gè)商品的詳情用戶最多只看幾次。
緩存在哪里?
從內(nèi)存讀取數(shù)據(jù)是最快的,但內(nèi)存非常有限。因此,內(nèi)存一般只用來(lái)緩存使用頻率非常高的數(shù)據(jù)。
文件緩存主要就是圖片、音頻、視頻了。
數(shù)據(jù)庫(kù)可以保存大量數(shù)據(jù),主要就是用于保存商品列表、聊天記錄之類(lèi)的關(guān)系型數(shù)據(jù)。
然而,不管緩存在哪里,都需要限定好緩存的容量,要定期清理,不然會(huì)越積越多。
緩存時(shí)間多長(zhǎng)?
首先,每份緩存數(shù)據(jù)都應(yīng)該設(shè)置一個(gè)緩存的有效時(shí)間,有效期的起始時(shí)間以最后一次被調(diào)用的時(shí)間為準(zhǔn),當(dāng)該數(shù)據(jù)長(zhǎng)時(shí)間沒(méi)有再被調(diào)用到時(shí),就應(yīng)該從緩存中清理掉。
緩存的有效時(shí)間應(yīng)該設(shè)多長(zhǎng)呢?可以短至一分鐘,長(zhǎng)至一星期甚至一個(gè)月,具體因數(shù)據(jù)而異。一般內(nèi)存的緩存時(shí)間不宜太長(zhǎng),程序退出基本就要全部清理了。文件緩存可以設(shè)置保留一天或一個(gè)星期,可以每隔一天清理一次。數(shù)據(jù)庫(kù)緩存再久一些也無(wú)所謂,但最好還是不要超過(guò)一個(gè)月。
交付層
交付層其實(shí)就是一個(gè)向上層開(kāi)放的交互接口層,是上層向數(shù)據(jù)層獲取數(shù)據(jù)的入口。上層向數(shù)據(jù)層請(qǐng)求數(shù)據(jù),它是不關(guān)心數(shù)據(jù)層的數(shù)據(jù)是從緩存獲取還是從網(wǎng)絡(luò)獲取的,它只關(guān)心結(jié)果,數(shù)據(jù)層能給到它想要的數(shù)據(jù)結(jié)果就OK了。因此,交付層主要就是定義一堆開(kāi)放的接口或協(xié)議。
如果接口或協(xié)議非常多,那么,將接口或協(xié)議按照模塊劃分也是有必要的。比如微信,按模塊劃分有:IM、公眾號(hào)、朋友圈、錢(qián)包、購(gòu)物、游戲等等。模塊之間應(yīng)該盡量相對(duì)獨(dú)立、松耦合。
?
業(yè)務(wù)層的設(shè)計(jì)
業(yè)務(wù)層其實(shí)并不復(fù)雜,但是大部分開(kāi)發(fā)人員對(duì)其職責(zé)并沒(méi)有理解清楚,從而使其淪落為一個(gè)數(shù)據(jù)中轉(zhuǎn)站。我之前分享過(guò)的Android項(xiàng)目重構(gòu)之路系列中提到的核心層,其實(shí)就是這里所講的業(yè)務(wù)層。但有不少讀者反映,他們?cè)趯?shí)際項(xiàng)目中就只是做一下參數(shù)檢查,然后直接調(diào)用API,與展示層對(duì)接的接口基本也與API的接口一致的。這樣,業(yè)務(wù)層無(wú)疑就已經(jīng)變?yōu)榱艘粋€(gè)數(shù)據(jù)中轉(zhuǎn)站。
業(yè)務(wù)層的職責(zé)
所以,設(shè)計(jì)業(yè)務(wù)層之前,對(duì)業(yè)務(wù)層的職責(zé)要先真正理解清楚。這里,我舉兩個(gè)栗子說(shuō)明一下。
第一個(gè)是新用戶注冊(cè)的例子。注冊(cè)時(shí),界面上一般都會(huì)要求用戶輸入手機(jī)號(hào)、驗(yàn)證碼、密碼和確認(rèn)密碼。但是,API接口一般只會(huì)有三個(gè)參數(shù):手機(jī)號(hào)、驗(yàn)證碼和密碼,不會(huì)有確認(rèn)密碼。因此,調(diào)用接口之前,密碼和確認(rèn)密碼的一致性檢查是必須的。同時(shí),也要檢查這些數(shù)據(jù)是否為空、手機(jī)號(hào)是否符合規(guī)范、驗(yàn)證碼是否有效、密碼有沒(méi)有包含了特殊字符等。正確姿勢(shì)就是當(dāng)所有檢查都通過(guò)了之后,才調(diào)用API接口。最后,調(diào)用注冊(cè)接口成功后,可能還要再調(diào)用一次登錄接口,并可能將用戶登錄信息緩存起來(lái),方便用戶下次啟動(dòng)應(yīng)用時(shí)自動(dòng)登錄。所有這些都屬于業(yè)務(wù)邏輯處理,也就是業(yè)務(wù)層的工作。
第二個(gè)是涉及用戶驗(yàn)證的例子。比如,在一個(gè)電商App,當(dāng)用戶瀏覽某個(gè)商品,點(diǎn)擊購(gòu)買(mǎi)時(shí),App首先會(huì)判斷用戶是否已經(jīng)登錄,如未登錄,則會(huì)跳轉(zhuǎn)到登錄頁(yè)面讓用戶先登錄。如果已經(jīng)登錄,但token已經(jīng)過(guò)期,那需要先去獲取新的token,之后才能進(jìn)行下一步的購(gòu)物操作。這些邏輯處理,也是業(yè)務(wù)層的工作。
因此,簡(jiǎn)單點(diǎn)說(shuō),業(yè)務(wù)層就是處理業(yè)務(wù)邏輯,包括數(shù)據(jù)的檢查、業(yè)務(wù)分支的處理等。比如上面第二個(gè)例子,可能很多人就會(huì)將用戶是否已經(jīng)登錄的判斷直接在界面上做處理,當(dāng)確認(rèn)登錄后,token也是有效的之后,才調(diào)用業(yè)務(wù)層做購(gòu)買(mǎi)商品的操作,這就是導(dǎo)致業(yè)務(wù)層淪落為API的數(shù)據(jù)中轉(zhuǎn)站的直接表現(xiàn)。
業(yè)務(wù)層的交互
只有真正理解了業(yè)務(wù)層的職責(zé)之后,才能有效地設(shè)計(jì)業(yè)務(wù)層與外層的交互接口。
業(yè)務(wù)層向下,與數(shù)據(jù)層交互;向上,與展示層交互。
與數(shù)據(jù)層交互只是調(diào)用數(shù)據(jù)層的接口獲取數(shù)據(jù),而與展示層交互則需要提供接口給展示層調(diào)用。因?yàn)闃I(yè)務(wù)處理一般屬于比較耗時(shí)的操作,主要在于底層的網(wǎng)絡(luò)請(qǐng)求比較耗時(shí),所以提供給展示層的接口數(shù)據(jù)結(jié)果應(yīng)該以異步的方式提供,因此,接口上就需要提供個(gè)回調(diào)參數(shù),返回業(yè)務(wù)處理之后的結(jié)果。我之前分享過(guò)的Android項(xiàng)目重構(gòu)之路:實(shí)現(xiàn)篇有講到一種實(shí)現(xiàn)方式,可參考。
?
展示層的設(shè)計(jì)
三層架構(gòu)中,數(shù)據(jù)層和業(yè)務(wù)層都已經(jīng)做過(guò)了簡(jiǎn)單的分享,最后,就剩下展示層了。本篇就給各位分享下我在展示層設(shè)計(jì)方面的一些經(jīng)驗(yàn)心得。
展示層是三層架構(gòu)中最復(fù)雜的一層了,需要考慮的包括但不限于界面布局、屏幕適配、文字大小、顏色、圖片資源、提示信息、動(dòng)畫(huà)等等。展示層也是變化最頻繁的一個(gè)層面,每天改得最多的就是界面了。因此,展示層也是最容易變得混亂不堪的一個(gè)層面。一個(gè)良好的展示層,應(yīng)該有較好的可讀性、健壯性、維護(hù)性、擴(kuò)展性。
三原則
我在Android項(xiàng)目重構(gòu)之路:界面篇中提到過(guò)三個(gè)原則,要設(shè)計(jì)好展示層,至少需要遵循好這三條基本的原則:
保持規(guī)范性:定義好開(kāi)發(fā)規(guī)范,包括書(shū)寫(xiě)規(guī)范、命名規(guī)范、注釋規(guī)范等,并按照規(guī)范嚴(yán)格執(zhí)行;
保持單一性:布局就只做布局,內(nèi)容就只做內(nèi)容,各自分離好,每個(gè)方法、每個(gè)類(lèi),也只做一件事情;
保持簡(jiǎn)潔性:保持代碼和結(jié)構(gòu)的簡(jiǎn)潔,每個(gè)方法,每個(gè)類(lèi),每個(gè)包,每個(gè)文件,都不要塞太多代碼或資源,感覺(jué)多了就應(yīng)該拆分。
關(guān)于這三個(gè)原則詳細(xì)的解說(shuō),界面篇已經(jīng)講過(guò)的,我這里就不再重復(fù)。在此,我只做些補(bǔ)充。
關(guān)于規(guī)范,Android方面,我已經(jīng)分享過(guò)一套Android技術(shù)積累:開(kāi)發(fā)規(guī)范,主要分為書(shū)寫(xiě)規(guī)范、命名規(guī)范、注釋規(guī)范三部分。iOS方面,蘋(píng)果已經(jīng)有一套Coding Guidelines,主要屬于命名方面的規(guī)范。當(dāng)我們制定自己的開(kāi)發(fā)規(guī)范時(shí),首先就要遵守蘋(píng)果的這份規(guī)范,在此基礎(chǔ)上再加上自己的規(guī)范。
最重要的不是開(kāi)發(fā)規(guī)范的制定,而是開(kāi)發(fā)規(guī)范的執(zhí)行。如果沒(méi)有按照開(kāi)發(fā)規(guī)范去執(zhí)行,那開(kāi)發(fā)規(guī)范就等于形同虛設(shè),那代碼混亂的問(wèn)題依然得不到解決。
另外,Android系統(tǒng)本身已經(jīng)對(duì)資源進(jìn)行了很好的分離,字符串、顏色值、尺寸大小、圖片、動(dòng)畫(huà)等等都用不同的xml文件定義。而iOS系統(tǒng)在這方面就遜色很多,只能自己實(shí)現(xiàn),其中一種實(shí)現(xiàn)方案就是通過(guò)plist文件的方式實(shí)現(xiàn)和Android一樣的機(jī)制。
工程結(jié)構(gòu)
工程結(jié)構(gòu)其實(shí)就是模塊的劃分,無(wú)非分為兩類(lèi):按業(yè)務(wù)劃分或按組件劃分。
比如一個(gè)電商App,可能會(huì)有首頁(yè)、附近、分類(lèi)、我的四大模塊,工程結(jié)構(gòu)也根據(jù)這四大模塊進(jìn)行劃分,Android可能就分為了四個(gè)模塊包:
com.domain.home 首頁(yè)
com.domain.nearby 附近
com.domain.category 分類(lèi)
com.domain.user 我的
同樣的,iOS則分為四個(gè)分組:home、nearby、category、user。
之后,每個(gè)模塊下相應(yīng)的頁(yè)面就放入相應(yīng)的模塊。那么,問(wèn)題來(lái)了,商品詳情頁(yè)應(yīng)該屬于哪個(gè)模塊呢?首頁(yè)會(huì)跳轉(zhuǎn)到商品詳情頁(yè),附近也會(huì)跳轉(zhuǎn)到商品詳情頁(yè),分類(lèi)也會(huì)跳轉(zhuǎn)到商品詳情頁(yè),用戶查看訂單時(shí)也能跳轉(zhuǎn)到商品詳情頁(yè)。有些頁(yè)面,并不能很明顯的區(qū)分出屬于哪個(gè)模塊的。我接手過(guò)的,按業(yè)務(wù)劃分的二手項(xiàng)目中(即不是由我搭建的項(xiàng)目),我要找一個(gè)頁(yè)面時(shí),我認(rèn)為應(yīng)該屬于A模塊的,但在A模塊卻找不到,問(wèn)了同事才知道在B模塊。類(lèi)似的情況出現(xiàn)過(guò)很多次,而且不止出現(xiàn)在我身上,對(duì)業(yè)務(wù)不熟悉的開(kāi)發(fā)人員都會(huì)出現(xiàn)這個(gè)問(wèn)題。而且,對(duì)業(yè)務(wù)不熟悉的開(kāi)發(fā)人員開(kāi)發(fā)新的頁(yè)面或功能時(shí),如果對(duì)業(yè)務(wù)理解不深,劃分出錯(cuò),那也將成為問(wèn)題,其他人員要找該頁(yè)面時(shí)更難找到了。
因此,我更喜歡按組件劃分的工程結(jié)構(gòu),因?yàn)榻M件每個(gè)人都懂,不管對(duì)業(yè)務(wù)熟不熟悉,查找起來(lái)都明顯方便很多。Android按組件劃分大致如下:
com.domain.activities 存放所有的Activity
com.domain.fragments 存放所有的Fragment
com.domain.adapters 存放所有的Adapter
com.domain.services 存放所有的Service
com.domain.views 存放所有的自定義View
com.domain.utils 存放所有的工具類(lèi)
iOS的分組則大致如下:
controllers 存放所有ViewController
cells 存放所有Cell,包括TableViewCell和CollectionViewCell
views 存放所有自定義控件或?qū)ο到y(tǒng)控件的擴(kuò)展
utils 存放所有的工具類(lèi)
基類(lèi)的定義
Android的Activity、Fragment、Adapter,iOS的ViewController,分別定義一個(gè)基類(lèi),將大部分通用的變量和方法定義和封裝好,將減少很多工作量,而且有了統(tǒng)一的設(shè)置,也會(huì)減少代碼的混亂。比如我在Android項(xiàng)目重構(gòu)之路:實(shí)現(xiàn)篇中提到的KBaseActivity和KBaseAdapter的實(shí)現(xiàn)就是例子,當(dāng)然還可以抽離出更多變量和方法。
每個(gè)Activity的onCreate()方法,一般分為三步:
變量的初始化;
View的初始化;
加載數(shù)據(jù)。
因此,其實(shí)可以將onCreate()方法拆分成三個(gè)方法:
initVariables()
initViews()
loadData()
在基類(lèi)中將這三個(gè)方法定義為抽象方法,由子類(lèi)去實(shí)現(xiàn),這樣,子類(lèi)就不需要實(shí)現(xiàn)onCreate()方法了,只要實(shí)現(xiàn)更細(xì)化的上述三個(gè)方法即可。
iOS的ViewController也是同樣的方式,這里就不重復(fù)了。
?
轉(zhuǎn)載于:https://www.cnblogs.com/jurendage/p/11193018.html
總結(jié)
以上是生活随笔為你收集整理的Java生鲜电商平台-App系统架构开发与设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 第二证券|钠离子电池将迎来量产 22股净
- 下一篇: 【免费领取】趣味测试小程序源码/内置超多