28

代码分层的设计之道

 5 years ago
source link: http://blog.720ui.com/2018/server_layer/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
mIvYfmi.png!web

分层思想,是应用系统最常见的一种架构模式,我们会将系统横向切割,根据业务职责划分。MVC 三层架构就是非常典型架构模式,划分的目的是规划软件系统的逻辑结构便于开发维护。MVC:英文即 Model-View-Controller,分成模型层、视图层、控制层。将页面和业务逻辑分离,提高应用的可扩展性及可维护性。如图所示。

b2a2Afq.png!web

事实上,MVC 三层架构只是概念层面的指导思想,我们会将层次结构划分的更加细致。例如,传统后端的 MVC 模式对于前后端的划分界限比较模糊。一般情况下,前端开发人员负责编写项目的静态页面,包括 HTML 页面、CSS 样式与 JavaScript 交互部分,并提供给服务端开发人员编写视图层业务,甚至有的项目直接让前端开发人员完成视图层的业务开发任务。这样的开发模式造成的问题在于,前后端在开发过程中分工不明确,并且存在相互强依赖,前端开发人员需要关心服务端的业务,服务端开发人员也需要依赖前端的进度。并且随着 Android、 IOS、 PC 以及 U3D 等多个客户端加入,程序的开发成本与维护成本会指数级上升。为了提高开发效率,细化职责,前后端分离的需求越来越被重视。前后端分离在于服务端提供 API 接口,前端调用 AJAX 实现数据交互。如图所示。

emYzEnf.png!web

此外,随着数据存储能力的不断扩展(MySQL、Oracle、Redis、MongoDB、ElasticSearch、PostgreSQL、HBase 等),以及随着微服务的流行与普及,我们经常通过 RPC(Dubbo、HSF、Thrift 等)依赖很多外部接口或 HTTP 调用第三方平台。因此,我们需要一套细致划分的代码结构。此外,很多时候,我们在开发过程中,也并没有把它们职责划分开。例如,在代码结构中,我们将非常多的逻辑业务放在了 Controller 层,而只把 Service 作为数据透传的途径了。事实上,这个是不对的。无独有偶,我们还会发现有的项目中在 Dao 层调用远程服务,也有的会在 Service 层或者 Controller 层进行这样的操作,由于不同研发同学的习惯不同,或者偷工取巧导致开发代码风格完全不同,代码层次结构混乱。

总结一下,MVC 三层架构只是概念层面的指导思想,我们会将层次结构划分的更加细致。现在,我们来深入探讨“ 如何合理的设计代码分层,论代码分层的设计之道 ”。在笔者看来,合理的代码分层应该是这样的。如图所示。

IBzEfiy.jpg!web 其中, 数据持久层 承载了数据存储和访问的能力,它既与底层数据进行交换,包括 MySQL、Oracle、Redis、MongoDB、ElasticSearch、PostgreSQL、HBase 等,又通过 Pxoxy 的代理和包装与远程服务数据进行联动。因此,在业务逻辑层调用时,它对底层的数据实现方式是无感知的,无论是哪种数据存储方式,以及它是远程数据,还是本地数据,都可以非常容易的调用。换句话说,我们需要将数据的查询和更改操作限制在数据持久层,并只能被业务逻辑层访问。

那么, 业务逻辑层 的职责是与 数据持久层 交互,对多个数据源的操作进行聚合,并且提供组合复用的能力。此外,它也是业务通用能力的处理层,其中还包括缓存方案、消息监听(MQ)、定时任务等等。此外,我们要将尽可能多的业务处理放在 业务逻辑层 ,包括了参数校验、数据转换、异常处理等,而不是在 Controller 再去处理。

笔者认为:请求处理层具有三块能力,一个是通过模板引擎渲染,例如 FreeMarket、Velocity 的页面渲染,以及通过 Controller 层封装的 RESTful API 的 HTTP 接口。如果项目中用到了 Dubbo、HSF、Thrift 等 RPC 服务,我们还需要提供对于的服务给上游的业务方使用,它通过 Service 来实现并暴露成 RPC 接口。这里,Service 的命名是相对的,一般通过 Client 提供接口,通过 Service 实现具体的业务逻辑。

我们了解了逻辑结构,那么,笔者认为比较清晰的物理代码结构应该是这样的。

UBvai2U.png!web

那么,我们可以跨层级调用吗?笔者认为:我们需要禁止跨层级调用,因为每个层级都自己的职责,并且对上层而言是透明的,就像 OSI 七层协议模型和 TCP/IP 四层协议模型一样,只有将职责限制在自己的边界内,整体层次结构才清晰明了。那么对于同级调用,笔者认为在业务逻辑层是允许的,但是,要特别注意循环调用的产生。

现在,我们再横向理解几个领域模型:VO、BO、DO、DTO。这个概念,是由阿里编码规约提到的,由于其业务非常复杂,因此为了更好地进行领域建模和模型隔离,提出了这几个概念。其中,DO(Data Object)与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。 而 DTO(Data Transfer Object)是远程调用对象,它是 RPC 服务提供的领域模型。注意的是,对于 DTO 一定要保证其序列化,实现 Serializable 接口,并显示提供 serialVersionUID,否则在反序列化时,如果 serialVersionUID 被修改,那么反序列化会失败。事实上,DO 和 DTO 唯一的区别在于,一个是本地数据源的领域模型,一个是远程服务的序列化领域模型。对于 BO(Business Object),它是业务逻辑层封装业务逻辑的对象,一般情况下,它是聚合了多个数据源的复合对象。那么,VO(View Object) 通常是请求处理层传输的对象,它通过 Spring 框架的转换后,往往是一个 JSON 对象。例如,你需要解决 Long 类型的数据精度丢失的问题(如果直接传给 Web 端的话,在 Long 长度大于 17 位时会出现精度丢失),你就可以在 Controller 层通过 @ResponseBody 将返回数据自动转换成 JSON 时,统一封装成字符串。

总结一下,分层思想,将系统横向切割,根据业务职责划分。划分的目的是规划软件系统的逻辑结构便于开发维护。但是,随着微服务的演变和不同研发的编码习惯,往往导致了代码分层不彻底导致引入了“坏味道”。笔者试图提出一个新的代码分层思路来规避和规范这种分层结构。

如果你有不同的想法和兴趣,欢迎加我微信一起探讨与交流哟~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK