[譯]按功能(特性)分包

一種流行的方法是經過技術層面對項目進行分包。可是這種方法有一些缺點。相反,咱們能夠按功能分包並建立獨立自治的程序包。結果是一個易於理解且不易出錯的代碼庫。java

總體分析

按照技術分包形成的缺點:數據庫

  1. 對屬於某個功能的全部類的概述不佳。api

  2. 通用代碼、重用代碼和複雜代碼趨向於難以理解,而且因爲難以把握變動的影響,所以變動很容易破壞其餘功能用例。架構

按功能分包從而建立包含功能所需的全部類的程序包。好處是:dom

  1. 更好的可發現性和概覽
  2. 獨立且自治
  3. 更簡單的代碼
  4. 可測試性
  5. 便於團隊協做開發

按照技術分層分包

項目結構的一種很是流行的方法是逐層分包。這將爲每一個技術組所屬類提供一個軟件包。測試

⚠️:按層分包從技術角度對全部類進行分組編碼

讓咱們將調用層次結構添加到圖片中,以「清楚地」瞭解哪一個類取決於其餘哪一個類。線程

⚠️:調用層次結構遍佈整個項目,涉及許多包翻譯

那麼,按層分包的缺點是什麼?日誌

  1. 功能概述不佳。一般,當咱們在項目中處理代碼時,咱們首先會想到要更改的特定領域或功能。所以,咱們會從領域的角度出發。不幸的是,按技術分層分包迫使咱們從一種軟件包過渡到另外一種軟件包,才能掌握功能的概況。

  2. 通用,重用和複雜代碼的趨勢。一般,這種方法致使中心類包含每一個功能用例的全部方法。隨着時間的流逝,這些方法愈來愈抽象化(帶有額外的參數和泛型)來知足更多用例。上圖中僅一個示例是ProductDAO,其中放置了ProductController和ExportController的方法。結果是:

    • 當添加更多方法時,類將變得更大。所以,僅憑代碼量,就很難理解它。

    • 更改通用重用代碼很危險。儘管您只想處理一個用例,但您能夠輕鬆地打破全部用例。

    • 因爲如下兩個緣由,難以理解抽象方法和通用方法:首先,要通用,一般須要其餘技術構造(例如,switch,參數,泛型),這使得查看與當前用例相關的業務邏輯更加困難。其次,認知需求更高,由於您必須瞭解全部其餘用例,以確保您不會破壞它們。

桑迪·梅斯(Sandi Metz)指出:

「我以爲我必須瞭解全部內容才能提供幫助。」桑迪·梅斯(Sandi Metz)。請參閱個人帖子,瞭解咱們的編碼智慧牆。

⚠️:咱們達到了DRY,但違反了KISS。


按功能(特性)分包

讓咱們將這些類從新排列成獨立的功能包。

👆用戶管理功能包

新的包userManagement包含屬於此功能的全部類:控制器,DAO,DTO和實體。

👆產品管理功能包

新軟件包productManagement包含相同的類類型,以及StockServiceClient和相應的StockDTO。這個事實清楚地代表:庫存服務僅由產品管理人員使用。

userManagement和productManagement使用不一樣的域實體和表。將它們分紅不一樣的包很簡單。可是,當一個功能須要與另外一個功能類似或甚至相同的域實體時,會發生什麼?

👆產品出口的功能包

如今,它變得愈來愈有趣。exportProduct包也處理產品實體,但具備不一樣的功能用例。

咱們的目標是擁有獨立自治的功能包。所以,exportProduct應該具備本身的DAO,DTO類和實體類,即便它們看起來與productManagement中的類類似。抵制重用productManagement中的類的衝動。

  • 咱們可使用針對出口用例量身定製的結構(DTO,實體)。它們僅包含相關字段,而且能夠基於具備相關列的良好投影的查詢來建立實體-別無其餘。
  • 專用的ExportProductDAO包含特定於出口功能的查詢和預測。

咱們可能不得再也不次編寫更多代碼,但最終會遇到很是有利的狀況:

  • productManagement中的更改永遠不會破壞exportProduct代碼,反之亦然。它們能夠獨立發展。
  • 更改代碼時,咱們僅需牢記當前功能。
  • 代碼自己將變得更加簡單易懂,由於它不是通用的,而且沒必要在兩個用例中均可以使用。

上面的功能包很棒,但實際上,咱們將始終須要一個通用的包。

👆通用軟件包包含技術配置和可重複使用的代碼

它包含技術配置類(例如用於DI,Spring,對象映射,http客戶端,數據庫鏈接,鏈接池,日誌記錄,線程池)

它包含可重用的有用代碼片斷。可是要很是當心代碼的過早抽象。我老是先把代碼放到儘量接近它的用法的地方,也就是特性包,甚至是使用類。僅當片斷確實有更多用途(⚠️:而不是我認爲未來可能會使用)時,纔將其移動到通用包中。三定律提供了很好的指導。

在通用包中找到全部實體多是有意義的。咱們還對某些項目執行了此操做,其中許多功能包一次又一次地使用相同的實體。一些開發人員還但願將全部實體放在中心位置,以便可以總體查看數據庫架構的映射。目前,我並非教條,由於實體的兩個位置均可以合理。不過,一開始我老是儘量多地將代碼轉移到功能包中,並依賴於定製的特定於用例的實體和投影。


