《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接口。
  2. 【参考】(分层异常处理规约)在DAO层,产生的异常类型有很多,无法用细粒度的异常进行catch,使用catch(Exception e)方式,并throw new DAOException(e),不需要打印日志,因为日志在Manager/Service层一定需要捕获并打印到日志文件中去,如果同台服务器再打日志,浪费性能和存储。在Service层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息,相当于保护案发现场。如果Manager层与Service同机部署,日志方式与DAO层处理一致,如果是单独部署,则采用与Service一致的处理方式。Web层绝不应该继续往上抛异常,因为已经处于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该跳转到友好错误页面,加上用户容易理解的错误提示信息。开放接口层要将异常处理成错误码和错误信息方式返回。

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

    • 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. 彩蛋