我们都知道,很多业务系统都是基于 MVC 三层架构来开发的。实际上,更确切点讲,这是一种基于贫血模型的 MVC 三层架构开发模式。虽然这种开发模式已经成为标准的 Web 项目的开发模式,但它却违反了面向对象编程风格,是一种彻彻底底的面向过程的编程风格,因此而被有些人称为反模式(antipattern)。

特别是领域驱动设计(Domain Driven Design,简称 DDD)盛行之后,这种基于贫血模型的传统的开发模式就更加被人诟病。而基于充血模型的 DDD 开发模式越来越被人提倡。

MVC架构(大前提)

MVC 三层开发架构是一个比较笼统的分层方式,落实到具体的开发层面,很多项目也并不会 100% 遵从 MVC 固定的分层方式,而是会根据具的项目需求,做适当的调整。

现在很多 Web 或者 App 项目都是前后端分离的,后端负责暴露接口给前端调用。 这种情况下,我们一般就将后端项目分为 Repository 层、Service 层、Controller 层。其中,Repository 层负责数据访问,Service 层负责业务逻辑,Controller 层负责暴露接口。当然,这只是其中一种分层和命名方式。不同的项目、不同的团队,可能会对此有所调整。不过,万变不离其宗,只要是依赖数据库开发的 Web 项目,基本的分层思路都大差不差。

  • V :View(展示层):HTML
  • C(#) :Controller(逻辑层)
  • Controller : 前端交互,暴露接口 ↑
  • Service(#) :核心业务逻辑
  • Dao :数据库交互,数据存取 ↓
  • M : Model(数据层):MySQL

1.传统开发模式(贫血模型)

1.1 贫血模型

数据与逻辑分离 --> 面向过程编程

1.2 传统开发模式

分层模型(全贫)

  • Controller + VO
  • Service + BO 完全数据逻辑隔离,~O只做数据结构;破坏了面向对象封装特性,是典型面向过程的编程风格
  • Dao + Entity

1.3 开发流程

SQL驱动

我们接到一个后端接口的开发需求的时候,就去看接口需要的数据对应到数据库中,需要哪张表或者哪几张表,然后思考如何编写 SQL 语句来获取数据。

之后就是定义 Entity、BO、VO, 然后模板式地往对应的 Repository、Service、Controller 类中添加代码。业务逻辑包裹在一个大的 SQL 语句中,而 Service 层可以做的事情很少。

SQL 都是针对特 定的业务功能编写的,复用性差。当我要开发另一个业务功能的时候,只能重新写个满足新需求的 SQL 语句,这就可能导致各种长得差不多、区别很小的 SQL 语句满天飞。

所以,在这个过程中,很少有人会应用领域模型、OOP 的概念,也很少有代码复用意识。 对于简单业务系统来说,这种开发方式问题不大。但对于复杂业务系统的开发来说,这样的 开发方式会让代码越来越混乱,最终导致无法维护。

1.4 代码示例

// 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) // 
// 业务逻辑都在Service中
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;  }
}
// BO只存储数据,无业务逻辑
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; 
}

2.DDD 开发模式(充血模型)

2.1 充血模型:

领域模型(Domain)处理大部分逻辑,Service只做少部分逻辑 --> 数据逻辑融合 --> 面向对象编程

2.2 DDD开发模式

分层模型(贫+充)

  • Controller + VO(贫血):因为VO是作前端传输数据载体,不应该有逻辑
  • Service < Domain(领域模型)
  • Service 类负责与 Dao 交流
  • 将Entity转化为Domain,由 Domain(领域模型)完成大部分业务逻辑
  • 保证Domain不与其他层(Dao…)或框架(Spring…) 耦合在一起
  • Service 类负责跨领域模型的业务聚合功能。
  • Service 类负责一些非功能性及与三方系统交互的工作。比如幂等、事务、发邮件、发消 息、记录日志、调用其他系统的 RPC 接口等,都可以放到 Service 类中
  • Dao + Entity(贫血):Entity主要用于数据库字段映射,若加入业务逻辑有被任意代码修改的风险

2.3 开发流程

领域驱动。

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

我们知道,越复杂的系统,对代码的复用性、易维护性要求就越高,我们就越应该花更多的 时间和精力在前期设计上。而基于充血模型的 DDD 开发模式,正好需要我们前期做大量的 业务调研、领域模型设计,所以它更加适合这种复杂系统的开发。

2.4 代码示例

篇幅原因,代码示例详见下一篇文章…

3.总结

ddd 架构 缺点 ddd模式开发_领域模型

3.1 贫血模型比充血模型更受欢迎

面向过程编程风格有种种弊端,比如,数据和操作分离之后,数据本身的操作就不受限制了。任何代码都可以随意修改数据。既然基于贫血模型的这种传统开发模式 是面向过程编程风格的,那它又为什么会被广大程序员所接受呢?关于这个问题,下面三点原因:

  • 第一点原因是,大部分情况下,我们开发的系统业务可能都比较简单,简单到就是基于 SQL 的 CRUD 操作,所以,我们根本不需要动脑子精心设计充血模型,贫血模型就足以应 付这种简单业务的开发工作。除此之外,因为业务比较简单,即便我们使用充血模型,那模型本身包含的业务逻辑也并不会很多,设计出来的领域模型也会比较单薄,跟贫血模型差不 多,没有太大意义。
  • 第二点原因是,充血模型的设计要比贫血模型更加有难度。因为充血模型是一种面向对象的编程风格。我们从一开始就要设计好针对数据要暴露哪些操作,定义哪些业务逻辑。而不是 像贫血模型那样,我们只需要定义数据,之后有什么功能开发需求,我们就在 Service 层定 义什么操作,不需要事先做太多设计。
  • 第三点原因是,思维已固化,转型有成本。基于贫血模型的传统开发模式经历了这么多年, 已经深得人心、习以为常。你随便问一个旁边的大龄同事,基本上他过往参与的所有 Web 项目应该都是基于这个开发模式的,而且也没有出过啥大问题。如果转向用充血模型、领域驱动设计,那势必有一定的学习成本、转型成本。很多人在没有遇到开发痛点的情况下,是不愿意做这件事情的。

3.2 什么项目使用DDD开发模式

  • 基于贫血模型的传统的开发模式,比较适合业务比较简单的系统开发。
  • 基于充血模型的 DDD 开发模式,更适合业务复杂的系统开发。比如,包含各种利息 计算模型、还款模型等复杂业务的金融系统。