一,简介
我们做一个大项目会把项目分解成很多不不同的模块(Module),通常分为 Controller,Service,Model,Dao 和 Utils。
有没有其实都可以,叫不叫这些名字也无妨,你要把Service的内容全写在Controller里也没问题,你要把Utils的工具函数分散在所有需要用的文件也Ok。但是久而久之,一堆人做一件事做的多了,就会形成这些约定俗成的部分,就好比形成了人行道,车行道,形成了红绿灯,当然这些终归还没有加进标准里去,所以你遵不遵守,都靠你自己。
所以,项目中是否包含这些模块或者单词,和你的项目结构是否完善一毛钱关系没有。但是当你的项目结构相对完善的时候,你会发现有这样一些角色的存在。
这一切都是为了实现高内聚度低耦合度,便于维护,也就是在软件生命周期中达到好的可用性以及总体上的低成本。但不应把这些当作教条,甚至不知道为了什么这样做。
二,项目分层
Controller 控制器
Controller 层是对客户端传来的数据做处理,传给 Service 层,然后把 Service 层产生的数据,返回给客户端。
比如,用户传了一个 pageSize,但是有可能传 null,这个时候你可以再进行一次转换成默认的10;然后调用Service 的方法,返回结果给到前端。
Service 服务
Service 比 Util 的概念大很多,它的重点是在于提供一个服务。这个服务可能包括一系列的数据处理,也有可能会调用多个Util,或者是调用别的服务。
技术上讲,一个 Service 可以调用其他 Service ,有人建议不要这样做,除非这个被调用的 Service 是公用的工具类。比如,有人给 Service 分了内外两层, 所有业务逻辑都放在 Service 里, 可复用的逻辑放在内层, 不复用的放在外层。这种情况会造成发布版本时,Service 变的不稳定。如果只允许 Controller 调用 Service,不允许 Service 互相调用,就可以避免这样的事。但是,这样遇到前后台统一业务逻辑的时候,代码要写两份。
综上所述,基本原则是 Service 不允许互相调用,保持 Controller 到 Service 到数据库的单线调用,然后在需要的时候打破这个规则,用 Service 调用 Service。
Service一般而言,都是包含有业务逻辑的,很少能做单元测试。
Model 模型
通常会有很多Model,就是数据库中的很多表定义。
Model 的作用就是数据的抽象,描述了一个数据的定义,Model的实例就是一组组的数据。整个系统都可以看成是数据的流动,既然要流动,就一定是有流动的载体。一条业务流就是对应一条或者多条数据流,
对于初学者而言,第一个要学会,就是建模,把业务逻辑映射成数据模型。
DAO 数据持久层
Dao一般而言,都是用来和底层数据库通信,负责对数据库的增删改查。
更直观一些,一个DAO对象 Article,包含 createArticle(), updateArticle(), deleteArticle()等方法。奇奇怪怪的SQL语句可以写在各个方法中。
这样可以解决,对象范例和关系范例这两大领域之间存在“阻抗不匹配”。这样分层也让controller 和 service 层更为干净。
本质上来说,DAO并不一定要和数据库有联系,DAO只是 Data Access Object(数据存取对象)的缩写,所以只要是把数据持久化包装成一个对象的访问(读写),这种对象都可以被称之为DAO,譬如,用JSON格式存到硬盘上。
Util 工具
Util就是工具,开发的过程中会产生许多奇奇怪怪的私有方法,有时候还都是重复的。这时候我们就可以把这些私有方法提取出来作为公用的方法。
像是URL编码或者解码,或是自创的加密签名算法等等。Util 代表他和业务逻辑是不相关的。通常命名也是ArticleUtil,CommentUtil之类的。
Util一般都有明确的输入和输出结果,都可以进行单元测试。
三,案例
案例一
阿里巴巴Java开发手册中的分层,甚至加入了DDD(领域模型)。
开放接口层:可直接封装 Service 接口暴露成 RPC 接口; 通过 Web 封装成 http 接口; 网关控制层等。
终端显示层:各个端的模板渲染并执行显示层。 当前主要是 velocity 渲染, JS 渲染, JSP 渲染,移动端展示层等。
Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
Service 层:相对具体的业务逻辑服务层。
Manager 层:通用业务处理层,它有如下特征:
1)对第三方平台封装的层,预处理返回结果及转化异常信息;
2)对 Service 层通用能力的下沉,如缓存方案、 中间件通用处理;
3)与 DAO 层交互,对 DAO 的业务通用能力的封装。
DAO 层:数据访问层,与底层 MySQL、 Oracle、 Hbase 进行数据交互。
外部接口或第三方平台:包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口。
案例二
SpaceX-API是 r/SpaceX 社区开源的用于火箭、核心舱、太空舱、发射台和发射数据的开源 REST API。
SpaceX-API 技术细节:
部署在美国中部的 Linode 服务器上
在 Koa 框架中使用 Node.js
使用 Redis、Nginx 和 Cloudflare 进行内容缓存
使用 Jest 和 Supertest 进行测试
使用 Circle CI 进行持续集成 / 部署
所有数据都存储在 MongoDB Atlas 3 节点副本集集群中
夜间 mongodump 数据库备份
SpaceX-API 无视 MVC
下面是 SpaceX-API 的三个版本,v3版本的时候,还有点mvc的影子,路由和控制器还是分开的。其中有个 v4-experimental 版本,直接使用 go 语言写的。v4版本的时候,routes和controllers不见了,弄出来一个services,下面按版本和模块分文件夹,model和routes放在一个文件夹中。最新的版本应该是v5版本,services改名routes,按模块和版本分文件夹,models 挪出去,单独放在一个文件夹。
感觉 SpaceX-API 完全是想怎么写就怎么写,完全无视 MVC,中间还换了一次 go 语言。
案例三
这个星球上40%以上的网站使用的是 WordPress,完全没有用这种分层的概念。
实践
在实践中,很多小项目的业务逻辑都在Controller中进行处理,服务层只负责一些增删改查的方法。甚至增删改查也在控制器。这样会造成完全无法单元测试和后期维护性差。特别简单的项目和没有“以后”的项目可以这样做。
还有下面这样的观点:
“如果一个项目的生命周期比较长,要不要分层,项目一开始就要确定,这是架构师的职责。”
“当项目达到一定规模,再重构,不要想着一个万全的设计。”
“因为是面向数据库编程,但凡没有或者不怎么依赖数据库的程序,三层模型全面崩塌。”
“controller 主要是控制数据的接收和调用哪个 service,如果没有 service 可不可以,其实也可以,那你的业务就要直接写在 controller 中,还有 controller 中的代码块为了能复用起来,干脆就再封装一次 service 算了。
至于 DAO ,也可以是 Model 或者是其他,主要是控制数据库的查询和结果返回,也是同理,也可以和 service 直接合并,甚至是直接写在 controller 里面,常用的 orm 是要创建映射的对象的,再算上代码复用性问题,是不是直接放在 service 或者是 controller 就很乱了,所以又单独分出一层 dao 来。”
“一般 service 只调用自己的 dao,如果想调用其他 dao,则通过引入该层的 service”
参考:
https://www.zhihu.com/question/58410621
https://segmentfault.com/q/1010000018560036/a-1020000018615733
https://blog.csdn.net/qq_22771739/article/details/82344336
https://www.zhihu.com/question/58410621/answer/623496434
《阿里巴巴java开发手册》
https://github.com/r-spacex/SpaceX-API
https://www.zhihu.com/question/484268189/answer/2112084953
修改时间 2023-11-02