我項目中的代碼都是如何分層的?

一、背景

提及應用分層,大部分人都會認爲這個不是很簡單嘛 就controller,service, mapper三層。看起來簡單,不少人其實並無把他們職責劃分開,在不少代碼中,controller作的邏輯比service還多,service每每當成透傳了,這實際上是不少人開發代碼都沒有注意到的地方,反正功能也能用,至於放哪無所謂唄。這樣每每形成後面代碼沒法複用,層級關係混亂,對後續代碼的維護很是麻煩。程序員

的確在這些人眼中分層只是一個形式,前輩們的代碼這麼寫的,其餘項目代碼這麼寫的,那麼我也這麼跟着寫。數據庫

可是在真正的團隊開發中每一個人的習慣都不一樣,寫出來的代碼必然帶着本身的標籤,有的人習慣controller寫大量的業務邏輯,有的人習慣在service中之間調用遠程服務,這樣就致使了每一個人的開發代碼風格徹底不一樣,後續其餘人修改的時候,一看,我靠這我的寫的代碼和我日常的習慣徹底不一樣,修改的時候究竟是按着本身之前的習慣改,仍是跟着前輩們走,這又是個艱難的選擇,選擇一旦有誤差,你的後輩又維護你的代碼的時候,恐怕就要罵人了。apache

因此一個好的應用分層須要具有如下幾點:編程

  • 方便後續代碼進行維護擴展;後端

  • 分層的效果須要讓整個團隊都接受;緩存

  • 各個層職責邊界清晰。微信

二、應用分層模型

在項目開發中,一個良好的工程架構是必須的。工程架構就像一個骨架,寫代碼就是在這個骨架上增添血肉,這個骨架會影響到總體的模塊劃分,功能劃分,即會影響到代碼的解耦和聚合,將會很大程度上決定一個項目寫得好很差。數據結構

這裏要分享的是我我的在開發時所採起的工程架構,以及相關的思想。不一樣的人對於工程架構的理解會不一樣,實際上也很難分出哪一種好,哪一種壞,只要符合本身的設計思想,而且可以有效的進行開發,那就是好的一種架構方式。架構

2.一、分層

我總體上的思想爲《阿里巴巴 Java 開發手冊》中所描述的分層模型。以下:app

應用分層圖

接下來將自底向上的講解我對各層的理解和設計,還有我本身所增長的層。

2.二、通用工具層

通用工具層其實爲對業務無關的,通用的工具類,例如日期處理的工做累,一些數據格式的序列化與反序列化工具。相似於 apache commons 包和 guava 包。

2.三、分層領域模型

領域模型,也就是咱們以前常見的各類數據實體,用 DDD 的術語來講,這種在分層模型中的領域模型稱爲貧血領域模型。 貧血領域模型只做爲數據載體,只有 getter/setter 方法,而不包含業務方法。

對於分層領域模型,會進一步進行劃分規約,主要也是參考自《阿里巴巴 Java 開發手冊》具體以下:

  • DO(Data Object) : 數據對象,對數據源數據的映射,如數據庫表,ElasticSearch 索引的數據結構。所在包通常命名爲 data 。
  • DTO(Data Transfer Object) : 數據傳輸對象,業務層向外傳輸的對象。若是在某個業務中須要屢次查詢獲取不一樣的數據對象,最後將會把這多個數據對象組合成一個 DTO 並對外傳輸。所在包命名爲 dto 。
  • BO(Business Object) : 業務對象,由 Service 層輸出的封裝業務邏輯的對象。即對象除了數據外,還會包含必定的業務邏輯,也能夠說是充血領域模型。可是我通常不會使用。
  • AO(Application Object) : 應用對象,在 Web 層與 Service 層之間抽象的複用對象模型,極爲貼近展現層,複用度不高。比較少用。
  • VO(View Object) : 顯示層對象,一般是 Web 向模板渲染引擎層傳輸的對象。如今的項目多數爲先後端分離,後端只須要返回 JSON ,那麼能夠理解爲 JSON 便是須要渲染成的「模板」。我通常會將這類對象命名爲 xxxResponse ,所在包命名爲 response 。
  • Query : 數據查詢對象,數據查詢對象,各層接收上層的查詢請求。其實通常用於 Controller 接受傳過來的參數,能夠將其都命名爲 xxxQuery ,而我我的習慣將放在 request body 的參數(即 @RequestBody)包裝爲 xxxRequest ,而若是使用表單傳輸過來的參數(即 @RequestParam)包裝爲 xxxForm ,並分別放在包 request 和包 form 下。

