⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Onemall/Application-layer/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

1. 概述

本文,我们来分享下 Onemall 电商开源项目的后端的应用分层规范。

目前,Onemall 的电商后端,采用 Spring Boot + Dubbo 的方式,提供给前端接口,也就是说,采用前后端分离的方式。为什么要提这一点呢,我们往下来瞅瞅。

2. 阿里巴巴规范

在说 Onemall 的应用分层规范之前,我们先来看看阿里巴巴分享的应用分层规范。

如下内容,引用自 《阿里巴巴Java开发手册(详尽版)》

考虑到排版,下面内容就不使用引用先。

  1. 【推荐】图中默认上层依赖于下层,箭头关系表示可直接依赖,如:开放接口层可以依赖于Web层,也可以直接依赖于Service层,依此类推: 应用分层
  • 开放接口层:可直接封装Service方法暴露成RPC接口;通过Web封装成http接口;进行网关安全控制、流量控制等。
  • 终端显示层:各个端的模板渲染并执行显示的层。当前主要是velocity渲染,JS渲染,JSP渲染,移动端展示等。
  • Web层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
  • Service层:相对具体的业务逻辑服务层。
  • Manager层:通用业务处理层,它有如下特征: - 1) 对第三方平台封装的层,预处理返回结果及转化异常信息; - 2) 对Service层通用能力的下沉,如缓存方案、中间件通用处理; - 3) 与DAO层交互,对多个DAO的组合复用。
  • DAO层:数据访问层,与底层MySQL、Oracle、Hbase等进行数据交互。
  • 外部接口或第三方平台:包括其它部门RPC开放接口,基础平台,其它公司的HTTP接口。
  1. 【参考】(分层异常处理规约)在DAO层,产生的异常类型有很多,无法用细粒度的异常进行catch,使用catch(Exception e)方式,并throw new DAOException(e),不需要打印日志,因为日志在Manager/Service层一定需要捕获并打印到日志文件中去,如果同台服务器再打日志,浪费性能和存储。在Service层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息,相当于保护案发现场。如果Manager层与Service同机部署,日志方式与DAO层处理一致,如果是单独部署,则采用与Service一致的处理方式。Web层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该跳转到友好错误页面,加上用户容易理解的错误提示信息。开放接口层要将异常处理成错误码和错误信息方式返回。

  2. 【参考】分层领域模型规约:

  • DO(Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象。
  • DTO(Data Transfer Object):数据传输对象,Service或Manager向外传输的对象。
  • BO(Business Object):业务对象。由Service层输出的封装业务逻辑的对象。
  • AO(Application Object):应用对象。在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
  • VO(View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象。
  • Query:数据查询对象,各层接收上层的查询请求。注意超过2个参数的查询封装,禁止使用Map类来传输。

3. Onemall 的选择

看到阿里巴巴的规范之后,胖友是不是一脸懵逼,竟然有这么多 POJO ?!每个公司的业务复杂度不同,架构不同,所以 POJO 的选择实际会有不同。当然,艿艿觉得,原则上是 Service 不将 DO 数据库实体从 Service 暴露到 Controller ,避免后续数据库设计的变化,影响暴露出去的方法。

🔥 如下图,是 Onemall 的应用分层选择:应用分层

我们按照自下而上,来看看各层的选择。

  • 按照 Controller、Service、DAO 分成三层,去掉 Manager 层。
    • 大多数业务场景下,无需与第三方平台对接。
    • 当然,如果需要和第三方对接,还是会封装成 Client ,例如说 Pay Client 和 第三方支付平台的对接。😈 所以实际还是有“隐藏”的 Manager 层。
  • DAO 层
    • 入参,使用 DO(Data Object)。
    • 出参,使用 DO(Data Object)。
  • Service 层
    • 入参,使用 DTO(Data Transfer Object)。
      • 需要加上 Bean Validation 注解,从而校验参数。
      • 需要加上 Swagger API 注解,因为后续 Controller 很大可能性会使用到它,从而生成 API 文档。更细的原因,我们在 Controller 层一起讲。
      • 示例:AdminAddDTOAdminUpdateDTO
    • 出参,使用 BO(Business Object)。
      • 本来考虑使用 DTO ,考虑到区分,所以使用了 BO 。😈 当然,我了解到蛮多朋友返回是使用 DTO 的,问题不大。
      • 需要加上 Swagger API 注解,原因同 DTO 。
      • 示例:AdminBO
  • Controller 层
    • 入参,使用 DTO(Data Transfer Object)。
      • 因为前后端分离之后,Controller 大多数情况下,基本是将 Service 进行封装,提供 API 接口。所以大多数情况,Service DTO 可以重用,所以就默许使用 Service DTO 。😈 当然,这块有不同意见的胖友,可以一起来讨论下,我也挺纠结的。
      • 当然,如果 Service DTO 不够用的情况下,可以自己在创建下 Controller DTO 。
      • 本来想 Controller 单独在取个 XXO 的名字,结果想了半天没想出来,就继续沿用 Service DTO 了。
      • 所以,因为是这样的设定,我们就要求 Service DTO 上,增加 Swagger API 注解。
    • 出参,使用 BO(Business Object)。
      • 原因,也是同 Controller 入参。
      • 当然,如果 Service BO 不够用的情况下,可以自己在创建下 Controller VO 。

艿艿:每个公司的分层架构不同,欢迎一起讨论。妥妥的。

因为分层规范是后来调整的,所以项目中可能有部分不符合这样的规范,具体以示例为主。

🔥 聊完了应用分层的话题,我们在一起讨论下 Service 逻辑异常的时候,如何进行返回。这里的逻辑异常,我们指的是,例如说用户名已经存在,商品库存不足等。一般来说,常用的方案选择,有两种:

  • 封装统一的业务异常类 ServiceException ,里面有错误码和错误提示,然后进行 throws 抛出。
  • 封装通用的返回类 CommonResult ,里面有错误码和错误提示,然后进行 return 返回。

一开始,我们选择了 CommonResult ,结果发现如下情况:

  • 因为 Spring @Transactional 声明式事务,是基于异常进行回滚的,如果使用 CommonResult 返回,则事务回滚会非常麻烦。
  • 当调用别的方法时,如果别人返回的是 CommonResult 对象,还需要不断的进行判断,写起来挺麻烦的。

所以,后来我们采用了抛出业务异常 ServiceException 的方式。

🔥 可能机智的小伙伴会问,如果抛出异常,Controller 如何做通用的处理,答案在 GlobalExceptionHandler 类。结果 Spring MVC 的 Exception 处理机制,我们会将 ServiceException 转换成 CommonResult 对象,返回给前端。

当然,故事还没有结束,Controller 虽然返回的是 BO / VO 对象,我们选择在外面包了一层 CommonResult ,用于返回可能存在的业务逻辑错误的情况。因为呢,HTTP API 是语言无关,无法使用 Java Excpetion 。

不过哈,最初我们使用了 @ControllerAdvice 机制,自动全局将 BO / VO 对象,包装成 CommonResult 对象,但是和基友 didi 讨论了下这个选择,建议还是 Controller 显示声明 CommonResult 返回,考虑点是 AOP 不应该破坏方法的 Schema ,即有一天去掉这个 AOP ,依然返回的是 CommonResult 。

4. 彩蛋

🔥 最后的最后,大家总是会讨论到的一个问题,这么多 POJO 对象,如何进行复制呢?Onemall 采用 mapstruct ,因为广告法的原因,我们不能说它是最好用的,但是的确是(并且,效果还非常非常非常的高),哈哈哈哈。具体的示例,可以看看 AdminConvert

文章目录
  1. 1. 1. 概述
  2. 2. 2. 阿里巴巴规范
  3. 3. 3. Onemall 的选择
  4. 4. 4. 彩蛋