掘金 后端 ( ) • 2024-04-08 14:03

本文主要说明下单体项目的工程结构如何设计,目前业界存在两种主流的应用工程结构:一种是阿里推出的《 Java 开发手册》中推荐的,另外一种是基于 DDD (领域驱动设计)推荐的。下面我们来看下两种工程结构是怎样的。

一、 基于阿里《 Java 开发手册》的分层结构

阿里巴巴的官方文档的分层主要是分为如下这些层:

  • 开放API 层:可直接封装Service 接口暴露成RPC 接口;通过Web 封装成http 接口;网关控制层等。
  • 终端显示层:各个端的模板渲染并执行显示的层。当前主要是velocity 渲染,JS 渲染,JSP 渲染,移动端展示等。
  • Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
  • Service 层:相对具体的业务逻辑服务层。
  • Manager 层:通用业务处理层,它有如下特征:

1)对第三方平台封装的层,预处理返回结果及转化异常信息,适配上层接口。

2)对Service 层通用能力的下沉,如缓存方案、中间件通用处理。

3)与DAO 层交互,对多个DAO 的组合复用。

  • DAO 层:数据访问层,与底层MySQL、Oracle、Hbase、OB 等进行数据交互。
  • 第三方服务:包括其它部门RPC 服务接口,基础平台,其它公司的HTTP 接口,如淘宝开放平台、支付宝付款服务、高德地图服务等。
  • 外部数据接口:外部(应用)数据存储服务提供的接口,多见于数据迁移场景中。

调用关系如下:

image

涉及到的领域模型如下:

DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。

DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。

BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象。

Query:数据查询对象,各层接收上层的查询请求。注意超过2 个参数的查询封装,禁止使用Map 类来传输。 VO(View Object):显示层对象,通常是Web 向模板渲染引擎层传输的对象

基于这样的规范我创建了一个 demo 工程的目录,下面我来解释下这个结构

$├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─demo
│  │  │          ├─client           --第三方服务:调用第三方服务
│  │  │          │  ├─model         --模型对象
│  │  │          │  └─service       --接口
│  │  │          ├─controler        --终端显示层
│  │  │          │  ├─request       --请求对象
│  │  │          │  ├─response      --响应包装对象
│  │  │          │  └─vo            --不同接口显示层对象
│  │  │          ├─dao              --DAO 层
│  │  │          │  ├─config        --数据库的配置文件
│  │  │          │  ├─dbclient      --与底层MySQL、Oracle、Hbase等进行数据交互的文件
│  │  │          │  ├─query         --数据查询对象
│  │  │          │  ├─do            --此对象与数据库表结构一一对应
│  │  │          │  ├─serializer    --序列化相关  
│  │  │          │  ├─sharding      --分库分表类目
│  │  │          │  └─utils         --一些公共类
│  │  │          ├─facede           --开放API 层:对外封装的Service接口暴露成RPC接口
│  │  │          │  ├─constant      --常量
│  │  │          │  ├─model         
│  │  │          │  ├─request       --请求对象
│  │  │          │  └─service       --接口层
│  │  │          ├─job              --开放API 层:处理job工作           
│  │  │          │  └─model         --数据传输对象
│  │  │          ├─manager          --Manager 层
│  │  │          │  └─dto           
│  │  │          ├─mq               --开放API 层:处理mq工作
│  │  │          │  ├─config
│  │  │          │  ├─consumer      --处理消费者工作
│  │  │          │  ├─model
│  │  │          │  └─publish       --处理生产者工作
│  │  │          ├─service          --Service 层
│  │  │             ├─schedule      --处理定时任务
│  │  │          │  ├─bo
│  │  │          │  └─dto
│  │  │          └─web              --Web 层
│  │  │              ├─filter       --过滤层
│  │  │              └─Interceptor  --拦截器层
│  │  └─resources                   --资源文件
│  │      └─META-INF
│  └─test                           --针对每个层次文件的测试文件
│      └─java
│          └─com
│              └─demo
│                  ├─client
│                  │  ├─model
│                  │  └─service
│                  ├─controler
│                  │  ├─request
│                  │  ├─response
│                  │  └─vo
│                  ├─dao
│                  │  ├─config
│                  │  ├─dbclient
│                  │  ├─do
│                  │  ├─query
│                  │  ├─sharding
│                  │  ├─serializer
│                  │  ├─sharding
│                  │  └─utils
│                  ├─facede
│                  │  ├─constant
│                  │  ├─model
│                  │  ├─request
│                  │  └─service
│                  ├─job
│                  │  └─model
│                  ├─manager
│                  │  └─dto
│                  ├─mq
│                  │  ├─config
│                  │  ├─consumer
│                  │  ├─model
│                  │  └─publish
│                  ├─service
│                  │  ├─schedule
│                  │  ├─bo
│                  │  └─dto
│                  └─web
│                      ├─filter
│                      └─Interceptor
└─$