其實貧血領域模型只是做爲數據的載體,在一開始我以爲不必進行具體的分類,基本上都是往一個包內丟,可是當項目規模上來後,各類各樣的數據實體開始增長,慢慢的變得混亂。對數據對象的分類是爲了更好的定義每一個數據的做用以及在後續可以快速的定位到對應的數據對象。

2.四、Helper

開發中會遇到一些很基礎的,通用的業務邏輯,例如咱們可能會根據每一個用戶的信息生成一個惟一的 account id 。又或者說有一個用戶排名的需求,咱們將從用戶的相關信息中計算出一個分數,從而根據這個分數進行排名。那麼這時候咱們可能會將這些邏輯寫在 User 數據對象或是其餘相應對應的數據對象下。 而我我的來講,不但願數據對象包含業務邏輯,因此我會將這些通用的業務邏輯都抽出來,放到 Manager 中進行統一管理。如會將生成 account id 的邏輯放在 AccountIdGenerator 中,將計算排名分數的邏輯放在 RankCalculator 中。 我將這些類都歸爲 Helper ,用於提供底層的業務計算邏輯。而爲何不放在通用工具層中呢?由於這些 Helper 其實都是依賴於特定的領域,即特定的業務。而通用工具類則是業務無關的,任何系統,只要有須要均可以引用。

2.五、DAO

DAO 就不用過多解釋了,數據訪問對象,用於對數據庫的訪問。可是我我的不會將 DAO 只侷限於數據庫,對於不一樣的數據源的交互,如 HBase ,ElasticSearch ,本地緩存甚至 Redis 我都會定義相對應的 DAO 進行訪問。 這樣的定義,實際上是想將數據 CURD 的邏輯和業務邏輯進行分離,將獲 CRUD 封裝在 DAO 中,業務邏輯即放在業務層中。

以前接手了一個項目,項目將 Redis 視爲中間件,將相關的邏輯都封裝在 xxxRedisService 中,包括 CRUD 和一些業務邏輯。隨着項目的發展,一些其實能夠歸類到一塊兒的業務,變得有些放在了 RedisService 中,一些放在了業務層的 Service 中,可想而知十分混亂,還致使了一些 BUG 的出現。

2.六、Service 和 Manager

Service 的做用不用多說明,爲具體業務邏輯的封裝層。

具體要說明的是 Manager ,《阿里巴巴 Java 開發手冊》中定義以下:

  1. 對第三方平臺封裝的層,預處理返回結果及轉化異常信息
  2. 對 Service 層通用能力的下沉,如緩存方案、中間件通用處理
  3. 與 DAO 層交互,對多個 DAO 的組合複用

能夠將 Manager 理解爲對通用邏輯的封裝,避免 Service 與 Service 進行相互調用,以及對通用邏輯的管理。

在開發中,咱們常常會遇到 AService 中的某個業務能夠提供給 BService 調用,從而讓 BService 調用 AService 的方法,認爲是 Service 之間具備共同的業務。其實 Service 之間沒有共同的業務,而是具有通用的邏輯,這時應該將其抽離出來放在 Manager 中。不管何種工程架構都好,我都不贊同 Service 與 Service 之間的相互調用。

在實際開發中,我會對 Manager 進行更細一點的劃分。大體將其分爲用於項務類,所封裝的是由 Service 下沉的通用業務。 而另外一種則是一些偏向於工具、計算的類,例如某個業務使用了策略模式,所編寫的策略類則屬於這一類。 我會將業務類的用 @Service 註釋,而偏工具類的則用 @Component 註釋。這樣作的緣由仍是避免業務之間的相互調用,相互耦合。

這裏可能會想,爲何不將 Helper 的邏輯也放在 Manager 層中?緣由在於 Helper 的邏輯比 Manager 更加基礎,有可能在 DAO 中都會調用 Helper 的相關邏輯,若是放在 Manager 中,就會出現底層依賴上層的問題。

2.七、接口層

最後的一層,則是暴露給外部調用的層。能夠是 Spring 體系中的 Controller ,也能夠是 gRPC 。 這一層將組織、調用咱們所定義的 Service ,進行業務處理。

三、分層模型的優勢以及缺點

不管什麼工程架構,都會有其優勢以及缺點,在選擇工程架構時,其實就是對優勢缺點的衡量。

3.一、優勢

其實不管什麼架構,特別是對業務工程來講,最但願架構帶來的是解耦以及內聚。 經過分層,在必定程度上對項目內的各個模塊進行了解耦內聚,依賴關係十分明確,再怎麼寫,只要符合規約,老是上層依賴於下層。並且分層的規約十分簡單,在多人協做的狀況下大部分狀況均可以很好的遵照規約。

3.二、缺點

