DDD建模系列(五)

简介: DDD建模系列(五)

技术面问题:事务脚本模式/MVC三层开发架构面向过程,DDD面向对象

库表驱动开发:事务脚本模式

熟悉MVC的开发们就会突然发现,平时写的就是Transaction Script.

事务脚本模式(Transaction Script)可以简称TS模式,这个模式本身的核心思想就是名称中的两个词:即,事务与脚本。

①、事务可以理解为实际需要执行的一段原子业务;

②、脚本则是指的一组原子业务的编排方式。而通常来说脚本的编排会直接映射到用户的一个行为动作上。

或者说:

①、事务--通常指执行的业务

②、脚本--指一组系统执行的操作(也就是脚本)与每个用户操作在逻辑上关联起来。

事务脚本模式可能是最简单的业务逻辑模式,它完全是过程式的。

按照模式,TS没有包含任何面向对象设计的成分,通过TS建模的任何逻辑都是通过IF,WHILE和FOR等语言语法元素表达。

事务脚本本身可以理解是用户动作的一次任务编排,而触发的方式一般直接与用户的行为建立联系。由于使用的是脚本的编排方式,所以事务的组织是面向过程的。

而对于事务脚本模式,不同关联用户动作的脚本所编排的事务之间一般是相互隔离的,即事务之间不会影响,但不同的脚本之间可以编排相同的事务,即事务之间可以复用。

事务脚本模式的本质是事务与事务的编排。

一次用户的动作对应的是一次业务请求,请求后便需要一层来进行事务的编排,也就是脚本层。

脚本层是对于事务本身的编排,所以我们还需要一层事务层。事务针对的是数据库的多次操作,所以还有一层数据库层。

于是就有了:事务层,脚本层层,数据库层的三层模型。

而这与经典的三层模型是等价的,当然如果你的框架结构中有Handler层或者Manager层的话,脚本层或者事务层的对应关系是可以进行调整的。

而对于事务脚本的实现模式上就可以发现,其中是完全不需要任何面向对象的设计模式。所以在进行事务脚本设计模式建模的时候,其中的任何逻辑都可以通过if,else,for,while等元素表达,所以事务脚本模式会造成技术和业务混合,业务和业务混合。

基于贫血模型的传统MVC三层架构

①、什么是三层架构

MVC三层架构中的M表示Model,V表示View,C表示Controller。它将整个项目分为三层:展示层,逻辑层,数据层。

MVC三层开发架构是一个比较笼统的分层方式,落实到具体的开发层面,很多项目也并不会100%遵从MVC固定的分层方式,而是会根据具体的项目需求,做适当的调整。比如:现在很多Web或者APP项目都是前后端分离,后端负责暴漏接口给前端调用。这种情况下,我们一般将后端的项目分为Repository层,Service层,Controller层。其中,Repository层负责数据访问,Service层负责业务逻辑,Controller层负责暴漏接口。当然,这只是其中一分层和命名方式。

不同的项目,不同的团队,可能会对此有所调整,不过,万变不离其宗,只要是依赖数据库开发的Web项目,基本的分层思路都差不多。

MVC对应于DDD中的贫血模型

目前几乎所有的业务后端系统,都是基于贫血模型的。

那么什么是贫血模型呢?

贫血模型所有的业务逻辑都不包含在实体对象内,而是放在Business Logic层。

9f6a5bd7fcc64d121e12dd313cb55456.png

贫血模型优点是系统的层次结构清楚,各层之间单向依赖,Client ->(Business Facade)->Business Logic ->Data Access Object.

可见,领域对象几乎只作为传输介质之用,不会影响到层次的划分。这就是传统的数据驱动开发。

在使用Spring的使用,通常暗示着你使用了贫血模型,我们把Domain类用来单纯地存储数据,Spring管不着这些类的注入和管理,Spring关心的逻辑层(比如单例的被池化了的Business Logic层)可以被设计成singleton 的bean。假如我们这里逆天而行,硬要在Domain类中提供业务逻辑方法,那么我们在使用Spring构造这样的数据bean的时候就遇到许多麻烦,比如:bean之间的引用,可能引起大范围的bean之间的嵌套构造器的调用。

举个例子:


// 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;}

我们平时开发web后端项目的时候,基本上都是这么组织代码的

①、UserEntity和UserRepository组成了数据访问层

②、UserBo和UserService组成了业务逻辑层

③、UserVo和UserController在这里属于接口层。

从代码中,我们可以发现,UserBo是一个纯粹的数据结构,只包含数据,不包含任何业务逻辑,业务逻辑集中在UserService中,我们通过UserService来操作UserBo。

换句话来说,Service层的数据和业务逻辑,被分割为BO和Service两个类,像UserBo这样,只包含数据,不包含业务逻辑的类,就叫做贫血模型。

同理,UserEntity,UserVo都是基于贫血模型设计的。这种贫血模型将数据与业务分离,破坏了面向对象的封装特性,是一种典型的面向过程的编程风格。

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

充血模型,数据和对应的业务逻辑被封装到同一个类中,因此,这种充血模型满足面向对象的封装特性,是典型的面向对象编程风格。

实际上,基于充血模型的DDD开发模式实现的代码,也可以按照三层架构分层的。

Controller层还是负责暴漏接口,Repository层还是负责数据存取,Service层负责核心业务逻辑。它根基于贫血模型的传统开发模式的区别主要在Service层。

