《整洁架构之道》的最后一章《细节决定成败》又在讨论 Javaer 永恒的问题:分层后 DAO Service Controller 应该按功能分包还是按层分包。 按功能分包的人认为这些文件在业务上是一起的,应该放在同一个包。按层分包的人认为每个层代表了不同的技术,应该按层分包。

可以想象,按层分包的人主张,DAO 层应该打包成一个 jar,对 PG 数据库用户提供 PG 的 jar,对 mysql 的提供 mysql 的 jar,到时换库很灵活。

而按功能分包的人现在有一个更好的理由,未来可以按功能切分成微服务!

《细节决定成败》这一章似乎是国内的译者写的,Bob 应当不太可能关注这种问题。但是问题的确存在。

假如我们把系统拆分来看,在一个古老的 CS 系统,分为服务器和客户端两大部分。客户端是 .net 开发的,服务端是 java 开发的。业务运行在 Java 服务器,客户端同样也表达一些业务知识,当然,主要是呈现性的知识。在包的组织上,客户端基本和服务器会采取一一对应的包设计。

这种系统里一个功能分在两个项目中,上面讨论的问题也就消失了。

从这个类比看,按层拆分似乎是最正确的选择。假如开发语言不同,同一个功能分成两个工程就很合理了。

那么,为什么大家还要纠结拆分呢?如果从一开始就分为三个工程,dao,service,controller 各一个工程,问题不是解决了?

问题出在那儿?

首先最直接的感受是效率的损耗,一个功能分前后端可以说是无奈的选择,现在一个后端还要分三个工程,老板就要问一问有没有价值了。

DAO 层要独立,原因是所谓的“数据库是细节”,不能绑定太深,未来可能要换数据库。

Controller 要独立,因为它是属于 Web 的,不是业务。

随后,更多的分层来了,Service 要分 ServiceInterface 和 ServiceImpl,DAO 也要分 ServiceInterface 和 ServiceImpl。

Service 这么分的理由是 Controller 没有资格访问具体的 ServiceImpl,应当通过依赖反转由容器对 Controller 赋予 ServiceImpl。

而 DAO 拆分的理由是,Service 直接触碰 DAO 太脏,DAO 可能更换,甚至可以说,DAO 接口是由 Service 出具的,只有那点可怜的 DAOImpl 才属于 DAO 工程。

也就是说,如果切分为 3 个工程,Service 这层应当出具一个 Service 接口工程给 Controller 工程,并且出具一个 DAO 接口工程给 DAO 工程。

以上就是完美的划分。

但是,在实践中,

  1. Controller 代码太少了,Controller 是不能改变业务逻辑的,所以 Controller 仅仅是将 Service 的一些函数映射出去而已。专门搞一个 Controller 工程非常傻。
  2. 数据库没有想象的那么烂————Uncle Bob 踩过早期数据库的坑,他固执的认为数据库仅仅是一个存储,带着这种错误的认知他甚至没有把关系运算当作一个范式。但是在实际项目中,DAO并不是简单的存取,往往用到关系运算或如 mongodb 的 map-reduce,换数据库难度是非常大的。试想,Model 最终都在数据库里,Service 要取用数据都要走数据库,而号称实现业务的 Service,使用的却是普通的运行于内存的编程语言,随时可能因为 JVM 宕机导致业务逻辑中断,并且还不支持事务,由此,一个业务件竟然不敢持有业务状态。并且
  3. Service DAO Controller 全部位于同一个 Spring 容器,这使得这种切分显得很荒谬。Controller 是 Web 服务器层的东西,带有高强度的 IO,随时会被冲垮,Service 这么宝贵的东西竟然和 Controller 混在一起,是不是很奇葩。
  4. 这就导致很多业务最终并不是靠 Service.java 或 ServiceImp.java,而是通过规则引擎、消息队列、工作队列等工作。

这时有人会说, 照你这个说法,Java 根本就不适合写业务?不是没有可能。

我可以理直气壮的说,如果一个 Service 把持久扔掉,它根本没有资格做 Service。此外, Controller 不过是 Service 的 RPC 入口。

最严重的问题是,它很蹩脚,这么蹩脚的设计不禁让我们思考:什么是理想的划层?

看三个例子:

网络 7 层,包括应用层传输层等,HTTP 是建立在 TCP 上的,TCP 这一层不需要为 HTTP 做任何调整。上一层对下一层是完全无知无感的,当层划分好后,甚至能做到下一层的变动不会影响上一层。

又如操作系统的层,我们日用的进程文件等等,属于操作系统提供的层。这一层上的应用例如视频播放器完全不需要考虑我的进程有没有被调度到CPU,磁盘磁道怎么安排,如果用的是 NVME 怎么办等等,视频播放器绝不会说我要加一个功能需要操作系统改一改升个级。

