写点什么

【架构设计】你的应用该如何分层呢?

作者:JAVA旭阳
  • 2023-01-05
    浙江
  • 本文字数:2809 字

    阅读完需:约 9 分钟

前言

最近 review 公司的代码,发现现在整个代码层级十分混乱,一个 service 类的长度甚至达到了 5000 多行。而且各种分层模型 DTO、VO 乱用, 最终出现逻辑不清晰、各模块相互依赖、代码扩展性差、改动一处就牵一发而动全身等问题。


我们在吸取了阿里巴巴的分层规范以及网上的一些经验后,重新梳理总结了属于我们项目的分层规范。


欢迎关注个人公众号『JAVA 旭阳』交流沟通

三层架构 VS 四层架构

我们公司原来的分层采用的是传统的三层架构,比如在构建项目的时候,我们通常会建立三个目录:Web、Service 和 Dao,它们分别对应了表现层、逻辑层还有数据访问层。



这样导致一个很大的问题,随着业务越来越复杂,逻辑层也就是 service 层越来越庞大,所以出现了前面说的 5000 多行的类,可想而知维护成本有多大。


参照阿里发布的《阿里巴巴 Java 开发手册 v1.4.0(详尽版)》,我们可以将原先的三层架构细化成下面的样子:



  • 终端显示层:各端模板渲染并执行显示的层。当前主要是 Velocity 渲染,JS 渲染, JSP 渲染,移动端展示等。

  • 开放接口层:将 Service 层方法封装成开放接口,同时进行网关安全控制和流量控制等。

  • Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。

  • Service 层:业务逻辑层。

  • Manager 层:通用业务处理层。主要实现下面的功能,1) 对第三方平台封装的层,预处理返回结果及转化异常信息,适配上层接口。2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理。3) 与 DAO 层交互,对多个 DAO 的组合复用。

  • DAO 层:数据访问层,与底层 MySQLOracleHbase 等进行数据交互。

  • 外部接口或第三方平台:包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口。


在这个分层架构中主要增加了 Manager 层,它可以将Service层中的一些通用能力比如操作缓存、消息队列的操作下沉,也可以将通过feign调用其他服务的接口进行一层包装,再提供给Service调用,这也就是所谓的防腐层。

VO、DTO、BO、DO 区别

前面讲解了整体的一个分层架构,那么在不同的层级之间必然需要一些模型对象进行流转传递,VO,BO,DO,DTO, 那么他们之间有什么区别呢?


  • VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。

  • DTO(Data Transfer Object):数据传输对象,用于展示层与服务层之间的数据传输对象。

  • BO(Business Object):业务对象,把业务逻辑封装为一个对象,这个对象可以包括一个或多个其它的对象。

  • DO(Domain Object):领域对象,阿里巴巴规范中引入,此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。


  1. VO 和 DTO 什么区别?


VO比较容易混淆的是DTODTO是展示层与服务层之间传递数据的对象,可以这样说,对于绝大部分的应用场景来说,DTOVO的属性值基本是一致的,而且他们通常都是POJO,那么既然有了VO,为什么还需要DTO呢?


例如服务层有一个getUser的方法返回一个系统用户,其中有一个属性是gender(性别),对于服务层来说,它只从语义上定义:1-男性,2-女性,0-未指定,而对于展示层来说,它可能需要用“帅哥”代表男性,用“美女”代表女性,用“秘密”代表未指定。说到这里,可能你还会反驳,在服务层直接就返回“帅哥美女”不就行了吗?对于大部分应用来说,这不是问题,但设想一下,如果需求允许客户可以定制风格,而不同风格对于“性别”的表现方式不一样,又或者这个服务同时供多个客户端使用(不同门户),而不同的客户端对于表现层的要求有所不同,那么,问题就来了。再者,回到设计层面上分析,从职责单一原则来看,服务层只负责业务,与具体的表现形式无关,因此,它返回的 DTO,不应该出现与表现形式的耦合。


  1. BO 和 DTO 的区别?


