我项目中的代码都是如何分层的?

一、背景

提及应用分层,大部分人都会认为这个不是很简单嘛 就controller,service, mapper三层。看起来简单,不少人其实并无把他们职责划分开,在不少代码中,controller作的逻辑比service还多,service每每当成透传了,这实际上是不少人开发代码都没有注意到的地方,反正功能也能用,至于放哪无所谓呗。这样每每形成后面代码没法复用,层级关系混乱,对后续代码的维护很是麻烦。程序员

的确在这些人眼中分层只是一个形式,前辈们的代码这么写的,其余项目代码这么写的,那么我也这么跟着写。数据库

可是在真正的团队开发中每一个人的习惯都不一样,写出来的代码必然带着本身的标签,有的人习惯controller写大量的业务逻辑,有的人习惯在service中之间调用远程服务,这样就致使了每一个人的开发代码风格彻底不一样,后续其余人修改的时候,一看,我靠这我的写的代码和我日常的习惯彻底不一样,修改的时候究竟是按着本身之前的习惯改,仍是跟着前辈们走,这又是个艰难的选择,选择一旦有误差,你的后辈又维护你的代码的时候,恐怕就要骂人了。apache

因此一个好的应用分层须要具有如下几点:编程

  • 方便后续代码进行维护扩展;后端

  • 分层的效果须要让整个团队都接受;缓存

  • 各个层职责边界清晰。微信

二、应用分层模型

在项目开发中,一个良好的工程架构是必须的。工程架构就像一个骨架,写代码就是在这个骨架上增添血肉,这个骨架会影响到总体的模块划分,功能划分,即会影响到代码的解耦和聚合,将会很大程度上决定一个项目写得好很差。数据结构

这里要分享的是我我的在开发时所采起的工程架构,以及相关的思想。不一样的人对于工程架构的理解会不一样,实际上也很难分出哪一种好,哪一种坏,只要符合本身的设计思想,而且可以有效的进行开发,那就是好的一种架构方式。架构

2.一、分层

我总体上的思想为《阿里巴巴 Java 开发手册》中所描述的分层模型。以下:app

应用分层图

接下来将自底向上的讲解我对各层的理解和设计,还有我本身所增长的层。

2.二、通用工具层

通用工具层其实为对业务无关的,通用的工具类,例如日期处理的工做累,一些数据格式的序列化与反序列化工具。相似于 apache commons 包和 guava 包。

2.三、分层领域模型

领域模型,也就是咱们以前常见的各类数据实体,用 DDD 的术语来讲,这种在分层模型中的领域模型称为贫血领域模型。 贫血领域模型只做为数据载体,只有 getter/setter 方法,而不包含业务方法。

对于分层领域模型,会进一步进行划分规约,主要也是参考自《阿里巴巴 Java 开发手册》具体以下:

  • DO(Data Object) : 数据对象,对数据源数据的映射,如数据库表,ElasticSearch 索引的数据结构。所在包通常命名为 data 。
  • DTO(Data Transfer Object) : 数据传输对象,业务层向外传输的对象。若是在某个业务中须要屡次查询获取不一样的数据对象,最后将会把这多个数据对象组合成一个 DTO 并对外传输。所在包命名为 dto 。
  • BO(Business Object) : 业务对象,由 Service 层输出的封装业务逻辑的对象。即对象除了数据外,还会包含必定的业务逻辑,也能够说是充血领域模型。可是我通常不会使用。
  • AO(Application Object) : 应用对象,在 Web 层与 Service 层之间抽象的复用对象模型,极为贴近展现层,复用度不高。比较少用。
  • VO(View Object) : 显示层对象,一般是 Web 向模板渲染引擎层传输的对象。如今的项目多数为先后端分离,后端只须要返回 JSON ,那么能够理解为 JSON 便是须要渲染成的“模板”。我通常会将这类对象命名为 xxxResponse ,所在包命名为 response 。
  • Query : 数据查询对象,数据查询对象,各层接收上层的查询请求。其实通常用于 Controller 接受传过来的参数,能够将其都命名为 xxxQuery ,而我我的习惯将放在 request body 的参数(即 @RequestBody)包装为 xxxRequest ,而若是使用表单传输过来的参数(即 @RequestParam)包装为 xxxForm ,并分别放在包 request 和包 form 下。

