设计模式之美学习(九):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
我们都知道,很多业务系统都是基于 MVC
三层架构来开发的。实际上,更确切点讲,这是一种基于贫血模型的 MVC
三层架构开发模式。
虽然这种开发模式已经成为标准的 Web
项目的开发模式,但它却违反了面向对象编程风格,是一种彻彻底底的面向过程的编程风格,因此而被有些人称为反模式(anti-pattern
)。特别是领域驱动设计(Domain Driven Design
,简称 DDD
)盛行之后,这种基于贫血模型的传统的开发模式就更加被人诟病。而基于充血模型的 DDD
开发模式越来越被人提倡。
基于上面的描述,我们先搞清楚下面几个问题:
- 什么是贫血模型?什么是充血模型?
- 为什么说基于贫血模型的传统开发模式违反
OOP
? - 基于贫血模型的传统开发模式既然违反
OOP
,那又为什么如此流行? - 什么情况下我们应该考虑使用基于充血模型的
DDD
开发模式?
什么是基于贫血模型的传统开发模式?
对于大部分的后端开发工程师来说,MVC
三层架构都不会陌生。
MVC
三层架构中的 M
表示 Model
,V
表示 View
,C
表示 Controller
。它将整个项目分为三层:展示层、逻辑层、数据层。MVC
三层开发架构是一个比较笼统的分层方式,落实到具体的开发层面,很多项目也并不会 100%
遵从 MVC 固定的分层方式,而是会根据具体的项目需求,做适当的调整。
比如,现在很多 Web
或者 App
项目都是前后端分离的,后端负责暴露接口给前端调用。这种情况下,我们一般就将后端项目分为 Repository
层、Service
层、Controller
层。其中,Repository
层负责数据访问,Service
层负责业务逻辑,Controller
层负责暴露接口。当然,这只是其中一种分层和命名方式。不同的项目、不同的团队,可能会对此有所调整。不过,万变不离其宗,只要是依赖数据库开发的 Web
项目,基本的分层思路都大差不差。
再来看一下,什么是贫血模型?
目前几乎所有的业务后端系统,都是基于贫血模型的。举一个简单的例子来解释一下。
////////// Controller+VO(View Object) //////////
public class UserController {
private UserService userService; //通过构造函数或者IOC框架注入
public UserVo getUserById(Long userId) {
UserBo userBo = userService.getUser(userId);
UserVo userVo = [...convert userBo to userVo...];
return userVo;
}
}
public class UserVo {//省略其他属性、get/set/construct方法
private Long id;
private String name;
private String cellphone;
}
////////// Service+BO(Business Object) //////////
public class UserService {
private UserRepository userRepository; //通过构造函数或者IOC框架注入
public UserBo getUserById(Long userId) {
UserEntity userEntity = userRepository.getUserById(userId);
UserBo userBo = [...convert userEntity to userBo...];
return userBo;
}
}
public class UserBo {//省略其他属性、get/set/construct方法
private Long id;
private String name;
private String cellphone;
}
////////// Repository+Entity //////////
public class UserRepository {
public UserEntity getUserById(Long userId) { //... }
}
public class UserEntity {//省略其他属性、get/set/construct方法
private Long id;
private String name;
private String cellphone;
}
平时开发 Web
后端项目的时候,基本上都是这么组织代码的。其中,UserEntity
和 UserRepository
组成了数据访问层,UserBo
和 UserService
组成了业务逻辑层,UserVo
和 UserController
在这里属于接口层。
从代码中可以发现,UserBo
是一个纯粹的数据结构,只包含数据,不包含任何业务逻辑。业务逻辑集中在 UserService
中。我们通过 UserService
来操作 UserBo
。换句话说,Service
层的数据和业务逻辑,被分割为 BO
和 Service
两个类中。像 UserBo
这样,只包含数据,不包含业务逻辑的类,就叫作贫血模型(Anemic Domain Model
)。同理,UserEntity
、UserVo
都是基于贫血模型设计的。这种贫血模型将数据与操作分离,破坏了面向对象的封装特性,是一种典型的面向过程的编程风格。
什么是基于充血模型的 DDD 开发模式?
首先,我们先来看一下,什么是充血模型?
在贫血模型中,数据和业务逻辑被分割到不同的类中。充血模型(Rich Domain Model
)正好相反,数据和对应的业务逻辑被封装到同一个类中。因此,这种充血模型满足面向对象的封装特性,是典型的面向对象编程风格。
接下来,再来看一下,什么是领域驱动设计?
领域驱动设计,即 DDD
,主要是用来指导如何解耦业务系统,划分业务模块,定义业务领域模型及其交互。领域驱动设计这个概念并不新颖,早在 2004
年就被提出了,到现在已经有十几年的历史了。不过,它被大众熟知,还是基于另一个概念的兴起,那就是微服务。
除了监控、调用链追踪、API
网关等服务治理系统的开发之外,微服务还有另外一个更加重要的工作,那就是针对公司的业务,合理地做微服务拆分。而领域驱动设计恰好就是用来指导划分服务的。所以,微服务加速了领域驱动设计的盛行。
领域驱动设计有点儿类似敏捷开发、SOA
、PAAS
等概念,听起来很高大上,但实际上只值“五分钱”。即便你没有听说过领域驱动设计,对这个概念一无所知,只要你是在开发业务系统,也或多或少都在使用它。做好领域驱动设计的关键是,看你对自己所做业务的熟悉程度,而并不是对领域驱动设计这个概念本身的掌握程度。即便你对领域驱动搞得再清楚,但是对业务不熟悉,也并不一定能做出合理的领域设计。所以,不要把领域驱动设计当银弹,不要花太多的时间去过度地研究它。
实际上,基于充血模型的 DDD
开发模式实现的代码,也是按照 MVC
三层架构分层的。Controller
层还是负责暴露接口,Repository
层还是负责数据存取,Service
层负责核心业务逻辑。它跟基于贫血模型的传统开发模式的区别主要在 Service
层。
在基于贫血模型的传统开发模式中,Service
层包含 Service
类和 BO
类两部分,BO
是贫血模型,只包含数据,不包含具体的业务逻辑。业务逻辑集中在 Service
类中。在基于充血模型的 DDD
开发模式中,Service
层包含 Service
类和 Domain
类两部分。Domain
就相当于贫血模型中的 BO
。不过,Domain
与 BO
的区别在于它是基于充血模型开发的,既包含数据,也包含业务逻辑。而 Service
类变得非常单薄。总结一下的话就是,基于贫血模型的传统的开发模式,重 Service
轻 BO
;基于充血模型的 DDD
开发模式,轻 Service
重 Domain
。
为什么基于贫血模型的传统开发模式如此受欢迎?
基于贫血模型的传统开发模式,将数据与业务逻辑分离,违反了 OOP
的封装特性,实际上是一种面向过程的编程风格。但是,现在几乎所有的 Web
项目,都是基于这种贫血模型的开发模式,甚至连 Java Spring
框架的官方 demo
,都是按照这种开发模式来编写的。
面向过程编程风格有种种弊端,比如,数据和操作分离之后,数据本身的操作就不受限制了。任何代码都可以随意修改数据。既然基于贫血模型的这种传统开发模式是面向过程编程风格的,那它又为什么会被广大程序员所接受呢?
第一点原因是,大部分情况下,我们开发的系统业务可能都比较简单,简单到就是基于 SQL
的 CRUD
操作,所以,我们根本不需要动脑子精心设计充血模型,贫血模型就足以应付这种简单业务的开发工作。除此之外,因为业务比较简单,即便我们使用充血模型,那模型本身包含的业务逻辑也并不会很多,设计出来的领域模型也会比较单薄,跟贫血模型差不多,没有太大意义。
第二点原因是,充血模型的设计要比贫血模型更加有难度。因为充血模型是一种面向对象的编程风格。我们从一开始就要设计好针对数据要暴露哪些操作,定义哪些业务逻辑。而不是像贫血模型那样,我们只需要定义数据,之后有什么功能开发需求,我们就在 Service
层定义什么操作,不需要事先做太多设计。
第三点原因是,思维已固化,转型有成本。基于贫血模型的传统开发模式经历了这么多年,已经深得人心、习以为常。你随便问一个旁边的大龄同事,基本上他过往参与的所有 Web
项目应该都是基于这个开发模式的,而且也没有出过啥大问题。如果转向用充血模型、领域驱动设计,那势必有一定的学习成本、转型成本。很多人在没有遇到开发痛点的情况下,是不愿意做这件事情的。
什么项目应该考虑使用基于充血模型的 DDD 开发模式?
基于贫血模型的传统的开发模式,比较适合业务比较简单的系统开发。相对应的,基于充血模型的 DDD
开发模式,更适合业务复杂的系统开发。比如,包含各种利息计算模型、还款模型等复杂业务的金融系统。
这两种开发模式,落实到代码层面,区别不就是一个将业务逻辑放到 Service
类中,一个将业务逻辑放到 Domain
领域模型中吗?为什么基于贫血模型的传统开发模式,就不能应对复杂业务系统的开发?而基于充血模型的 DDD
开发模式就可以呢?
实际上,除了我们能看到的代码层面的区别之外(一个业务逻辑放到 Service
层,一个放到领域模型中),还有一个非常重要的区别,那就是两种不同的开发模式会导致不同的开发流程。基于充血模型的 DDD
开发模式的开发流程,在应对复杂业务系统的开发的时候更加有优势。为什么这么说呢?先来回忆一下,我们平时基于贫血模型的传统的开发模式,都是怎么实现一个功能需求的。
不夸张地讲,我们平时的开发,大部分都是 SQL
驱动(SQL-Driven
)的开发模式。我们接到一个后端接口的开发需求的时候,就去看接口需要的数据对应到数据库中,需要哪张表或者哪几张表,然后思考如何编写 SQL
语句来获取数据。之后就是定义 Entity
、BO
、VO
,然后模板式地往对应的 Repository
、Service
、Controller
类中添加代码。
业务逻辑包裹在一个大的 SQL
语句中,而 Service
层可以做的事情很少。SQL
都是针对特定的业务功能编写的,复用性差。当我要开发另一个业务功能的时候,只能重新写个满足新需求的 SQL
语句,这就可能导致各种长得差不多、区别很小的 SQL
语句满天飞。
所以,在这个过程中,很少有人会应用领域模型、OOP
的概念,也很少有代码复用意识。对于简单业务系统来说,这种开发方式问题不大。但对于复杂业务系统的开发来说,这样的开发方式会让代码越来越混乱,最终导致无法维护。
如果我们在项目中,应用基于充血模型的 DDD
的开发模式,那对应的开发流程就完全不一样了。在这种开发模式下,我们需要事先理清楚所有的业务,定义领域模型所包含的属性和方法。领域模型相当于可复用的业务中间层。新功能需求的开发,都基于之前定义好的这些领域模型来完成。
越复杂的系统,对代码的复用性、易维护性要求就越高,我们就越应该花更多的时间和精力在前期设计上。而基于充血模型的 DDD
开发模式,正好需要我们前期做大量的业务调研、领域模型设计,所以它更加适合这种复杂系统的开发。
重点回顾
平时做 Web
项目的业务开发,大部分都是基于贫血模型的 MVC
三层架构,这里把它称为传统的开发模式。之所以称之为“传统”,是相对于新兴的基于充血模型的 DDD
开发模式来说的。基于贫血模型的传统开发模式,是典型的面向过程的编程风格。相反,基于充血模型的 DDD
开发模式,是典型的面向对象的编程风格。
不过,DDD
也并非银弹。对于业务不复杂的系统开发来说,基于贫血模型的传统开发模式简单够用,基于充血模型的 DDD
开发模式有点大材小用,无法发挥作用。相反,对于业务复杂的系统开发来说,基于充血模型的 DDD
开发模式,因为前期需要在设计上投入更多时间和精力,来提高代码的复用性和可维护性,所以相比基于贫血模型的开发模式,更加有优势。
思考
- 对于举的例子中,
UserEntity
、UserBo
、UserVo
包含的字段都差不多,是否可以合并为一个类呢?
参考:实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
设计模式之美学习(九):业务开发常用的基于贫血模型的MVC架构违背OOP吗?的更多相关文章
- mybatis学习 九 代理开发
1.作用: 实现创建一个接口后把mapper.xml由mybatis生成接口的实现类,通过调用接口对象就可以获取 mapper.xml 中编写的 sql. 2.实现步骤: 2.1 创建一个接口 (1) ...
- Singer 学习九 运行&&开发taps、targets (四 开发target)
singer 的target 需要从stdin 的行数据,同时处理schema.record.state 消息 指南 schema 需要进行关联stream records 数据的校验 一旦Targe ...
- 《设计模式之美》 <01>为什么需要学习掌握设计模式?
1. 应对面试中的设计模式相关问 题学习设计模式和算法一样,最功利.最直接的目的,可能就是应对面试了.不管你是前端工程师.后端工程师,还是全栈工程师,在求职面试中,设计模式问题是被问得频率比较高的一类 ...
- IOS开发常用设计模式
IOS开发常用设计模式 说起设计模式,感觉自己把握不了笔头,所以单拿出iOS开发中的几种常用设计模式谈一下. 单例模式(Singleton) 概念:整个应用或系统只能有该类的一个实例 在iOS开发我们 ...
- 第八模块:算法&设计模式、企业应用 第1章 常用算法&设计模式学习
第八模块:算法&设计模式.企业应用 第1章 常用算法&设计模式学习
- 学习游戏服务器开发必看,C++游戏服务器开发常用工具介绍
C++游戏服务器开发常用工具介绍 在软件开发过程中需要使用的工具类型实属众多,从需求建模到软件测试,从代码编译到工程管理,这些工具都对项目有着不可替代的作用.庄子有云,"吾生也有涯,而知也无 ...
- java开发常用jar包介绍(转载)
jta.jar 标准JTA API必要 commons-collections.jar 集合类 必要 antlr.jar ANother Tool for Language Recognition ...
- 《设计模式之美》 <03>面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
面向对象 现在,主流的编程范式或者是编程风格有三种,它们分别是面向过程.面向对象和函数式编程.面向对象这种编程风格又是这其中最主流的.现在比较流行的编程语言大部分都是面向对象编程语言.大部分项目也都是 ...
- 设计模式笔记之二:Android开发中的MVP架构(转)
写在前面,本博客来源于公众号文章:http://mp.weixin.qq.com/s?__biz=MzA3MDMyMjkzNg==&mid=402435540&idx=1&sn ...
随机推荐
- MongoDB系列---入门安装操作
MongoDB 学习大纲: 1.MongoDB简介与其它数据库对比以及数据类型 2.MongoDB安装 3.MongoDB简单操作 环境要求: Linux 一.MongoDB简介 1 什么是Mongo ...
- 使用FastReport报表工具生成标签打印文档
在我们实际开发报表的时候,我们需要按一定的业务规则组织好报表的模板设计,让报表尽可能的贴近实际的需求,在之前的随笔中<使用FastReport报表工具生成报表PDF文档>介绍了FastRe ...
- 解决html连续字符或数字换行的问题
word-break: break-all; word-wrap:break-word; 强制换行
- VirtualBox NAT Network配置
VirtualBox NAT Network配置(OSX上的) VirtualBox的5种连接方式 NAT :虚拟机之间不能互通 NAT网络 :本文对象 桥接 :一般情况下虚拟机无法设置静态IP,并且 ...
- Java集合框架个人学习笔记
从网上粘贴的结构图 Collection ├List │├LinkedList │├ArrayList │└Vector(了解,已过时) │ └Stack └Set Map ├Hashtable ...
- Linux文件同步工具之rsync
学习背景 1.最近公司的项目在使用jenkins做自动化构建,因为jenkins在构建时是比较耗性能的,便单独使用了一台服务器做构建服务器.但是个人觉得这样成本过高,单独拿一台服务器来构建并且该服务器 ...
- Java 向数组中添加一个元素
方法 一般数组是不能添加元素的,因为他们在初始化时就已定好长度了,不能改变长度. 向数组中添加元素思路: 第一步:把 数组 转化为 集合 list = Arrays.asList(array); 第二 ...
- MONGODB-LINUX 安装步骤
1.MongoDB 提供了 linux 各发行版本 64 位的安装包,你可以在官网下载安装包. 下载地址:https://www.mongodb.com/download-center#communi ...
- 几种部署Goku API Gateway的方式,最快一分钟可使用上网关
本文将介绍几种部署Goku API Gateway的方式,最快一分钟可使用上为网关,详情请看全文. 什么是Goku API Gateway? Goku API Gateway (中文名:悟空 API ...
- how2heap 源码及输出
备个份,慢慢写总结 1 first_fit #include <stdio.h> #include <stdlib.h> #include <string.h> i ...