2天时间,聊今年最热的 Agent、上下文工程、AI 产品创新等话题。2025 年最后一场~ 了解详情
写点什么

Spring Web 应用的最大瑕疵

  • 2013-11-08
  • 本文字数:2065 字

    阅读完需:约 7 分钟

众所周知, 现在的 Spring 框架已经成为构建企业级 Java 应用事实上的标准了,众多的企业项目都构建在 Spring 项目及其子项目之上,特别是 Java Web 项目,很多都使用了 Spring 并且遵循着 Web、Service、Dao 这样的分层原则,下层向上层提供服务;不过 Petri Kainulainen 在其博客中却指出了众多 Spring Web 应用的最大瑕疵,请继续阅读看看文中所提到的问题是否也出现在你的项目当中。

使用 Spring 框架构建应用的开发者很乐于谈论依赖注入的好处。但遗憾的是,他们很多人并没有在其应用中很好地利用其优势,如单一职责原则关注分离原则。如果仔细看看基于 Spring 的 Web 应用,你会发现很多都是使用如下这些常见且错误的设计原则来实现的:

  • 领域模型对象只是用来存储应用的数据。换句话说,领域模型使用了贫血模型这种反模式。
  • 业务逻辑位于服务层中,管理域对象的数据。
  • 在服务层中,应用的每个实体对应一个服务类。

可问题是:如果这种做法很普遍,那为什么说是不对的呢?下面来阐述一下。

旧习难改

Spring Web 应用之所以看起来是这个样子原因在于这是人们长久以来的做法,旧习难改,特别是在高级开发者或是软件架构师强制开发人员这样做的时候。问题在于这些人非常擅于捍卫自己的观点。他们喜欢的一个论调就是应用应该遵循关注分离原则,因为它被划分成了几个层次,每个层次都有自己具体的职责。

一个典型的 Spring Web 应用会有如下几个层次:

  • Web 层:负责处理用户的输入并向用户返回正确的响应。Web 层只会与服务层通信。
  • 服务层:作为事务边界。它还负责授权并包含了应用的业务逻辑。服务层管理着领域对象模型并且与其他服务及存储层通信。
  • 存储 / 数据访问层:负责与所用的数据存储进行通信。

关注分离原则的定义是这样的:关注分离(Soc)是一种将计算机程序划分到不同部分的一种设计原则,这样每一部分都会有单独的关注点。虽然一个典型的 Spring Web 应用也在一定程度上遵循了这个原则,不过实际情况却是应用拥有一个整体的服务层,它包含了太多的职责了。更具体一些,服务层主要有两个问题:

首先,应用的业务逻辑来自于服务层。

这是个问题,因为业务逻辑散落在服务层。如果需要查看某个业务规则是如何实现的,我们需要先找到它才行,这可不是那么轻松的事情。此外,如果有多个服务类都需要相同的业务规则,那么开发人员很可能会将这个业务规则从一个服务复制到另一个服务中,这会导致维护的梦魇。

其次,每个领域模型类在服务层中都有一个服务类。

这违背了单一职责原则:单一职责原则表明每个类都应该只有一个职责,这个职责应该完全被这个类所封装。它的所有服务都应该与这个职责保持一致。

服务类存在大量的依赖和大量的循环依赖。一个典型的 Spring Web 应用的服务层没有包含只拥有一个职责的松耦合的服务,它更像是一个紧耦合的大量服务的集合。这使得它很难理解、维护与重用。看起来有点苛刻,不过服务层经常是 Spring Web 应用最容易出现问题的一环。幸好对我们来说还存在着希望。

推翻

目前的状况并不好,不过也不是完全没有希望的。下面我们来看看如何打破旧有的习惯。

首先,我们需要将应用的业务逻辑从服务层移动到领域模型类中。

为何要这么做呢,看看下面这个例子:

假设我是个服务类,你是个领域模型对象。如果我告诉你从房顶跳下来,那么你是否会拒绝呢?

将服务层的业务逻辑移动到领域模型类中有如下 3 个好处:

  • 根据合理的方式划分代码的职责。服务层会负责应用的逻辑,而领域模型类则负责业务逻辑。
  • 应用的业务逻辑只会位于一处。如果需要验证特定的业务规则是如何实现的,我们总是知道该去哪里寻找。
  • 服务层的源代码将会变得更加整洁,再不会包含任何复制粘贴的代码了。

其次,我们需要将特定于实体的服务划分为更小的服务,每个服务只有一个目标。

