領域驅動設計下的Package的劃分

DDD爲我們在梳理業務的時候,提供了很多不錯的分析手段,但在代碼組織上,始終都比較模糊,即使是在三層架構中,我們在組織代碼時,也常常會陷入混亂的思緒,這裏我分享一下我對後端Java項目的代碼package分層的認知(這篇文章最後有我所分享的最終結構,如果沒有時間看整個分析過程,可以直接跳到最後一個段落)。

首先在DDD中,是將代碼的層次分爲了四層架構的:

  • 用戶展示層
  • 應用服務層
  • 領域層
  • 基礎設施層

如果直接按照這樣的理解,就可以直接分成這樣的結構:

.
├── view // 視圖
├── use.case // 用例
├── domain // 領域層
└── infrastructure // 基礎設施

但是在現實項目中會遇到幾個問題,DDD的分層實質上是對一個系統的分層,而非對一個單體,即在單個項目中部分層次可能是缺失的。像目前前後端分離的情況下,前端的頁面作爲用戶展示層並不會放入後端的項目,除此之外還需要有一個配置包,存放項目所需要的配置類,所以後端的項目這時候的結構可以形成:

.
├── config               // 配置類
├── use.case            // 用例
├── domain              // 領域層
└── infrastructure     // 基礎設施

以上的每個package(用例,領域層,基礎設施)都分開分析一下,先看基礎設施,實際上,基礎設施是分爲好幾個層面的:

  1. 對外暴露的服務接口,可以命名爲gateway,表示對外統一的接入層
  2. 項目內部非常基礎和通用的代碼,一般我們稱之爲common或者integration,這個部分的基礎設施實際上是橫跨整個項目
  3. 對外部中間件和數據庫的依賴框架,也是我們最習慣認爲的基礎設施
  4. 配置包

這樣我們可以將基礎設施也分成四個package

.
├── config                   // 配置類
├── gateway                // 視圖
├── integration             // 集成
└── infrastructure         // 基礎設施
... ...

這四個基礎設施在不同的維度,職責獨立且不同,均可以置於根目錄下。

截止到以上的部分,根據《架構整潔之道》提出的整潔架構就可以進行比較有效的代碼分層了。但是一套業務系統之中,其核心還是業務,即在context這個包下面要如何分package,則真的要完全基於業務來進行劃分了,從技術層面對這些層次(用例,基礎設施等等)定義,還能有一些通用的說法,但是業務千變萬化,是很難有通用的劃分層次的,所以對業務進行劃分也是最難的。

.
├── application    // 應用服務(use case)
└── domain         // 領域層
│ ├── model       // 領域模型
│ ├── acl           // 防腐層,在領域中使用的外部系統代理
│ └── repo         // 資源倉儲,領域對象的資源庫
│ └── service      // 領域服務
... ...
  • application: 應用服務,用來描述業務用例,它主要是用來描述業務的場景的,負責提問、協調domain中的類來進行工作。
  • model:領域模型類放在這個地方,即業務模型所對應的實體對象等等。
  • acl: 防腐層,在領域中使用的外部系統代理,外部服務的接口(interface)在這裏定義,比如消息通知、區塊鏈應用等等。這裏的對接口的命名應該儘可能地通用一些,偏向業務一些,不與外部系統做強綁定不要出現類型IRedisClient之類的命名。
  • repo:資源倉儲,領域對象的資源庫,有點類似過去的DAO
  • service:領域服務,一些在領域類中無法被定義的方法,放置的位置,更多時候還是應該儘可能多地將方法放置到model中。

這裏最核心的類是model,model中的類是可以根據情況做引用用的,但是並不意味着他等同於數據庫中的實體。相反,應該儘可能地根據業務,將類進行拆分。比如模板在編輯階段、在使用階段和在列表展示中,他們的數據類型和行爲可能有非常大的不同,這裏就可以拆成多個包。如何分解這裏的業務就是我們需要和產品反覆溝通確認,再根據業務模型所作出的對領域模型的定義了。