其实贫血领域模型只是做为数据的载体,在一开始我以为不必进行具体的分类,基本上都是往一个包内丢,可是当项目规模上来后,各类各样的数据实体开始增长,慢慢的变得混乱。对数据对象的分类是为了更好的定义每一个数据的做用以及在后续可以快速的定位到对应的数据对象。

2.四、Helper

开发中会遇到一些很基础的,通用的业务逻辑,例如咱们可能会根据每一个用户的信息生成一个惟一的 account id 。又或者说有一个用户排名的需求,咱们将从用户的相关信息中计算出一个分数,从而根据这个分数进行排名。那么这时候咱们可能会将这些逻辑写在 User 数据对象或是其余相应对应的数据对象下。 而我我的来讲,不但愿数据对象包含业务逻辑,因此我会将这些通用的业务逻辑都抽出来,放到 Manager 中进行统一管理。如会将生成 account id 的逻辑放在 AccountIdGenerator 中,将计算排名分数的逻辑放在 RankCalculator 中。 我将这些类都归为 Helper ,用于提供底层的业务计算逻辑。而为何不放在通用工具层中呢?由于这些 Helper 其实都是依赖于特定的领域,即特定的业务。而通用工具类则是业务无关的,任何系统,只要有须要均可以引用。

2.五、DAO

DAO 就不用过多解释了,数据访问对象,用于对数据库的访问。可是我我的不会将 DAO 只局限于数据库,对于不一样的数据源的交互,如 HBase ,ElasticSearch ,本地缓存甚至 Redis 我都会定义相对应的 DAO 进行访问。 这样的定义,实际上是想将数据 CURD 的逻辑和业务逻辑进行分离,将获 CRUD 封装在 DAO 中,业务逻辑即放在业务层中。

以前接手了一个项目,项目将 Redis 视为中间件,将相关的逻辑都封装在 xxxRedisService 中,包括 CRUD 和一些业务逻辑。随着项目的发展,一些其实能够归类到一块儿的业务,变得有些放在了 RedisService 中,一些放在了业务层的 Service 中,可想而知十分混乱,还致使了一些 BUG 的出现。

2.六、Service 和 Manager

Service 的做用不用多说明,为具体业务逻辑的封装层。

具体要说明的是 Manager ,《阿里巴巴 Java 开发手册》中定义以下:

  1. 对第三方平台封装的层,预处理返回结果及转化异常信息
  2. 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理
  3. 与 DAO 层交互,对多个 DAO 的组合复用

能够将 Manager 理解为对通用逻辑的封装,避免 Service 与 Service 进行相互调用,以及对通用逻辑的管理。

在开发中,咱们常常会遇到 AService 中的某个业务能够提供给 BService 调用,从而让 BService 调用 AService 的方法,认为是 Service 之间具备共同的业务。其实 Service 之间没有共同的业务,而是具有通用的逻辑,这时应该将其抽离出来放在 Manager 中。不管何种工程架构都好,我都不赞同 Service 与 Service 之间的相互调用。

在实际开发中,我会对 Manager 进行更细一点的划分。大体将其分为用于项务类,所封装的是由 Service 下沉的通用业务。 而另外一种则是一些偏向于工具、计算的类,例如某个业务使用了策略模式,所编写的策略类则属于这一类。 我会将业务类的用 @Service 注释,而偏工具类的则用 @Component 注释。这样作的缘由仍是避免业务之间的相互调用,相互耦合。

这里可能会想,为何不将 Helper 的逻辑也放在 Manager 层中?缘由在于 Helper 的逻辑比 Manager 更加基础,有可能在 DAO 中都会调用 Helper 的相关逻辑,若是放在 Manager 中,就会出现底层依赖上层的问题。

2.七、接口层

最后的一层,则是暴露给外部调用的层。能够是 Spring 体系中的 Controller ,也能够是 gRPC 。 这一层将组织、调用咱们所定义的 Service ,进行业务处理。

三、分层模型的优势以及缺点

不管什么工程架构,都会有其优势以及缺点,在选择工程架构时,其实就是对优势缺点的衡量。

3.一、优势

其实不管什么架构,特别是对业务工程来讲,最但愿架构带来的是解耦以及内聚。 经过分层,在必定程度上对项目内的各个模块进行了解耦内聚,依赖关系十分明确,再怎么写,只要符合规约,老是上层依赖于下层。并且分层的规约十分简单,在多人协做的状况下大部分状况均可以很好的遵照规约。

3.二、缺点

