相关文章推荐
逃课的消炎药  ·  SQL split-阿里云·  6 月前    · 
强健的茄子  ·  使用 fuzzywuzzy ...·  1 年前    · 

项目目录(包、模块)结构

在项目的开发阶段,目录结构的划分往往被看做是迈向成功的第一步。这一步的迈出往往伴随着很多方面的权衡(考量),总的来说是两个方面的考量:业务方面和技术方面。

  • 业务方面的考量包括:限界上下文、子域、业务模块。
  • 技术方面的考量包括:软件架构(分层架构、六边形架构)、构造型分类。
  • 目录结构构成

    常见的项目的目录结构基本上由:领域名(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 的业务模块。

    由:领域名(domain)、层名(layer)、构造型名(stereotype)、业务模块名(module)这四个部分组成的目录(包)结构是项目中常采用的。同时在划分目录结构时也可以使用 Module 先进行横向切割的方式。

    分类:
    后端