实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?

the night sky with stars above a mountain

这篇主要讲,基于贫血模型的MVC架构和DDD充血模型的开发架构区别。

首先提出几个问题:

  1. 什么是贫血模型?什么是充血模型?
  2. 为什么说基于贫血模型的传统开发模式违反 OOP?
  3. 基于贫血模型的传统开发模式既然违反 OOP,那又为什么如此流行?
  4. 什么情况下我们应该考虑使用基于充血模型的 DDD 开发模式?

什么是基于贫血模型的传统开发模式

MVC三层架构中的M表示Model,V表示view,C表示Controller;将整个项目分成三层:展示层、逻辑层和数据层;

相应的,后端会这么分:

后端项目分为 Repository 层、Service 层、Controller 层。其中,Repository 层负责数据访问,Service 层负责业务逻辑,Controller 层负责暴露接口。

什么是贫血模型?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

////////// Controller+VO(View Object) //////////
public class UserController {
private UserService userService; //通过构造函数或者IOC框架注入

public UserVo getUserById(Long userId) {
UserBo userBo = userService.getUserById(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;
}

UserEntity 和 UserRepository 组成了数据访问层,UserBo 和 UserService 组成了业务逻辑层,UserVo 和 UserController 在这里属于接口层。

像 UserBo 这样,只包含数据,不包含业务逻辑的类,就叫作贫血模型(Anemic Domain Model)。同理,UserEntity、UserVo 都是基于贫血模型设计的。这种贫血模型将数据与操作分离,破坏了面向对象的封装特性,是一种典型的面向过程的编程风格。

什么是基于充血模型的 DDD 开发模式?

在贫血模型中,数据和业务逻辑被分割到不同的类中。充血模型(Rich Domain Model)正好相反,数据和对应的业务逻辑被封装到同一个类中。因此,这种充血模型满足面向对象的封装特性,是典型的面向对象编程风格。

什么是领域驱动设计?

领域驱动设计,即 DDD,主要是用来指导如何解耦业务系统,划分业务模块,定义业务领域模型及其交互。

我们知道,除了监控、调用链追踪、API 网关等服务治理系统的开发之外,微服务还有另外一个更加重要的工作,那就是针对公司的业务,合理地做微服务拆分。而领域驱动设计恰好就是用来指导划分服务的。所以,微服务加速了领域驱动设计的盛行。

做好领域驱动设计的关键是,看你对自己所做业务的熟悉程度,而并不是对领域驱动设计这个概念本身的掌握程度。

基于贫血模型的传统的开发模式,重 Service 轻 BO;基于充血模型的 DDD 开发模式,轻 Service 重 Domain。

为什么基于贫血模型的传统开发模式如此受欢迎?

第一、我们开发的系统业务可能都比较简单,简单到就是基于 SQL 的 CRUD 操作,所以,我们根本不需要动脑子精心设计充血模型,贫血模型就足以应付这种简单业务的开发工作。除此之外,因为业务比较简单,即便我们使用充血模型,那模型本身包含的业务逻辑也并不会很多,设计出来的领域模型也会比较单薄,跟贫血模型差不多,没有太大意义。

第二、充血模型的设计要比贫血模型更加有难度。从一开始就要设计好针对数据要暴露哪些操作,定义哪些业务逻辑。

第三、思维已固化,转型有成本。

什么项目应该考虑使用基于充血模型的DDD开发模式?

基于贫血模型的传统的开发模式,比较适合业务比较简单的系统开发。相对应的,基于充血模型的 DDD 开发模式,更适合业务复杂的系统开发。

你可能会有一些疑问,这两种开发模式,落实到代码层面,区别不就是一个将业务逻辑放到 Service 类中,一个将业务逻辑放到 Domain 领域模型中吗?为什么基于贫血模型的传统开发模式,就不能应对复杂业务系统的开发?而基于充血模型的 DDD 开发模式就可以呢?

两种不同的开发模式会导致不同的开发流程。基于充血模型的 DDD 开发模式的开发流程,在应对复杂业务系统的开发的时候更加有优势。

在这种开发模式下,我们需要事先理清楚所有的业务,定义领域模型所包含的属性和方法。领域模型相当于可复用的业务中间层。新功能需求的开发,都基于之前定义好的这些领域模型来完成。

充血模型的 DDD 开发模式,正好需要我们前期做大量的业务调研、领域模型设计,所以它更加适合这种复杂系统的开发。