- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
《整洁架构之道》的最后一章《细节决定成败》又在讨论 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 工程.
以上就是完美的划分.
但是,在实践中, 。
这时有人会说, 照你这个说法,Java 根本就不适合写业务?
不是没有可能。我可以理直气壮的说,如果一个 Service 把持久扔掉,它根本没有资格做 Service。此外, Controller 不过是 Service 的 RPC 入口.
最严重的问题是,它很蹩脚,这么蹩脚的设计不禁让我们思考:什么是理想的划层?
看三个例子:
网络 7 层,包括应用层传输层等,HTTP 是建立在 TCP 上的,TCP 这一层不需要为 HTTP 做任何调整。上一层对下一层是完全无知无感的,当层划分好后,甚至能做到下一层的变动不会影响上一层.
又如操作系统的层,我们日用的进程文件等等,属于操作系统提供的层。这一层上的应用例如视频播放器完全不需要考虑我的进程有没有被调度到CPU,磁盘磁道怎么安排,磁盘改成 NVME 怎么办等等,视频播放器绝不会说我要加一个功能需要操作系统改一改升个级.
像 JVM 这种跨平台虚拟机进一步对操作系统做了抽象,我们甚至可以编写与目标操作系统完全无关的程序.
上述这些分层都是完全解耦的,上一层依赖下一层,但是下一层对上一层无感,不需要考虑上一层的心情.
套用一下让子弹飞体:什么叫分层?这 TM 才叫分层! 。
不怕不识货就怕货比货,现在我们可以发现 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 的查询对象,如果 controller 只做这么一点工作,它应该是通用的,这部分工作应当变成一种协议,也就是 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 是替换数据库的,那么,根据需求,我们可以从原来的 Service 派生一个 ESService,覆盖部分方法(部分替换),也可以将原 Service 抽象为 Service + PGService,然后另外派生一个 ESService(完全替换).
最后此篇关于Java项目愚蠢的分层及解决办法的文章就讲到这里了,如果你想了解更多关于Java项目愚蠢的分层及解决办法的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
@Cacheable在同一类中方法调用无效 上述图片中,同一个类中genLiveBullets()方法调用同类中的queryLiveByRoom()方法,这样即便标识了Cacheable标签,
目录 @Transaction注解导致动态切换更改数据库失效 使用场景 遇到问题 解决 @Transaction
@RequestBody不能class类型匹配 在首次第一次尝试使用@RequestBody注解 开始加载字符串使用post提交(貌似只能post),加Json数据格式传输的时候,
目录 @Autowired注入static接口问题 @Autowired自动注入普通service很方便 但是如果注入static修饰的serv
目录 @RequestBody部分属性丢失 问题描述 JavaBean实现 Controller实现
目录 解决@PathVariable参数接收不完整的问题 今天遇到的问题是: 解决办法: @PathVariable接受的参
这几天在项目里面发现我使用@Transactional注解事务之后,抛了异常居然不回滚。后来终于找到了原因。 如果你也出现了这种情况,可以从下面开始排查。 1、特性 先来了解一下@Trans
概述: ? 1
场景: 在处理定时任务时,由于这几个方法都是静态方法,在aop的切面中使用@Around注解,进行监控方法调用是否有异常。 发现aop没有生效。 代码如下:
最近做项目的时候 用户提出要上传大图片 一张图片有可能十几兆 本来用的第三方的上传控件 有限制图片上传大小的设置 以前设置的是2M&nb
我已经实现了这个SCIM reference code在我们的应用程序中。 我实现的代码确实通过了此postman link中存在的所有用户测试集合。 。我的 SCIM Api 也被 Azure 接受
我一直对“然后”不被等待的行为感到困扰,我明白其原因。然而,我仍然需要绕过它。这是我的用例。 doWork(family) { return doWork1(family)
我正在尝试查找 channel 中的消息是否仍然存在,但是,我不确定如何解决 promise ,查看其他答案和文档,我可以看到它可能是通过函数实现的,但我是不完全确定如何去做。我希望能在这方面获得一些
我有以下情况: 同一工作区中的 2 个 Eclipse 项目:Apa 和 Bepa(为简洁起见,使用化名)。 Apa 项目引用(包括)Bepa 项目。 我在 Bepa 有一个类 X,具有公共(publ
这个问题已经有答案了: Why am I getting a NoClassDefFoundError in Java? (31 个回答) 已关闭 6 年前。 我正在努力学习 spring。所以我输入
我正在写一个小游戏,屏幕上有许多圆圈在移动。 我在两个线程中管理圈子,如下所示: public void run() { int stepCount = 0; int dx;
我在使用 Sympy 求解方程时遇到问题。当我运行代码时,例如: 打印(校正(10)) 我希望它打印一个数字 f。相反,它给我错误:执行中止。 def correction(r): from
好吧,我制作的每个页面都有这个问题。我不确定我做错了什么,但我所有的页面都不适用于所有分辨率。可能是因为我使用的是宽屏?大声笑我不确定,但在小于宽屏分辨率的情况下,它永远不会看起来正确。它的某些部分你
我正在尝试像这样进行一个非常简单的文化 srting 检查 if(culture.ToUpper() == "ES-ES" || "IT-IT") { //do something } else
Closed. This question is off-topic. It is not currently accepting answers. Learn more。 想改进这个问题吗?Upda
我是一名优秀的程序员,十分优秀!