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

東溪陳姓少年發表於2020-05-05

一種流行的方法是通過技術層面對專案進行分包。但是這種方法有一些缺點。相反,我們可以按功能分包並建立獨立自治的程式包。結果是一個易於理解且不易出錯的程式碼庫。

整體分析

按照技術分包造成的缺點:

  1. 對屬於某個功能的所有類的概述不佳。

  2. 通用程式碼、重用程式碼和複雜程式碼趨向於難以理解,並且由於難以把握變更的影響,因此變更很容易破壞其他功能用例。

按功能分包從而建立包含功能所需的所有類的程式包。好處是:

  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

相關文章