像 JVM 这种跨平台虚拟机进一步对操作系统做了抽象,我们甚至可以编写与目标操作系统完全无关的程序。

上述这些分层都是完全解耦的,上一层依赖下一层,但是下一层对上一层无感,不需要考虑上一层的心情。

对照来看我们可以发现 Java 项目这种所谓的分层有多么荒谬。既然已经分层还要考虑横切还是竖切,那就说明根本没有实现正交,Service 层不是建立在 DAO 基础上,DAO 也不建立在 Service 上,它们互相缠绕,把一个现实的功能凌迟的到处都是。

为什么 Java 要把持久化到数据库搞成一个层呢?究其原因就是 Uncle Bob 等人踩过的一些坑使其一朝被蛇咬十年怕井绳,Uncle Bob 豪迈的宣称:ORM 只是细节,只应当放在 Dao 层;对象数据库害了我们,我们把宝押在某某数据库、对象数据库,犯了大错,诸如此类。

客户要求换数据库固然会导致重写很多代码,但是换程序语言呢?假如客户要把 Java 换成 C#,rust,python 呢?那我们是不是该庆幸当初写的全是 SQL,只要把数据库连接方式改一下把SQL抄过来就 OK?据此推理下去,为了随时替换编程语言,我们把系统搞成 nginx + 存储过程?

如果我们不像 Uncle Bob 等人一样,带着畏惧情绪去看待数据库,而是把关系运算也当作一种范式,OOP + 关系运算结合才是业务的实际状态,换数据库和换 Java 一样,是绝对偶发的情形,问题就会简单不少。重复一遍,业务是由数据库和 OOP 共同完成的

同样的,Service 不可能是一个闷葫芦,它要对外提供服务,就必然有对外服务的手段,而这根本不足以形成一个层。为什么呢?Controller 没有资格自行决定暴露哪些 Service 接口,暴露哪些是业务规定的,因此,暴露 Service 这个工作就应当由 Service 自己说明。这可以是 Annotation 也可以是配置文件之类的,但是这并不足以构成一个层。在现实代码中 Controller 主要工作就是做输入校验,把 request 转化为 Service 的查询对象,如果 request 只做这么一点工作,它应该是通用的,这部分工作应当变成一种协议,也就是 Tcp 层 Http 层之后的一个对象路由层,用过 mina 的人都知道我在说什么。

该层输出的对象大体是这样的

class UserQuery extends Query{
@Required
String name
GenderEnum gender
}
abstract class Query{
abstract void validate() throws ValidationError;
HttpRequest request;
}

至于向该层投放的对象,由于是 Restful 接口,输出的查询结果用普通的 POJO 甚至 Map 均可。

有兴趣还可以继续推演。如需要将 Service 保持参数形态,如 UserService.search(String name, Gender gender) 而不是 UserServer.search(UserQuery query))那么这种层应当怎么设计?

把完整的框架发明出来超出了本文讨论范围。

综上,

第一:Service 理应和 DAO 合并,业务本就是透过 Java 和关系运算共同实现的。

第二:Controller 可能没有资格存在,应当处理为一个对象路由层。

其它讨论:

  • 有人会说,那么其它的中间件呢?为什么只有数据库有这种地位?消息队列呢?

看完上述分析我们可以理解,消息队列对于 Service 同样是一个对象路由层。

  • 那么 ES 呢?

ES 之类其它带有运算的存储,必然是业务共同体。假如 ES 是替换数据库的,那么,根据需求,我们可以从原来的 Service 派生一个 ESService,覆盖部分方法(部分替换),也可以将原 Service 抽象为 Service + PGService,然后另外派生一个 ESService(完全替换)。