大圖景

最終,咱們的大圖看起來像這樣:

👆按功能分包的大圖

好處

讓咱們簡要總結一下好處:

  1. 從域的角度來看,更好的可發現性和概述。屬於業務功能的大多數代碼位於一塊兒。這很關鍵,由於咱們一般會在考慮某個業務需求的狀況下訪問代碼庫。
  2. 獨立的和自治的。功能所需的大多數代碼都位於一個程序包中。所以,咱們避免依賴其餘功能包。結果是:在開發功能時,咱們不太可能破壞其餘功能。須要較少的認知能力來估計變化的影響。一般,咱們只須要記住當前的軟件包便可。
  3. 更簡單的代碼。因爲咱們避免使用通用和抽象的代碼,所以代碼變得更加簡單,由於它只須要處理一個用例。所以,更容易理解和改進代碼。
  4. 可測試性。一般,與試圖知足全部用例的技術包中的「上帝類」相比,功能包中的類具備較少的依賴關係。所以,因爲咱們能夠建立更少的測試依賴,所以測試變得更加容易。

缺點

  1. 咱們必須編寫更多代碼。
  2. 咱們可能會屢次編寫相似的代碼。
  3. 決定什麼時候才能更好地將代碼移至通用軟件包並重用它是很難的。有疑問時,「三定律」頗有用。我想強調指出,重用仍然是容許且有用的。
  4. 找出功能包的適當範圍和大小也很棘手。有關詳細信息,請參閱問題部分。

可是,我認爲優勢大於缺點。


背後的原理

擬議的按功能分包方法遵循的原則很是貼切:

KISS > DRY

再次,我想引用桑迪·梅斯(Sandi Metz)

「我以爲我必須瞭解全部內容才能提供幫助。」桑迪·梅斯(Sandi Metz)。請參閱個人帖子,瞭解咱們的編碼智慧牆。


按功能包裝的方法

咱們的團隊記錄了其遵循的編碼準則和原則。關於按功能分包的部分以下所示:

咱們基於功能分包。每一個功能包均包含提供該功能所需的大多數代碼。每一個功能包都應獨立且自治。

├── feature1
│   ├── Feature1Controller
│   ├── Feature1DAO
│   ├── Feature1Client
│   ├── Feature1DTOs.kt
│   ├── Feature1Entities.kt
│   └── Feature1Configuration
├── feature2
├── feature3
└── common
  1. 這種方法影響全部層。例如,每一個程序包都有本身的DAO和客戶端。不該有龐大的DAO類神。
  2. 一個程序包應該與其餘程序包只有幾個關係。該功能所需的全部邏輯事物都應放在程序包內。
  3. 經驗法則:若是要刪除功能,則只需刪除相應的程序包。
  4. 儘管如此,也能夠在通用軟件包中重複使用東西,但它只應包含屢次使用的代碼(請參閱三定律)。它不該該包含業務邏輯。可是技術上有用是能夠的。
  5. 若是存在特定於特性的Spring Bean,咱們將把它們的配置放在特性包中。

問題

功能包中的結構如何?

這取決於項目和功能包的大小。

對於中小型項目,我喜歡避免定義可能會增長更多儀式而非價值的規則(例如,要求定義某些接口和子包)。只要您構建獨立的、自治的、從您的特定業務領域派生的包,您就在正確的軌道上。

若是要處理更大的代碼庫,則可能須要定義有關子包結構和方式的更多規則,則容許一個功能包訪問另外一個功能包。「模塊」或「組件」而不是「功能包」的概念可能更有幫助。例如,Tom Hombergs建議在每一個組件包中添加api和內部包,這些組件包定義組件的哪些部分容許其餘組件使用。有關詳細信息,請參閱他的文章「使用Spring Boot和ArchUnit清理架構邊界」。

我最終會一次又一次寫相同的代碼嗎?

是的,會有一些重複,可是根據個人經驗,您可能不會相信那麼多100%相同的代碼。因爲類似的代碼涵蓋了不一樣的用例,所以一般是不一樣的。例如,兩種方法能夠按產品名稱查詢產品,可是它們在計劃的字段,排序和其餘條件方面有所不一樣。所以,最好將方法分開放在不一樣的程序包中。

並且,複製自己並非邪惡的。在開始將代碼提取到通用重用方法以前,我喜歡應用三定律

最後,我想強調指出,仍然容許集中使用可重用的代碼,有時甚至是合理的,可是這些狀況再也不那麼常見了。

Kotlin能夠支持這種方法嗎?

分包方法與語言無關。可是Kotlin使其易於遵循:

使用數據類,編寫量身定製的特定於功能的結構(如DTO或實體)僅需幾行,而無需樣板。

Kotlin容許將多個類放在一個文件中。所以,咱們可使一個包含全部數據類定義的DTOs.kt或Entities.kt文件成爲一個單獨的DTOs.kt或Entities.kt文件,而不是有一個子包DTO或包含每一個POJO類的許多Java文件的實體。

本文翻譯自:https://phauer.com/2020/package-by-feature/


關注筆者公衆號,推送各種原創/優質技術文章 ⬇️

WechatIMG6

相關文章
相關標籤/搜索