項目目錄(包、模塊)結構
在項目的開發階段,目錄結構的划分往往被看做是邁向成功的第一步。這一步的邁出往往伴隨着很多方面的權衡(考量),總的來說是兩個方面的考量:業務方面和技術方面。
- 業務方面的考量包括:限界上下文、子域、業務模塊。
- 技術方面的考量包括:軟件架構(分層架構、六邊形架構)、構造型分類。
目錄結構構成
常見的項目的目錄結構基本上由:領域名(domain)、層名(layer)、構造型名(stereotype)、業務模塊名(module)這四個部分組成。
領域(業務域、子域)名稱
在《領域驅動設計》中的領域通常是指一個業務域,是一個特定的業務范圍。同類項目中的業務可能雷同,但對於大多數的項目要解決的業務(問題)來說不會超出所在業務域的范圍,因此在項目的目錄(包、模塊)結構中包含業務域的名稱能起到限界作用。比如:產品目錄子域(Catalog)、訂單子域(Order)、物流子域(Shipping)、發票子域(Invoice)等等。
分層架構中層次名稱
在項目的目錄結構中顯式的引入層名是一種技術考量,更具體一些是編碼的考量。分層架構是一種從混亂到有序的解決方案(架構模式)。它的做法是將一個應用程序(流程)划分為多組子任務,其中每組子任務都位於特定抽象層中。例如:分層架構在應用系統的后端開發中,常將一個應用系統划分為三層架構或者四層架構。
三層架構:
- 表現層(Presentation)
- 業務邏輯層(Business)
- 持久層(Persistence)
四層架構:
- 表現層(Presentation)
- 應用(邏輯)層(Application)
- 領域層(Domain)
- 基礎設施層(Infrastructure)
在四層架構中的基礎設施層要比三層架構中的持久層的功能多一些。
在應用系統開發中的分層架構並不是嚴格意義上的分層架構。真正的分層表示為上層只能依賴下層,是單向依賴,不能存在雙向依賴。具體來說有以下特點:
- J 層依賴 J - 1 層,J + 1 層依賴 J 層。
- J - 1 層不會依賴 J 層,J 層也不會依賴 J + 1 層。
- J + 1 層也不會依賴 J - 1 層。
層與層之間通過數據的封裝、轉換或者直接使用來做到隔離。
在追求性能和靈活性方面,出現了兩種分層變種:寬松的分層系統(Relaxed Layered System)和通過繼承進行分層(Layering Through Inheritance)。我們將簡要討論 寬松的分層系統,因為普遍地應用系統采用的就是寬松的分層系統。
寬松的分層系統表示:每層都可以使用它的下層服務,而不僅僅是下一層的服務。每層都可能是半透明的,這意味着有些服務只對上一層可見,而有些服務對上面的所有層都可見。

就算是嚴格地分層架構或者寬松的分層架構,都表明是上層依賴下層。但在實際地應用系統的架構中並沒有完全遵循這種依賴關系,因為架構人員需要在整體架構與分層架構之間進行搖擺球式思考。比如:在領域(Domain)層中的 Repository 接口的實現類往往會放在基礎設施(Infrastructure)層中,這顯然違反了分層架構中的單向依賴關系。這樣的問題有兩種解決方案:一是誠然接受。二是將領域層中的 Repository 接口的實現類放置在領域層內。
構造型名稱(stereotype)
構造型使用書名號(<<>>)來表示,用於區分不同地建模元素。