比如说,如果应用有一个服务类,它为与用户帐户相关的人与操作提供了 CRUD 操作,那么我们就应该将其划分到两个单独的服务类中:

  • 第 1 个服务提供人的 CRUD 操作。
  • 第 2 个服务提供与用户帐户相关的操作。

这么做有如下 3 个好处:

  • 每个服务类都有一套合理的职责。
  • 每个服务类的依赖会更少,这意味着他们不再是紧耦合的庞然大物了。他们是更加小巧且松耦合的组件。
  • 服务类更易于理解、维护与重用。

这两个简单的步骤可以帮助我们清理应用的架构,提升开发者的生产力和幸福度。现在,我们想知道如果所有这些都是必要的,那么该何时解决这些问题呢?

有时生命是黑白的

我经常听到有人说我们不应该过多的关注于“架构”,因为我们的应用很小并且很简单。虽然这个论调有一定的正确性,不过我们必须要记住一开始很小的项目最后会变得很大。如果不考虑这种情况,那么一旦发生状况,我们就会陷入到巨大的麻烦当中。在未知的水域中航行可不是个好做法,但我们必须要知道,泰坦尼克号在撞到冰山沉没时是在熟悉的航线中航行的。这种事情也会发生在我们的应用中。当事情变得无法控制时,我们必须要有勇气说不。

如果你打算改变,那么我推荐你阅读一下Olivier Gierke 所写的“ Whoops! Where did my architecture ”(或是观看他在 SpringOne2GX 上关于这个项目的演讲)。但请注意,习惯的力量还是很强大的。

2013-11-08 11:329829
用户头像

发布了 88 篇内容, 共 272.5 次阅读, 收获喜欢 9 次。

关注

评论

发布
暂无评论
发现更多内容

代码手术刀-自定义你的代码重构工具

京东科技开发者

对话苏光牛:国内数据库市场已进入关键转折点,2024年或是分水岭

华为云开发者联盟

数据库 后端 华为云 华为云GaussDB 华为云开发者联盟

解密JavaChassis3:易扩展的多种注册中心支持

华为云开发者联盟

Java 开发 华为云 华为云开发者联盟

科蓝软件启动鲲鹏原生应用开发合作

彭飞

Wireshark使用技巧

小齐写代码

亿级流量高并发春晚互动前端技术揭秘

京东科技开发者

文心一言 VS 讯飞星火 VS chatgpt (195)-- 算法导论14.3 3题

福大大架构师每日一题

福大大架构师每日一题

一起畅玩!幻兽帕鲁服务器华为云搭建教程(Windows 平台)

YG科技

听GPT 讲Rust Tokio源代码(3)

fliter

阿里集团基于Fluid+JindoCache加速大模型训练的实践

阿里技术

阿里 大模型 Fluid JindoCache

一文详解静态图和动态图中的自动求导机制

百度Geek说

Python 深度学习 自动求导 autodiff

快速熟悉 MatrixOne 内核前端

MatrixOrigin

分布式数据库 云原生数据库 MatrixOrigin MatrixOne HTAP数据库

快速上手极狐GitLab设计管理功能

极狐GitLab

代码手术刀-自定义你的代码重构工具

京东零售技术

Java 源码分析 后端 代码重构

打破信息孤岛,聚道云软件连接器助力企业实现高效管理

聚道云软件连接器

案例分享

流批一体化数据加工处理之记录过滤

大河

「AI Party」喊你来!百度Create大会4月16-17日在深圳举办

飞桨PaddlePaddle

百度 AI 开发者大会

听GPT 讲Rust Tokio源代码(2)

fliter

iOS平台如何实现毫秒级延迟的RTMP|RTSP播放器

音视频牛哥

IOS RTSP播放器 IOS RTMP播放器 RTSP播放器 RTMP播放器 RTMP player

【亿级数据专题】「高并发架构」盘点本年度探索对外服务的百万请求量的API网关设计实现

码界西柚

性能优化 架构设计 高性能网关 亿级数据 2024年第三十三篇文章

深度神经网络中的BNN和DNN:基于存内计算的原理、实现与能量效率

申公豹

存内计算

华为云软件开发生产线CodeArts开发者实践8件套——开发者的进阶宝典!

软件开发 华为云

国内唯一!通义灵码入选全球智能编码助手使用率 TOP 榜单

阿里巴巴云原生

阿里云 云原生 通义灵码

2024年1月文章一览

codists

基于 SRAM 的存内计算助力实现节能 AI

申公豹

存内计算

低代码,流行的软件开发方式

互联网工科生

软件开发 低代码 JNPF

2023年12月文章一览

codists

codists

Spring Web应用的最大瑕疵_架构_张龙_InfoQ精选文章