可能上面的信息比较多看者比较不清晰,下面我们用列表来展示下:

层次 包名 对象名规范 备注 测试层 test 对象名规范同各层级 开放API 层 job、mq、 │model model为模型对象 终端显示层 controler request、response、vo Web 层 web Service层 service bo、dto Manager层 manager dto DAO层 dao query、do 第三方服务 client model

二、 基于DDD(领域驱动设计)的分层结构

DDD(领域驱动设计,Domain-Driven Design)的分层结构主要是为了更好地组织和分离关注点,确保业务逻辑的清晰性和可维护性。DDD 的分层结构通常包括以下几个层次:

  1. 用户接口层(User Interface Layer):
  • 这一层主要负责与前端应用或其他外部服务的交互。
  • 它会接收和响应外部请求,并将业务逻辑的结果展示给用户。
  • 这一层不应该包含任何业务逻辑,主要是数据的展示和接收。
  1. 应用服务层(Application Service Layer):
  • 这一层是DDD中的业务编排层,它协调领域层中的各个组件来执行特定的业务用例。
  • 应用服务层通常包含服务组合、编排、安全认证、权限校验、事务控制等功能。
  • 应用服务层可能会调用多个领域服务来完成一个复杂的业务操作。
  1. 领域层(Domain Layer):
  • 这是DDD中的核心层,包含了业务领域的所有核心逻辑和概念。
  • 领域层主要由实体(Entity)、值对象(Value Object)、领域服务(Domain Service)和领域事件(Domain Event)等组成。
  • 实体和值对象表达了业务概念,而领域服务则实现了复杂的业务规则。
  1. 基础设施层(Infrastructure Layer):
  • 这一层提供了技术实现和支撑服务,如数据库访问、消息传递、缓存等。
  • 基础设施层为上层提供了技术细节的实现,隐藏了底层技术的复杂性。
  • 基础设施层通常实现了领域层中定义的接口和协议,使得上层可以专注于业务逻辑。

DDD 的分层结构遵循了依赖倒置原则(Dependency Inversion Principle),即高层模块不应该依赖于低层模块,它们都应该依赖于抽象。此外,DDD 的分层结构还强调了领域层的独立性和核心地位,确保业务逻辑的稳定性和可重用性。四个层次调用关系如下:

image

根据这样的架构我们来理解下和工程结构的映射关系大致如下:

image

基于 DDD ,我们也可以根据这样的架构来设计一个工程结构,如下:

│    │ 
│    ├─apis   API接口层 
│    │    └─controller       控制器,对外提供(Restful)接口
│    │ 
│    ├─application  应用层
│    │    ├─model            数据传输对象模型及其装配器(含校验)
│    │    │    ├─assembler   装配器,,实现模型转换eg. apiModel<=> domainModel
│    │    │    └─dto         模型定义(含校验规则)      
│    │    ├─service          应用服务,非核心服务,跨领域的协作、复杂分页查询等
│    │    ├─task             任务定义,协调领域模型
│    │    ├─listener         事件监听定义
│    │    └─***              others
│    │ 
│    ├─domain   领域层
│    │    ├─common           模块0-公共代码抽取,限于领域层有效  
│    │    ├─module-xxx       模块1-xxx,领域划分的模块,可理解为子域划分     
│    │    ├─module-user      模块2-用户子域(领域划分的模块,可理解为子域划分)
│    │    │    ├─action      行为定义
│    │    │    │    ├─UserDomainService.java        领域服务,用户领域服务
│    │    │    │    ├─UserPermissionChecker.java    其他行为,用户权限检查器
│    │    │    │    ├─WhenUserCreatedEventPublisher.java     领域事件,当用户创建完成时的事件 
│    │    │    ├─model       领域聚合内模型 
│    │    │    │    ├─UserEntity.java                领域实体,有唯一标识的充血模型,如本身的CRUD操作在此处
│    │    │    │    ├─UserDictVObj.java              领域值对象,用户字典kv定义       
│    │    │    |    ├─UserDPO.java                   领域负载对象    
│    │    │    ├─repostiory  领域仓储接口
│    │    │    │    ├─UserRepository.java
│    │    │    ├─reference   领域适配接口
│    │    │    │    ├─UserEmailSenderFacade.java
│    │    │    └─factory     领域工厂  
│    │ 
│    ├─infrastructure  基础设施层
│    │    ├─persistence      持久化机制
│    │    │    ├─converter   持久化模型转换器
│    │    │    ├─po          持久化对象定义 
│    │    │    └─repository.impl  仓储类,持久化接口&实现,可与ORM映射框架结合
│    │    ├─general          通用技术支持,向其他层输出通用服务
│    │    │    ├─config      配置类
│    │    │    ├─toolkit     工具类  
│    │    │    ├─extension   扩展定义  
│    │    │    └─common      基础公共模块等 
│    │    ├─reference        引用层,包装外部接口用,防止穿插到Domain层腐化领域模型等
│    │    │    ├─dto         传输模型定义
│    │    │    ├─converter   传输模型转换器       
│    │    │    └─facade.impl 适配器具体实现,此处的RPC、Http等调用
│    │ 
│    └─resources  
│        ├─statics  静态资源
│        ├─template 系统页面 
│        └─application.yml   全局配置文件

以上就是我对于两种工程结构的阐述,对于两种项目结构,大家要根据自己的项目情况和公司情况进行微调。原因是每种理论都有自己的产生背景、适用条件和使用方法,不能硬搬硬套。而且理论也是有很多变种的,例如 DDD 也有很多种架构的,例如:洋葱架构、六边形架构等,根据每项架构我们的工程结构肯定还会有一些变种,限于知识的理解不深,本次就不再描述了。DDD 我了解的专家是张逸老师,他的著作《解构式领域驱动设计》对于 DDD 的解释是比较全面的,我们做技术的也是基于在一个公司里面做业务,所以工作的上下文中又有了公司和业务者两块,针对这两块,现在的认识是要通过学习 TOGAF 这样的企业架构框架和周金根老师的《业务架构解构与实践》这样的著作来深化自己对做好技术工作的理解。

三、两种结构的适用背景

1、阿里巴巴分层架构

现在对这个结构的认识是比较适用于:

1、在团队还比较小,职责还不是那么清晰的时候,

2、发展变化比较大的业务,因为此时还没有明确的领域概念,团队设置上也没有专人来保证领域的设计;

3、重心在与快速的支撑业务。

2、DDD分层架构

DDD 模式适用于以下几种场景:

1、支持处理复杂业务逻辑场景:

当应用程序需要处理复杂的业务逻辑时,DDD 可以将业务逻辑封装在领域模型中,从而更好地反映业务需求和业务流程,降低了系统架构的复杂度;

2、多团队协作:

当多个团队共同开发一个大型系统时,DDD 分层架构可以帮助团队之间更好地协作,每个团队负责不同的领域模型和业务逻辑,减少冲突和重复开发;

3、大型业务需要长期演化并快速迭代和交付的场景:

每个子域都可以独立开发、部署和扩展,这样可以使得团队可以快速迭代和交付应用程序。

如果将普通的 CRUD 业务系统也按照这套模式实现,反而会增加系统的复杂度。在现在的认识中,一般中大型的技术团队要能支撑起 DDD 这样的分层架构,在组织层面也至少达到了一定的复杂程度才可见效,例如至少有企业架构师、领域架构师(应用架构师)、业务架构师(应用架构师)、技术架构师、基础结构架构师,基础架构架构师有的有会分为、安全架构师、数据架构师其中的几个,但是在团队比较小变化大的业务中,很难支撑这样的团队配置,导致要在做好业务架构设计和时间、人力成本、机会成本、人员职责等产生了矛盾,最终妥协的结构应该就是采取阿里巴巴分层来快速低成本的支持业务跑起来。