Java 项目愚蠢的分层及解决方案的更多相关文章

  1. 1,eclipse导入项目jdk版本不一样解决方案 2,java报javax.servlet.jsp cannot be resolved to a type

    一:eclipse导入项目jdk版本不一样解决方案 参考博文: https://www.cnblogs.com/chenmingjun/p/8472885.html 选中项目右键 --> Pro ...

  2. 一次从0到1的java项目实践清单

    虽说工作就是简单的事情重复做,但不是所有简单的事你都能有机会做的. 我们平日工作里,大部分时候都是在做修修补补的工作,而这也是非常重要的.做好修补工作,做好优化工作,足够让你升职加薪! 但是如果有机会 ...

  3. 一份从0到1的java项目实践清单

    虽说工作就是简单的事情重复做,但不是所有简单的事你都能有机会做的. 我们平日工作里,大部分时候都是在做修修补补的工作,而这也是非常重要的.做好修补工作,做好优化工作,足够让你升职加薪! 但是如果有机会 ...

  4. Java 项目JDBC 链接数据库中会出现的错误

    1.出现的地方 package com.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql ...

  5. IntelliJ IDEA14.1中java项目Maven中没有配置JDK时的问题

    在IntelliJ IDEA 14.1中使用在java项目中使用Maven时当没有在Maven中配置JDK编译版本.源码版本时,IDEA将默认的编译版本.源码版本设置为jdk5. 在IDEA中Lang ...

  6. 使用maven来管理您的java项目

    maven是一个项目管理工具,使用maven可以自动管理java项目的整个生命周期,包括编译.构建.测试.发布和报告等.在大型项目开发中,使用maven来管理是必不可少的. 一.安装maven 1.W ...

  7. Java web项目引用java项目,类型找不到

    Java web项目引用java项目,类型找不到 错误信息: java.lang.ClassNotFoundException: org.codehaus.jackson.map.ObjectMapp ...

  8. web项目引用Java项目,连接报错error HTTP Status 500 - Servlet execution threw an exception

    错误信息 项目背景: 一个web项目引用一个java Project,项目中添加了引用,但是打开页面访问,总报500错误.提示:servlet初始化错误. 环境:Eclipse luna JDK: 1 ...

  9. 在命令行中运行eclipse中创建的java项目

    在命令行中运行eclipse中创建的java项目 博客分类: java相关 javaeclipse命令行  由于项目要求,需要对eclipse中的项目进行打包,似的可以在客户机上不装eclipse的情 ...

  10. Java项目经验——程序员成长的关键(转载)

    Java就是用来做项目的!Java的主要应用领域就是企业级的项目开发!要想从事企业级的项目开发,你必须掌握如下要点:1.掌握项目开发的基本步骤2.具备极强的面向对象的分析与设计技巧3.掌握用例驱动.以 ...

随机推荐

  1. 使用 Ant-Design-Vue 制作一个带图片上传功能的表单对话框

    功能需求 使用 Antdv 的 Modal 组件内嵌一个 a-form 表单,具有添加数据和图片的功能. 页面结构设计 <template> <!--Modal--> < ...

  2. 强化学习笔记之【SAC算法】

    强化学习笔记之[SAC算法] 前言: 本文为强化学习笔记第四篇,第一篇讲的是Q-learning和DQN,第二篇DDPG,第三篇TD3 TD3比DDPG少了一个target_actor网络,其它地方有 ...

  3. 云原生周刊:K8s 的 13 个最佳开源监控工具

    开源项目推荐 Silver Surfer 该项目旨在检查 Kubernetes 对象的 API 版本兼容性,并为其提供迁移路径,以便为集群升级做好准备. Actions Runner Controll ...

  4. .NET使用Moq开源模拟库简化单元测试

    前言 今天大姚给大家推荐一个.NET开源.流行.使用简单的.NET模拟库:Moq. Moq类库介绍 Moq是一个.NET开源.流行.使用简单的 .NET 模拟库,充分利用了.NET 的 Linq 表达 ...

  5. NOIP2015 提高组 子串

    NOIP2015 提高组 子串 感觉是最长公共子序列模型的变式. 容易想到记 \(f[i][j][k]\) 表示 \(A\) 走到了第 \(i\) 位,\(B\) 匹配上了 \(1 \sim j\), ...

  6. UE4纯C++实现游戏快捷栏之将快捷栏注册到玩家状态

    我们有了UI有了物品信息,接下来我们便需要给每一个玩家绑定一个快捷栏了,我们分以下几部分来注册我们玩家的快捷栏. 1.Types.h:定于ShortcutContainer类,定义快捷栏的单个容器结构 ...

  7. Python 如何根据给定模型计算权值

    在深度学习中,模型权值(或参数)是通过训练过程学习得到的.但是,有时候我们可能需要手动计算或检查这些权值.这通常是在理解模型工作原理.调试.或者进行模型分析时非常有用的. 下面我将通过一个简单的例子, ...

  8. 推荐一个.NetCore开源的CMS项目,功能强大、扩展性强、支持插件的系统!

    推荐一个基于.Net Core开发的开源CMS项目,该项目功能完善.涉及知识点比较多,不管是作为二次开发.还是学习都是不错的选择. 01 项目简介 Cofoundry是基于.Net开发的.代码优先开发 ...

  9. OpenGL编程指南(原书第9版)

    这本书是<OpenGL编程指南(原书第9版)>,也称为<OpenGL Programming Guide: The Official Guide to Learning OpenGL ...

  10. .NET Core 特性(Attribute)底层原理浅谈

    简介 烂大街的资料不再赘述,简单来说就是给代码看的注释 Attribute的使用场景 Attribute不仅仅局限于C#中,在整个.NET框架中都提供了非常大的拓展点,任何地方都有Attribute的 ...