当前位置: 首页 > 科技观察

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

时间:2023-03-16 02:04:16 科技观察

前言最近查阅了公司的代码,发现整个代码层次非常混乱,一个服务类的长度甚至达到了5000多行。而且各种分层模型DTO、VO乱用,最后出现逻辑不清晰、模块相互依赖、代码扩展性差、改动一处牵一发而动全身等问题。在吸收了阿里巴巴的分层规范和网上的一些经验后,我们重新整理总结了属于我们项目的分层规范。三层架构VS四层架构我们公司原有的分层采用传统的三层架构。例如,在构建项目时,我们通常会创建三个目录:Web、Service、Dao,分别对应表现层、逻辑层和数据访问层。这就导致了一个大问题。随着业务越来越复杂,逻辑层也就是服务层越来越大,于是出现了上面说的5000多行的类。维修费用是多少可想而知。参考阿里发布的《阿里巴巴 Java 开发手册 v1.4.0(详尽版)》,我们可以将原来的三层架构细化为:终端展示层:各端模板渲染和执行展示的层。目前主要是Velocity渲染、JS渲染、JSP渲染、移动端展示等。接口层开放:将Service层方法封装成一个开放接口,同时进行网关安全控制和流量控制。Web层:主要用于访问控制的转发,各种基础参数的验证,或者对不可复用服务的简单处理等。服务层:业务逻辑层。管理层:一般业务处理层。主要实现以下功能,1)针对第三方平台封装的层,对返回结果进行预处理和异常信息转换,适配上层接口。2)Service层通用能力的下沉,如缓存解决方案、中间件通用处理等。3)与DAO层交互,复用多个DAO。DAO层:数据访问层,与底层MySQL、Oracle、Hbase等进行数据交互。对外接口或第三方平台:包括其他部门的RPC开放接口、基础平台、其他公司的HTTP接口。在这个分层架构中,主要增加了Manager层,可以下沉Service层的一些通用能力,比如操作缓存,消息队列操作,也可以通过feign包装调用其他服务的接口,然后提供给调用Service,这就是所谓的防腐层。VO、DTO、BO、DO的区别。前面解释了整体分层架构,所以一些模型对象必须在不同层次之间传递。VO、BO、DO、DTO,那么它们有什么区别呢??VO(ViewObject):视图对象,用于展示层,其作用是封装指定页面(或组件)的所有数据。DTO(DataTransferObject):数据传输对象,用于显示层与服务层之间的数据传输对象。BO(BusinessObject):业务对象,将业务逻辑封装为一个对象,其中可以包含一个或多个其他对象。DO(DomainObject):领域对象,在阿里巴巴规范中引入,该对象与数据库表结构一一对应,通过DAO层向上传输数据源对象。VO和DTO有什么区别?VO更容易与DTO混淆,DTO是表示层和服务层之间传递数据的对象。可以说对于大部分应用场景来说,DTO和VO的属性值基本一致,而且一般都是POJO,既然有了VO,为什么还需要DTO呢?比如服务层有一个getUser方法返回一个系统用户,其中一个属性是gender(性别)。对于服务层,只在语义上定义:1-男,2-女,0-未指定,而对于表现层,可能需要男性使用“帅气”,女性使用“美丽”,“秘密”对于未指定的。说到这里,你可能还会反驳,服务层只返回“帅哥美女”不就可以了吗?对于大多数应用来说,这不是问题,但是想象一下,如果需求允许客户自定义样式,并且不同的样式对“性别”有不同的表达,或者这个服务被多个客户端同时使用(不同的入口),而不同的客户端对表现层的要求也不同,那么问题就来了。进一步回到设计层面的分析,从单一职责原则来看,服务层只负责业务,与具体的表现形式无关。所以,它返回的DTO不应该加上表达式的形式。BO和DTO有什么区别?在使用方面有根本的区别。BO是业务对象,DTO是数据传输对象。BO虽然也可以对数据进行排列组合,但是其作用是对内的,但是在对外提供接口的时候,BO对象中的一些属性对象可能用不到或者不方便暴露给外界。这时DTO只需要在BO的基础上提取自己需要的数据,然后对外提供即可。在这种关系中,数据内容通常不会发生变化,内容变化要么在BO内部业务计算时完成,要么在VO解释时完成。我们的项目根据实际情况总结了层次域模型的规范:前端传入的参数统一由DTO接收。由于公司是TOB项目,只有一台电脑web端,所以返回表现层的对象建议直接使用DTO或者Feign。返回,特殊情况使用VO返回。由于历史原因,对应的数据库模型没有使用DO结尾,直接使用原始对象。比如student表student直接使用了Student对象。目前很多代码都有继承关系,比如DTO、VO继承数据库对象。这种坚决不允许项目目录结构的最佳实践。可以参考github上的https://github.com/alvinlkk/mall4cloud项目,这是一个严格遵守阿里巴巴代码协议的项目。Feign的设计有个亮点,主要是为了避免服务提供者修改接口而调用者不修改接口导致异常的问题。将feign接口抽离成一个独立的模块服务,依赖于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--公共模块核心(公共代码inpublic)│├─mall4cloud-common-database--数据库连接相关的公共代码│├─mall4cloud-common-order--订单相关的公共代码│├─mall4cloud-common-product--产品相关的公共代码│├─mall4cloud-common-rocketmq--rocketmq相关公开代码│└─mall4cloud-common-security--安全相关公开代码├─mall4cloud-gateway--网关├─mall4cloud-leaf--基于美团的id生成服务leaf├─mall4cloud-multishop--商家端├─mall4cloud-order--订单服务├─mall4cloud-payment--支付服务├─mall4cloud-platform--平台端├─mall4cloud-product--商品服务├─mall4cloud-rbac--用户角色权限模块├─mall4cloud-search--搜索模块└─mall4cloud-user--用户服务