.
├── application    // 應用服務(use case)
└── domain         // 領域層
│ ├── model       // 領域模型
│ │ ├──use       // 使用階段
│ │ │ ├── UseStageTemplate      // 使用階段的模板
│ │ │ ├── PendingFieldContent  // 填充的字段內容
│ │ ├── template // 核心模型(主要是編輯階段)
│ │ │ ├── Template  // 核心的模板模型
│ │ │ ├── Recipient  // 參與人
│ │ ├──list       // 模板列表
│ │ │ ├── ListViewTemplate      // 列表視圖中的模板
│ ├── acl           // 防腐層,在領域中使用的外部系統代理
│ └── repo         // 資源倉儲,領域對象的資源庫
│ └── service      // 領域服務

... ...

UseStageTemplate類可以引用Template或者是templateId:

@Data
public class UseStageTemplate {
    private Long templateId;

}

剛剛上面的層次定義了不少接口,他們的實現可以放到基礎設施中做實現:

.
├── application    // 應用服務(use case)
├── domain         // 領域層
│ ├── model       // 領域模型
│ ├── acl           // 防腐層,在領域中使用的外部系統代理
│ └── repo         // 資源倉儲,領域對象的資源庫
│ └── service      // 領域服務
├── infrastructure // 基礎設施
│ ├── acl            // 防腐層實現,在領域中使用的外部系統代理
│ └── repo          // 資源倉儲實現,領域對象的資源庫
... ...

在infrastructure的包中做和這些外部系統和數據庫的具體對接。後續如果要更換實現,也可以在這裏做改變,而不影響業務層面的代碼。

最終以上所有的包結合:

.
├── config           // 配置類
├── gateway        // 視圖
│ ├── http          // 對外暴露的http接口
│ ├── dubbo       // 對外部系統暴露的dubbo接口
│ ├── mq           // 對外部系統暴露的mq接口
├── application    // 應用服務(use case)
└── domain         // 領域層
│ ├── model       // 領域模型
│ ├── acl           // 防腐層,在領域中使用的外部系統代理
│ └── repo         // 資源倉儲,領域對象的資源庫
│ └── service      // 領域服務
├── infrastructure // 基礎設施
│ ├── acl            // 防腐層實現,在領域中使用的外部系統代理
│ └── repo          // 資源倉儲實現,領域對象的資源庫
│ ├── mysql.dao              // MySQL的DAO
│ ├── elasticsearch          // ElasticSearch搜索引擎的對接
│ └── rabbitmq                // RabbitMQ的對接
├── integration      // 基礎通用代碼,各種common、utils等等

如果碰上多個獨立模塊結合在一個系統之中,這個時候分層會面臨另外一個挑戰,通用的部分如何複用的問題。這個時候,我建議再多添加一個context的package作隔離:

.
├── config // 配置類
├── gateway // 視圖
│ ├── http // 對外暴露的http接口
│ ├── dubbo // 對外部系統暴露的dubbo接口
│ ├── mq // 對外部系統暴露的mq接口
├── context // 上下文
│ ├── contract // 業務名稱
│ │ ├── application // 應用服務(use case)
│ │ ├── domain // 領域層
│ │ │ ├── model // 領域模型
│ │ │ ├── acl // 防腐層,在領域中使用的外部系統代理
│ │ │ └── repo // 資源倉儲,領域對象的資源庫
│ │ │ └── service // 領域服務
│ ├── template // 業務名稱
│ │ ├── application // 應用服務(use case)
│ │ ├── domain // 領域層
│ │ │ ├── model // 領域模型
│ │ │ ├── acl // 防腐層,在領域中使用的外部系統代理
│ │ │ └── repo // 資源倉儲,領域對象的資源庫
│ │ │ └── service // 領域服務
├── infrastructure // 基礎設施
│ ├── context // 上下文
│ │ ├── contract // 業務名稱
│ │ │ ├── acl // 防腐層實現,在領域中使用的外部系統代理
│ │ │ └── repo // 資源倉儲實現,領域對象的資源庫
│ │ ├── template // 業務名稱
│ │ │ ├── acl // 防腐層實現,在領域中使用的外部系統代理
│ │ │ └── repo // 資源倉儲實現,領域對象的資源庫
│ ├── mysql.dao // MySQL的DAO
│ ├── elasticsearch // ElasticSearch搜索引擎的對接
│ └── rabbitmq // RabbitMQ的對接
├── integration // 基礎通用代碼,各種common、utils等等
... ...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章