DDD爲我們在梳理業務的時候,提供了很多不錯的分析手段,但在代碼組織上,始終都比較模糊,即使是在三層架構中,我們在組織代碼時,也常常會陷入混亂的思緒,這裏我分享一下我對後端Java項目的代碼package分層的認知(這篇文章最後有我所分享的最終結構,如果沒有時間看整個分析過程,可以直接跳到最後一個段落)。
首先在DDD中,是將代碼的層次分爲了四層架構的:
- 用戶展示層
- 應用服務層
- 領域層
- 基礎設施層
如果直接按照這樣的理解,就可以直接分成這樣的結構:
.
├── view // 視圖
├── use.case // 用例
├── domain // 領域層
└── infrastructure // 基礎設施
但是在現實項目中會遇到幾個問題,DDD的分層實質上是對一個系統的分層,而非對一個單體,即在單個項目中部分層次可能是缺失的。像目前前後端分離的情況下,前端的頁面作爲用戶展示層並不會放入後端的項目,除此之外還需要有一個配置包,存放項目所需要的配置類,所以後端的項目這時候的結構可以形成:
.
├── config // 配置類
├── use.case // 用例
├── domain // 領域層
└── infrastructure // 基礎設施
以上的每個package(用例,領域層,基礎設施)都分開分析一下,先看基礎設施
,實際上,基礎設施
是分爲好幾個層面的:
- 對外暴露的服務接口,可以命名爲gateway,表示對外統一的接入層
- 項目內部非常基礎和通用的代碼,一般我們稱之爲common或者integration,這個部分的基礎設施實際上是橫跨整個項目
- 對外部中間件和數據庫的依賴框架,也是我們最習慣認爲的基礎設施
- 配置包
這樣我們可以將基礎設施也分成四個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等等
... ...