从用途上进行根本的区别,BO 是业务对象,DTO 是数据传输对象,虽然 BO 也可以排列组合数据,但它的功能是对内的,但在提供对外接口时,BO 对象中的某些属性对象可能用不到或者不方便对外暴露,那么此时 DTO 只需要在 BO 的基础上,抽取自己需要的数据,然后对外提供。在这个关系上,通常不会有数据内容的变化,内容变化要么在 BO 内部业务计算的时候完成,要么在解释 VO 的时候完成。


我们项目根据实际情况总结了分层领域模型的规范:


  1. 前端传入的参数统一使用 DTO 接收

  2. 由于公司是 TO B 项目,只有一个电脑 web 端,所以返回给展示层或者 Feign 返回的对象建议直接用 DTO 返回,特殊情况采用 VO 返回

  3. 由于历史原因,和数据库模型对应的不采用 DO 结尾,直接用原始对象,比如学生表student直接使用Student对象

  4. 目前很多代码存在继承关系,比如 DTO、VO 继承数据库对象,这个坚决不允许

项目目录结构最佳实践

可以参考 github 上面的https://github.com/alvinlkk/mall4cloud这个项目,它是一个极度遵守阿里巴巴代码规约的项目。



这里面有个关于Feign设计的亮点, 主要是为了避免服务提供方修改了接口,而调用方没有修改导致异常的问题。


  1. feign接口抽取出一个独立的模块



  1. 服务中依赖feign模块,实现FeignClient



我们来看下整体的一个结构。


mall4cloud├─mall4cloud-api -- 内网接口│  ├─mall4cloud-api-auth  -- 授权对内接口│  ├─mall4cloud-api-biz  -- biz对内接口│  ├─mall4cloud-api-leaf  -- 美团分布式id生成接口│  ├─mall4cloud-api-multishop  -- 店铺对内接口│  ├─mall4cloud-api-order  -- 订单对内接口│  ├─mall4cloud-api-platform  -- 平台对内接口│  ├─mall4cloud-api-product  -- 商品对内接口│  ├─mall4cloud-api-rbac  -- 用户角色权限对内接口│  ├─mall4cloud-api-search  -- 搜索对内接口│  └─mall4cloud-api-user  -- 用户对内接口├─mall4cloud-auth  -- 授权校验模块├─mall4cloud-biz  -- mall4cloud 业务代码。如图片上传/短信等├─mall4cloud-common -- 一些公共的方法│  ├─mall4cloud-common-cache  -- 缓存相关公共代码│  ├─mall4cloud-common-core  -- 公共模块核心(公共中的公共代码)│  ├─mall4cloud-common-database  -- 数据库连接相关公共代码│  ├─mall4cloud-common-order  -- 订单相关公共代码│  ├─mall4cloud-common-product  -- 商品相关公共代码│  ├─mall4cloud-common-rocketmq  -- rocketmq相关公共代码│  └─mall4cloud-common-security  -- 安全相关公共代码├─mall4cloud-gateway  -- 网关├─mall4cloud-leaf  -- 基于美团leaf的生成id服务├─mall4cloud-multishop  -- 商家端├─mall4cloud-order  -- 订单服务├─mall4cloud-payment  -- 支付服务├─mall4cloud-platform  -- 平台端├─mall4cloud-product  -- 商品服务├─mall4cloud-rbac  -- 用户角色权限模块├─mall4cloud-search  -- 搜索模块└─mall4cloud-user  -- 用户服务
复制代码

结束语

关于应用分层这块我觉得没那么简单,特别是团队大了以后,人员水平参差不齐,很难约束,很容易自由发挥,各写各的。不知道大家有没有什么好的办法呢?


欢迎关注个人公众号『JAVA 旭阳』交流沟通

发布于: 刚刚阅读数: 5
用户头像

JAVA旭阳

关注

欢迎关注个人公众号—— JAVA旭阳 2018-07-18 加入

旭阳,希望自己能像初升的太阳一样,对任何事情充满希望~~

评论

发布
暂无评论
【架构设计】你的应用该如何分层呢?_Java_JAVA旭阳_InfoQ写作社区