简单是一个优势,也是一个缺点。分层虽然在必定程度上进行了解耦,可是粒度十分粗,只要不出现下层依赖上层的状况,均可以认为是符合规约的,在这种状况下,很容易致使代码的分散、功能的碎片化,明明是同一类业务功能的代码,却分散在多个类,多个层次之间。在项目不断迭代时变得巨大时,慢慢就会变得混乱,而后就是一轮重构。 归根到底就是太松懈了,致使开发人员很容易就是在项目中随便找个地方写,还很容易致使由大量的复制粘贴所产生重复代码。

在学校开设的软件工程课中,设计一个系统,首先是组织架构的了解,而后从中抽出数据流,而后再在数据流中抽出业务流,进行根据业务流进行开发。而采用分层模型的化,每每在数据流中就能够开始开发,采用分层模型的话,每一个业务其实能够简单的抽象成数据在各层之间的流动。 这能够说是一个优势,简化了业务的理解,实现快速的开发,我在比较紧的排期下也由这么作过,扫一眼业务,构思好数据流的流动后就动手了。但这也是一个很严重的缺点,我见过很多功能性 BUG ,就是因为对业务的不充分理解所致使的,并且因为没有对业务流程充分理解后就开发,后续的扩展和修复,看起来就是不断的修修补补。

上面,我除了《阿里巴巴 Java 开发手册》所写的内容外,还添加了很多细节,其实所想要作的就是尽可能减小这种功能碎片化的问题。

四、与充血领域模型的对比

既然是说工程架构,就不得不提 DDD 这一个概念。

为何我说的是“与充血领域模型的对比”而不是“与 DDD 的对比”呢?是由于 DDD 是比分层模型更加高层的一种概念,它是一个产品服务,整个团队开发的一种指导思想,而不是一种工程代码上的规约。

DDD 能够分为两大方向,一个是战略层面上的,即以前提到的是一种开发的指导思想,定义、划分服务的领域,规定统一语言提升沟通效率等,这也是能够用于使用分层领域模型的项目开发中的。若是要与分层模型对比的话,实际上是 DDD 的战术层面,即充血领域模型。

充血领域模型实际上是回归于面向对象的思想。在目前的分层模型中,哪怕是用 Java 这种面向对象的语言去写,其实整体上仍是一种过程式的编程,在 DDD 中称为事务脚本。

充血领域模型是重领域,轻 Service 的。以以前生成 account id 以及排名的例子,在充血领域模型中,User 类将会有 generateAccountId 方法和 ranking 方法来完成这一逻辑。 彻底的面向对象,就能够充分的发挥面向对象的特性。面向对象的特性在书上为:继承、多态,封装。前二者可以实现归一化,使模块泛化通用,封装即会使模块划分明确,可以很好的实现解耦和内聚。比起分层模型,使用充血领域模型能够很好的解决上面提到的代码分散,碎片化的问题。

充血领域模型的优势是面向对象的优势,可是面向对象的缺点也成为这种模型的缺点。首先,万物皆可抽象在我看来就是伪命题,由于现实世界中总有事务是难以进行抽象的,或者抽象起来不优雅,老是有一种硬是抽象的感受。 在知乎中有一个很好的回答,描述了面向对象的弊端

相信不少人在初接触 DDD 时,都会去搜索充血领域模型实践的例子。其实在学校学习 Java Web 开发时,书本中写道的 MVC 结构其实在必定程度上也是充血领域模型,Model 除了是数据的载体外,还包含业务逻辑,经过 Controller 对 Model 的选择以及调用完成业务。假如用这种结构开发,当项目庞大后,我以为首先遇到的问题应该就是依赖问题,复杂的业务必然牵扯到各方各面,天然也就有复杂的依赖关系产生,甚至会有为了完成业务而产生很“脏”的实现,这是难以免的。

我我的以为充血领域模型目前仍是只适合于我的,很小的团队中使用,例如 2 到 3 我的的团队,由于抽象自己就是一个很是复杂的过程,随着需求迭代,以前的抽象还不必定正确,若是在较为多人的多人协做中,各类奇奇怪怪的写法都会出现,必然也会有随便找个“地”写的状况出现,这种状况比在分层模型中更为致命。

五、总结

仍是那句话,工程架构无分好坏,只有适合与不适合,问题的来与在于业务的复杂,计算机始终在某些方面难以映射到现实世界。因此我我的建议好好的理解好本身目前所用到的工程架构,尽可能作到扬长避短。

欢迎关注微信公众号”程序员小明”,获取更多资源。

相关文章
相关标签/搜索