簡單是一個優勢,也是一個缺點。分層雖然在必定程度上進行了解耦,可是粒度十分粗,只要不出現下層依賴上層的狀況,均可以認爲是符合規約的,在這種狀況下,很容易致使代碼的分散、功能的碎片化,明明是同一類業務功能的代碼,卻分散在多個類,多個層次之間。在項目不斷迭代時變得巨大時,慢慢就會變得混亂,而後就是一輪重構。 歸根到底就是太鬆懈了,致使開發人員很容易就是在項目中隨便找個地方寫,還很容易致使由大量的複製粘貼所產生重複代碼。

在學校開設的軟件工程課中,設計一個系統,首先是組織架構的瞭解,而後從中抽出數據流,而後再在數據流中抽出業務流,進行根據業務流進行開發。而採用分層模型的化,每每在數據流中就能夠開始開發,採用分層模型的話,每一個業務其實能夠簡單的抽象成數據在各層之間的流動。 這能夠說是一個優勢,簡化了業務的理解,實現快速的開發,我在比較緊的排期下也由這麼作過,掃一眼業務,構思好數據流的流動後就動手了。但這也是一個很嚴重的缺點,我見過很多功能性 BUG ,就是因爲對業務的不充分理解所致使的,並且因爲沒有對業務流程充分理解後就開發,後續的擴展和修復,看起來就是不斷的修修補補。

上面,我除了《阿里巴巴 Java 開發手冊》所寫的內容外,還添加了很多細節,其實所想要作的就是儘可能減小這種功能碎片化的問題。

四、與充血領域模型的對比

既然是說工程架構,就不得不提 DDD 這一個概念。

爲何我說的是「與充血領域模型的對比」而不是「與 DDD 的對比」呢?是由於 DDD 是比分層模型更加高層的一種概念,它是一個產品服務,整個團隊開發的一種指導思想,而不是一種工程代碼上的規約。

DDD 能夠分爲兩大方向,一個是戰略層面上的,即以前提到的是一種開發的指導思想,定義、劃分服務的領域,規定統一語言提升溝通效率等,這也是能夠用於使用分層領域模型的項目開發中的。若是要與分層模型對比的話,實際上是 DDD 的戰術層面,即充血領域模型。

充血領域模型實際上是迴歸於面向對象的思想。在目前的分層模型中,哪怕是用 Java 這種面向對象的語言去寫,其實整體上仍是一種過程式的編程,在 DDD 中稱爲事務腳本。

充血領域模型是重領域,輕 Service 的。以以前生成 account id 以及排名的例子,在充血領域模型中,User 類將會有 generateAccountId 方法和 ranking 方法來完成這一邏輯。 徹底的面向對象,就能夠充分的發揮面向對象的特性。面向對象的特性在書上爲:繼承、多態,封裝。前二者可以實現歸一化,使模塊泛化通用,封裝即會使模塊劃分明確,可以很好的實現解耦和內聚。比起分層模型,使用充血領域模型能夠很好的解決上面提到的代碼分散,碎片化的問題。

充血領域模型的優勢是面向對象的優勢,可是面向對象的缺點也成爲這種模型的缺點。首先,萬物皆可抽象在我看來就是僞命題,由於現實世界中總有事務是難以進行抽象的,或者抽象起來不優雅,老是有一種硬是抽象的感受。 在知乎中有一個很好的回答,描述了面向對象的弊端

相信不少人在初接觸 DDD 時,都會去搜索充血領域模型實踐的例子。其實在學校學習 Java Web 開發時,書本中寫道的 MVC 結構其實在必定程度上也是充血領域模型,Model 除了是數據的載體外,還包含業務邏輯,經過 Controller 對 Model 的選擇以及調用完成業務。假如用這種結構開發,當項目龐大後,我以爲首先遇到的問題應該就是依賴問題,複雜的業務必然牽扯到各方各面,天然也就有複雜的依賴關係產生,甚至會有爲了完成業務而產生很「髒」的實現,這是難以免的。

我我的以爲充血領域模型目前仍是隻適合於我的,很小的團隊中使用,例如 2 到 3 我的的團隊,由於抽象自己就是一個很是複雜的過程,隨着需求迭代,以前的抽象還不必定正確,若是在較爲多人的多人協做中,各類奇奇怪怪的寫法都會出現,必然也會有隨便找個「地」寫的狀況出現,這種狀況比在分層模型中更爲致命。

五、總結

仍是那句話,工程架構無分好壞,只有適合與不適合,問題的來與在於業務的複雜,計算機始終在某些方面難以映射到現實世界。因此我我的建議好好的理解好本身目前所用到的工程架構,儘可能作到揚長避短。

歡迎關注微信公衆號」程序員小明」,獲取更多資源。

相關文章
相關標籤/搜索