①、在基于贫血模型的传统开发模式中,Service层包含Service类和BO类两部分,BO是贫血模型,只包含数据,不包含具体的业务逻辑,业务逻辑集中在Service类中。

②、在基于充血模型的DDD开发模式中,Service层包含Service类和Domain类两部分。Domain就相当于贫血模型中的BO,不过,Domain与BO的区别在于它是基于充血模型开发的,既包含数据,也包含业务逻辑,而Service类变得非常单薄。

总结一下的话就是:

①、基于贫血模型的传统的开发模式,重Service轻BO;面向过程的思维方式,面向过程的架构方式

②、基于充血模型的DDD开发模式,轻Service重Domian,面向对象的思维方式,面向对象的架构方式。

和贫血模型相比,充血模型层次结构把大多业务逻辑放在Domain Object里面,Business Logic只是简单封装部分业务逻辑以及控制事务,权限等,这样层次结构就变为Client->Business Facade ->Business Logic ->Domain Object->Data Access Object.

75d48df505a6f729a389d7f3bcf8d22a.png

它的优点是面向对象,Business Logic符合单一职责,不像在贫血模型里面那样包含所有的业务逻辑太过沉重。

贫血模型是指领域对象里只有get和set方法(POJO),所有的业务逻辑都不包含在内而是放在Business Logic层。


@Data@ToStringpublic class User {     private Long id;     private String username;     private String password;     private Integer status;     private Date createdAt;     private Date updatedAt;     private Integer isDeleted;   public boolean isActive(User user){     return user.getStatus().equals(StatusEnum.ACTIVE.getCode());  }   public void setUsername(String username){    return username.trim();  }}

贫血模型在失血模型基础之上,领域对像的包含一些状态变化,但是停留在内存层面,不关心数据持久化。

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

基于贫血模型的传统开发模式,将数据与业务逻辑分离,违反了OOP的封装特性,实际上是一种面向过程的编程风格。

但是,现在几乎所有的Web项目,都是基于这种贫血模型的开发模式,甚至连java Spring框架的官方demo,都是按照这种开发模式来编写的。

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

原因如下:

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

②、第二点原因是,充血模型的设计要比贫血模型更有难度。因为充血模型是一种面向对象的编程风格,我们从一开始就要设计好针对数据暴漏哪些操作,定义哪些业务逻辑而不是像贫血模型那样,我们只需要定义数据,之后有什么功能开发需求,就在Service层定义什么操作,不需要事先做太多设计。对于业务部复杂的系统开发来说,基于贫血模型的传统开发模式简单够用,基于充血模型的DDD开发模型有点大材小用,无法发挥作用。相反,对于业务复杂的系统来说,基于充血模型的DDD开发模式,因为前期需要在设计上投入更多的时间和精力,来提高代码的复用性和维护性,所以相比基于贫血模型的开发模式,更加有优势。

③、第三点原因是,思维已固化,转型有成本,基于贫血模型的传统开发模式经历了这么多年,已经深得人心,习以为常。如果转向用充血模型,领域驱动设计,那势必有一定的学习成本,转型成本,很多人在没有遇到开发痛点的情况下,是不愿意做这件事情的。

相关文章
|
存储 自然语言处理 前端开发
领域驱动设计(DDD)-基础思想
一、序言     领域驱动设计是一种解决业务复杂性的设计思想,不是一种标准规则的解决方法。在领域驱动设计理念上,各路大侠的观点也是各有不同,能力有限、欢迎留言讨论。 二、领域驱动设计 DDD是什么 wiki释义:     领域驱动设计(英语:Domain-driven design,缩写 DDD)是一种通过将实现连接到持续进化的模型[1]来满足复杂
7541 0
|
3月前
|
设计模式 架构师 数据建模
DDD建模系列(四)
DDD建模系列(四)
DDD建模系列(四)
|
3月前
|
存储 前端开发 中间件
DDD建模系列(二)
DDD建模系列(二)
|
3月前
|
敏捷开发 架构师
DDD建模系列(三)
DDD建模系列(三)
|
3月前
|
架构师
DDD建模系列(一)
DDD建模系列(一)
|
设计模式 供应链 领域建模
DDD模型初探
DDD模型初探
126 0
|
消息中间件 JavaScript 小程序
领域驱动设计(DDD)的几种典型架构介绍
领域驱动设计(DDD)的几种典型架构介绍
|
设计模式 缓存 Java
DDD分层
为什么分层 引用《领域驱动设计模式、原理与实践》 为了避免将代码库变成大泥球(BBoM)并因此减弱领域模型的完整性且最终减弱可用性,系统架构要支持技术复杂性与领域复杂性的分离。引起技术实现发生变化的原因与引起领域逻辑发生变化的原因显然不同,这就导致基础设施和领域逻辑问题会以不同速率发生变化 每一层都有各自的职责,显然这也是符合SRP的
569 0
DDD分层
|
数据挖掘 Java 测试技术
DDD领域驱动设计实战(三)-深入理解实体(中)
DDD领域驱动设计实战(三)-深入理解实体(中)
281 0
|
前端开发 Java 数据库连接
DDD领域驱动设计实战(03)-深入理解实体
DDD领域驱动设计实战(03)-深入理解实体
409 0
DDD领域驱动设计实战(03)-深入理解实体