注:此文转载自领域驱动设计(DDD):项目目录(包、模块)结构
DDD项目目录(包、模块)结构
在项目的开发阶段,目录结构的划分往往被看做是迈向成功的第一步。这一步的迈出往往伴随着很多方面的权衡(考量),总的来说是两个方面的考量:业务方面和技术方面。
- 业务方面的考量包括:限界上下文、子域、业务模块。
- 技术方面的考量包括:软件架构(分层架构、六边形架构)、构造型分类。
目录结构的分层
常见的项目的目录结构基本上由:领域名(domain)、层名(layer)、构造型名(stereotype)、业务模块名(module)这四个部分组成。
1. 领域(业务域、子域)名称
在《领域驱动设计》中的领域通常是指一个业务域,是一个特定的业务范围。对于大多数的业务(问题)来说不会超出所在业务域的范围,因此业务域的名称在项目的目录(包、模块)结构中能起到限界作用。比如:产品目录子域(Catalog)、订单子域(Order)、物流子域(Shipping)、发票子域(Invoice)等等。
2. 层名称
在项目的目录结构中显式的引入层名是一种技术考量,更具体一些是编码的考量。分层架构是一种从混乱到有序的解决方案(架构模式)。它的做法是将一个应用程序(流程)划分为多组子任务,其中每组子任务都位于特定抽象层中。例如:分层架构在应用系统的后端开发中,常将一个应用系统划分为三层架构或者四层架构。
三层架构:
表现层(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 接口的实现类放置在领域层内。
3. 构造型名称(stereotype)
构造型使用书名号(《》)来表示,用于区分不同地建模元素。
如:实体(entity)、枚举(enumeration)、异常(exception)、查询(query)、事件(event)、资源库(repository)、服务(service)、控制器(controller)等等都是常见的构造型。
有些项目在划分目录结构时,会将构造型显式的引入到目录结构中,这是一种归类的组织方式。
如entity包,service包等。
4. 业务模块(module)
在分析一个业务(问题)域时,会将一个业务域划分为多个业务模块。比如在商店(Store)子域中会被划分为:商店员工(Staff)、商店会员(Member)、商店角色(Role)等等。
看到这里,如果你还迷茫,那我再来举个例子:
商品领域 划分成 商品品牌模块、商品类目模块、商品模块等
所以,领域是业务界限,业务模块是按抽象出的概念。
常见的目录结构
在分别对目录(包)结构的构成元素做了简单介绍后,下面要开始具体探讨由这些元素组合而成的目录结构了。
业务域名.层名.*
业务域名.层名.业务模块名.*
业务域名.构造型名.*
业务域名.构造型名.业务模块名.*
业务域名.(层名 & 构造型名).*
业务域名.(层名 & 构造型名).业务模块名.*
业务域名.业务模块名.*
业务域名.业务模块名.层名.*
业务域名.业务模块名.构造型名.*
业务域名.业务模块名.(层名 & 构造型名).*
业务域名.(业务模块名 & 层名 & 构造型名).*
(构造型名 || 层名).业务域名.业务模块名.*
补充说明:
省略包(package)的反向域名(org.mallfoundry.*)前缀的部分。
领域看作是业务域,其中业务域名是业务域 名,而不是业务 域名。
(层名 & 构造型名)是一种混合,表示在同一级别的目录(包)结构上同时存在按层和按构造型划分的两种方式。
我的理解
我的理解是:
- 领域名一般在最上
- 剩余三个,综合考量,谁最重要谁在前,不重要的也可以省略。
- 层名中的应用层(Application)是很重要的一个考量点,如果应用层编排时会用到多个模块,则应用层不能低于模块名。本人做的项目大多是这种情况,一般层名为领域名下。
- 如果模块清晰,且便于解耦,则分层结构中至少包含模块,模块越清晰,则越靠前,如:
业务域名.层名.业务模块名.*
业务域名.构造型名.业务模块名.*
//如果模块很清晰
业务域名.业务模块名.*
业务域名.业务模块名.层名.*
业务域名.业务模块名.构造型名.*
业务域名.业务模块名.(层名 & 构造型名).*
- 层名更偏向于流程清晰,一般位于领域名下, 如果综合考量,模块的重要性 > 流程的重要性,那么层名也可位于模块名下,如:
业务域名.层名.*
├─catalog // 商品领域层
│ ├─application // 应用层
│ ├─domain // 领域层
│ ├─infrastructure // 基础设施层
│ └─presentation // 表现层
业务域名.层名.业务模块名.*
├─catalog // 商品领域层
│ ├─application // 应用层
│ │ ├─brand
│ │ ├─category
│ │ └─product
│ ├─domain // 领域层
│ │ ├─brand // 商品品牌模块
│ │ ├─category // 商品类目模块
│ │ └─product // 商品模块
│ ├─infrastructure // 基础设施层
│ │ └─persistent // 持久化
│ │ ├─jpa
│ │ ├─mybatis
│ │ └─redis
│ └─presentation // 表现层
│ ├─graphql
│ ├─grpc
│ ├─rest
│ ├─view
│ └─websocket
业务域名.业务模块名.层名.*
├─catalog
│ ├─brand
│ │ ├─application
│ │ ├─domain
│ │ ├─infrastructure
│ │ └─presentation
│ ├─category
│ │ ├─application
│ │ ├─domain
│ │ ├─infrastructure
│ │ └─presentation
│ └─product
│ ├─application
│ ├─domain
│ ├─infrastructure
│ └─presentation
- 项目越简单,构造型名越靠前,对于比较复杂的项目,一般位于层名或模块名下
简单项目
├─catalog
│ ├─controller
│ ├─exception
│ ├─model
│ ├─query
│ ├─repository
│ └─service
参考:解读DDD中的包结构