如:實體(entity)、枚舉(enumeration)、異常(exception)、查詢(query)、事件(event)、資源庫(repository)、服務(service)、控制器(controller)等等都是常見的構造型。
補充:在 UML1.4 及以后版本允許一個建模元素可以附加多個構造型。
有些項目在划分項目的目錄結構時,會將構造型顯式的引入到目錄(包)結構中,這是一種歸類的組織方式。
業務模塊名稱(module)
在分析一個業務(問題)域時,會將一個業務域划分為多個業務模塊。比如在商店(Store)子域中會被划分為:商店員工(Staff)、商店會員(Member)、商店角色(Role)等等。
目錄結構分類
在分別對目錄(包)結構的構成元素做了簡單介紹后,下面要開始具體探討由這些元素組合而成的目錄結構了。
- 業務域名.層名.*
- 業務域名.層名.業務模塊名.*
- 業務域名.構造型名.*
- 業務域名.構造型名.業務模塊名.*
- 業務域名.(層名 & 構造型名).*
- 業務域名.(層名 & 構造型名).業務模塊名.*
- 業務域名.業務模塊名.*
- 業務域名.業務模塊名.層名.*
- 業務域名.業務模塊名.構造型名.*
- 業務域名.業務模塊名.(層名 & 構造型名).*
- 業務域名.(業務模塊名 & 層名 & 構造型名).*
- (構造型名 || 層名).業務域名.業務模塊名.*
補充說明:
- 省略包(package)的反向域名(org.mallfoundry.*)前綴的部分。
- 領域看作是業務域,其中業務域名是業務域 名,而不是業務 域名。
- (層名 & 構造型名)是一種混合,表示在同一級別的目錄(包)結構上同時存在按層和按構造型划分的兩種方式。
目錄結構:業務域名.層名.*
├─catalog // 商品目錄子域
│ ├─application
│ ├─domain
│ ├─infrastructure
│ └─presentation
├─order // 訂單子域
│ ├─application
│ ├─domain
│ ├─infrastructure
│ └─presentation
└─store // 商家子域
├─application
├─domain
├─infrastructure
└─presentation
業務域名.層名.業務模塊名.*
├─catalog // 商品目錄子域
│ ├─application // 應用層
│ │ ├─brand
│ │ ├─category
│ │ ├─collection
│ │ └─product
│ ├─domain // 領域層
│ │ ├─brand // 商品品牌模塊
│ │ ├─category // 商品類目模塊
│ │ ├─collection // 商品集合模塊
│ │ └─product // 商品模塊
│ ├─infrastructure // 基礎設施層
│ │ └─persistent // 持久化
│ │ ├─jpa
│ │ ├─mybatis
│ │ └─redis
│ └─presentation // 表現層
│ ├─graphql
│ ├─grpc
│ ├─rest
│ ├─view
│ └─websocket
└─order
├─application
│ ├─dispute
│ ├─review
│ ├─shipping
│ └─source
├─domain
│ ├─dispute
│ ├─review
│ ├─shipping
│ └─source
├─infrastructure
└─presentation
業務域名.構造型名.*
├─catalog
│ ├─controller
│ ├─exception
│ ├─model
│ ├─query
│ ├─repository
│ └─service
└─order
├─controller
├─exception
├─model
├─query
├─repository
└─service
備注:
- Model 包中中包含:實體、值對象、枚舉等領域模型。在有些項目中會將 Model 包命名為:Pojo、Bean、Entity 等。
- 在按構造型划分目錄時還會存在:DTO、VO 等包結構。
業務域名.構造型名.業務模塊名.*
├─catalog
│ ├─controllers
│ │ ├─brand
│ │ ├─category
│ │ ├─collection
│ │ └─product
│ ├─exceptions
│ │ ├─brand
│ │ ├─category
│ │ ├─collection
│ │ └─product
│ ├─models
│ │ ├─brand
│ │ ├─category
│ │ ├─collection
│ │ └─product
│ ├─queries
│ │ ├─brand
│ │ ├─category
│ │ ├─collection
│ │ └─product
│ ├─repositories
│ │ ├─brand
│ │ ├─category
│ │ ├─collection
│ │ └─product
│ └─services
│ ├─brand
│ ├─category
│ ├─collection
│ └─product
└─order
├─controllers
├─exceptions
├─models
├─queries
├─repositories
└─services
備注:在采用這種目錄結構時,構造型名稱常采用復數形式命名。
業務域名.(層名 & 構造型名).*
├─catalog
│ ├─controller
│ ├─dao
│ ├─exception
│ ├─model
│ ├─query
│ └─service
└─order
├─controller
├─dao
├─exception
├─model
├─query
└─service
備注:在目錄(包)結構中采用(層名 & 構造型名)混合式的方式,常常出現在將分層架構與構造型混淆在一起的項目中。
- Controller 代表表現層。
- Service 代表業務邏輯層。
- Dao 或者 Repository 代表數據訪問層。
- Model 代表領域模型。
業務域名.(層名 & 構造型名).業務模塊名.*
├─catalog
│ ├─controller
│ │ ├─brand
│ │ ├─category
│ │ ├─collection
│ │ └─product
│ ├─dao
│ │ ├─brand
│ │ ├─category
│ │ ├─collection
│ │ └─product
│ ├─exception
│ │ ├─brand
│ │ ├─category
│ │ ├─collection
│ │ └─product
│ ├─model
│ │ ├─brand
│ │ ├─category
│ │ ├─collection
│ │ └─product
│ ├─query
│ │ ├─brand
│ │ ├─category
│ │ ├─collection
│ │ └─product
│ └─service
│ ├─brand
│ ├─category
│ ├─collection
│ └─product
└─order
├─controller
├─dao
├─exception
├─model
├─query
└─service
業務域名.業務模塊名.*
├─catalog
│ ├─brand
│ ├─category
│ ├─collection
│ └─product
└─order
├─dispute
├─review
├─shipping
└─source
業務域名.業務模塊名.層名.*
├─catalog
│ ├─brand
│ │ ├─application
│ │ ├─domain
│ │ ├─infrastructure
│ │ └─presentation
│ ├─category
│ │ ├─application
│ │ ├─domain
│ │ ├─infrastructure
│ │ └─presentation
│ ├─collection
│ │ ├─application
│ │ ├─domain
│ │ ├─infrastructure
│ │ └─presentation
│ └─product
│ ├─application
│ ├─domain
│ ├─infrastructure
│ └─presentation
└─order
├─dispute
├─review
├─shipping
└─source
備注:分層會在模塊內部。
業務域名.業務模塊名.構造型名.*
├─catalog
│ ├─brand
│ │ ├─controller
│ │ ├─exception
│ │ ├─model
│ │ ├─query
│ │ ├─repository
│ │ └─service
│ ├─category
│ │ ├─controller
│ │ ├─exception
│ │ ├─model
│ │ ├─query
│ │ ├─repository
│ │ └─service
│ ├─collection
│ │ ├─controller
│ │ ├─exception
│ │ ├─model
│ │ ├─query
│ │ ├─repository
│ │ └─service
│ └─product
│ ├─controller
│ ├─exception
│ ├─model
│ ├─query
│ ├─repository
│ └─service
└─order
├─dispute
├─review
├─shipping
└─source
備注:構造型會在模塊內部。
業務域名.業務模塊名.(層名 & 構造型名).*
├─catalog
│ ├─brand
│ │ ├─controller
│ │ ├─dao
│ │ ├─exception
│ │ ├─model
│ │ ├─query
│ │ └─service
│ ├─category
│ │ ├─controller
│ │ ├─dao
│ │ ├─exception
│ │ ├─model
│ │ ├─query
│ │ └─service
│ ├─collection
│ │ ├─controller
│ │ ├─dao
│ │ ├─exception
│ │ ├─model
│ │ ├─query
│ │ └─service
│ └─product
│ ├─controller
│ ├─dao
│ ├─exception
│ ├─model
│ ├─query
│ └─service
└─order
├─dispute
├─review
├─shipping
└─source
業務域名.(業務模塊名 & 層名 & 構造型名).*
├─catalog
│ ├─brand
│ ├─category
│ ├─collection
│ └─product // Product 模塊
│ ├─controller
│ ├─dao
│ ├─exception
│ ├─model
│ ├─query
│ ├─review // Product 模塊內的 Review 模塊
│ │ ├─controller
│ │ ├─dao
│ │ ├─exception
│ │ ├─model
│ │ ├─query
│ │ └─service
│ └─service
└─order
├─dispute
├─review
├─shipping
└─source
備注:(業務模塊名 & 層名 & 構造型名)三者混合是一種項目目錄(包)划分的方式。這種結構往往看上去會有些混亂。
(構造型名 || 層名).業務域名.業務模塊名.*
├─catalog
│ ├─brand
│ ├─category
│ ├─collection
│ └─product
├─order
│ ├─dispute
│ ├─review
│ ├─shipping
│ └─source
└─rest // 發布 RESTful 接口。
├─catalog
│ ├─brand
│ ├─category
│ ├─collection
│ └─product
└─order
├─dispute
├─review
├─shipping
└─source
使用 Module 橫向分割
在 Java 中會有 jar 的形式來組織模塊(Module),我們可以使用 Module 先橫向分割,然后在模塊內部再划分目錄結構。
先將產品目錄(Catalog)子域和訂單(Order)子域橫向分割成四個模塊:
catalog
catalog-rest
order
order-rest
Module:catalog
└─org.mallfoundry.catalog
├─brand
├─category
├─collection
└─product
Module:catalog-rest
└─org.mallfoundry.rest.catalog
├─brand
├─category
├─collection
└─product
Module:order
└─org.mallfoundry.order
├─dispute
├─review
├─shipping
└─source
Module:order-rest
└─org.mallfoundry.rest.order
├─dispute
├─review
├─shipping
└─source
重名混淆(業務模塊名 & 層名 & 構造型名)
在初次瀏覽一個不熟悉的項目時,可能會對(業務模塊名 & 層名 & 構造型名)這三種結構發生重名混淆。
在一個以業務模塊名為主的目錄(包)結構中出現像 .repository.
、 .dao.
這樣的目錄結構時,你可能瞬間想到是構造型或者分層。但是 .repository.
、 .dao.
最大可能只是在表示一個 repository 或者 dao 的業務模塊。
開源電商
Mallfoundry 是一個完全開源的使用 Spring Boot 開發的多商戶電商平台。它可以嵌入到已有的 Java 程序中,或者作為服務器、集群、雲中的服務運行。
- 領域模型采用領域驅動設計(DDD)、接口化以及面向對象設計。
項目地址:https://gitee.com/mallfoundry/mallfoundry
總結
由:領域名(domain)、層名(layer)、構造型名(stereotype)、業務模塊名(module)這四個部分組成的目錄(包)結構是項目中常采用的。同時在划分目錄結構時也可以使用 Module 先進行橫向切割的方式。