当前位置: 首页 > news >正文

学到贫血之-贫血模型和充血模型

学习自:设计模式之美

1 基于贫血模型的传统开发模式


// 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 是一个纯粹的数据结构,只包含数据,不包含任何业务逻辑。业务逻辑集中在UserService 中,通过 UserService 来操作 UserBo。换句话说,Service 层的数据和业务逻辑,被分割为 BO 和 Service 两个类中。

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

2 基于充血模型的 DDD 开发模式

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

基于充血模型的 DDD 开发模式实现的代码,也是按照 MVC 三层架构分层的。Controller 层还是负责暴露接口,Repository 层还是负责数据存取,Service 层负责核心业务逻辑。它跟基于贫血模型的传统开发模式的区别主要在 Service 层。

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

@Data
@Slf4j
public class Order extends BaseDomain {/*** 订单ID*/private String orderNo;...// 也有业务处理逻辑public static Order readFromDO(OrderDO orderDO) {Order result = new Order();return result;}public OrderDO convertToDO() {OrderDO result = new OrderDO();return result;}public OrderDTO convertToDTO() {OrderDTO result = new OrderDTO();return result;}public void putExt(String key, String value) {}
}

3 开发一个虚拟钱包系统

3.1 需求背景

每个用户开设一个系统内的虚拟钱包账户,支持用户充值、提现、支付、冻结、透支、转赠、查询账户余额、查询交易流水等操作。

 每个虚拟钱包账户都会对应用户的一个真实的支付账户,有可能是银行卡账户,也有可能是三方支付账户(比如支付宝、微信钱包)。

整个钱包系统的业务划分为两部分,其中一部分单纯跟应用内的虚拟钱包账户打交道,另一部分单纯跟银行账户打交道。基于这样一个业务划分,给系统解耦,将整个钱包系统拆分为两个子系统:虚拟钱包系统和三方支付系统。

钱包的核心功能

 

 3.2 基于贫血模型的传统开发模式


public class VirtualWalletController {// 通过构造函数或者IOC框架注入private VirtualWalletService virtualWalletService;public BigDecimal getBalance(Long walletId) { ... } //查询余额public void debit(Long walletId, BigDecimal amount) { ... } //出账public void credit(Long walletId, BigDecimal amount) { ... } //入账public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) { ...} //转账//省略查询transaction的接口
}// Service 和 BO 负责核心业务逻辑
public class VirtualWalletBo {private Long id;private Long createTime;private BigDecimal balance;
}public class VirtualWalletService {private VirtualWalletRepository walletRepo;private VirtualWalletTransactionRepository transactionRepo;public VirtualWalletBo getVirtualWallet(Long walletId) {VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);VirtualWalletBo walletBo = convert(walletEntity);return walletBo;}public BigDecimal getBalance(Long walletId) {return walletRepo.getBalance(walletId);}@Transactionalpublic void debit(Long walletId, BigDecimal amount) {...}@Transactionalpublic void credit(Long walletId, BigDecimal amount) {...}@Transactionalpublic void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) {...}
}

3.3 基于充血模型的 DDD 开发模式

虚拟钱包 VirtualWallet 类设计成一个充血的 Domain 领域模型,并且将原来在 Service 类中的部分业务逻辑移动到 VirtualWallet 类中,让 Service 类的实现依赖 VirtualWallet 类。


public class VirtualWallet {private Long id;public VirtualWallet(Long preAllocatedId) {this.id = preAllocatedId;}public void freeze(BigDecimal amount) { ... }public void unfreeze(BigDecimal amount) { ...}public void increaseOverdraftAmount(BigDecimal amount) { ... }public void decreaseOverdraftAmount(BigDecimal amount) { ... }public void closeOverdraft() { ... }public void openOverdraft() { ... }public BigDecimal balance() {return this.balance;}public BigDecimal getAvaliableBalance() {BigDecimal totalAvaliableBalance = this.balance.subtract(this.frozenAmount);if (isAllowedOverdraft) {totalAvaliableBalance += this.overdraftAmount;}return totalAvaliableBalance;}...
}public class VirtualWalletService {private VirtualWalletRepository walletRepo;private VirtualWalletTransactionRepository transactionRepo;public VirtualWallet getVirtualWallet(Long walletId) {VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);VirtualWallet wallet = convert(walletEntity);return wallet;}public BigDecimal getBalance(Long walletId) {return walletRepo.getBalance(walletId);}@Transactionalpublic void debit(Long walletId, BigDecimal amount) {...}@Transactionalpublic void credit(Long walletId, BigDecimal amount) {...}
}

Service 的职责

Service 类负责与 Repository 交流,调用 Respository 类的方法,获取数据库中的数据,转化成领域模型 VirtualWallet,然后由领域模型 VirtualWallet 来完成业务逻辑,最后调用 Repository 类的方法,将数据存回数据库。

之所以让 VirtualWalletService 类与 Repository 打交道,而不是让领域模型 VirtualWallet 与 Repository 打交道,那是因为我们想保持领域模型的独立性,不与任何其他层的代码(Repository 层的代码)或开发框架(比如 Spring、MyBatis)耦合在一起,将流程性的代码逻辑(比如从 DB 中取数据、映射数据)与领域模型的业务逻辑解耦,让领域模型更加可复用。

Service 类负责一些非功能性及与三方系统交互的工作。比如幂等、事务、发邮件、发消息、记录日志、调用其他系统的 RPC 接口等,都可以放到 Service 类中。

Controller 层和 Repository 层是否有必要进行充血领域建模呢

没有必要。Controller 层主要负责接口的暴露,Repository 层主要负责与数据库打交道,这两层包含的业务逻辑并不多。如果业务逻辑比较简单,就没必要做充血建模,即便设计成充血模型,类也非常单薄,看起来也很奇怪。

相关文章:

学到贫血之-贫血模型和充血模型

学习自:设计模式之美 1 基于贫血模型的传统开发模式 // ControllerVO(View Object) public class UserController {private UserService userService; //通过构造函数或者IOC框架注入public UserVo getUserById(Long userId) {UserBo userBo userService.getUser…...

Java常用组件面试题

文章目录HTTP通信协议Kafka消息队列Linux操作系统Mybatis框架SpringCloud框架HTTP通信协议 https通信过程 https协议是指对通过http协议传输数据的进行加密和解密。当客户端发送https请求时,服务端会返回数字证书给客户端,客户端验证通过后会生成随机数…...

MySQL常见问题的解决方法

目录 cmd没有管理员权限 没有my.ini这个文件 ERROR 1045 (28000): Access denied for user ODBClocalhost (using password: NO) ERROR 1045 (28000): Access denied for user rootlocalhost (using password: NO) 其他常见问题 cmd没有管理员权限 cmd一定要用管理员权限打…...

全网详细介绍nginx的反向代理、正向代理配置,location的指令说明,反向代理的两个示例代码以及全局块,events块和http快的说明。

文章目录1. 文章引言2. 何谓反向代理3. 解析nginx的配置文件3.1 全局块(global block)3.2 events块(events block)3.3 http块(http block)4. 如何配置反向代理4.1 反向代理示例14.2 反向代理示例25. 补充说明5.1 location指令说明5.2 nginx完整配置文件1. 文章引言 如果你的服务…...

容斥恒等式的证明

容斥恒等式的证明 推广公式 P(A∪B)P(A)P(B)−P(A∩B)P(A\cup B)P(A)P(B)-P(A\cap B) P(A∪B)P(A)P(B)−P(A∩B) (a)设A、B、C为三个事件,则下列恒等式成立: P(A∪B∪C)P(A)P(B)P(C)−P(A∩B)−P(A∩C)−P(B∩C)P(A∩B∩C)P(A\cup B\cup C)P(A)P(B)P(C)…...

Java中的this与super关键字深度解析

一、this关键字this 关键字是 Java 常用的关键字,可用于任何实例方法内指向当前对象,也可指向对其调用当前方法的对象,或者在需要当前类型对象引用时使用。(1)this.属性名this修饰的变量用于指代成员变量方法的形参如果…...

CSS3新增的视口单位Vh、Vw单位

定义vw:浏览器可见视口【宽度】的百分比(1vw代表视窗【宽度】的1%)vh:浏览器可见视口【高度】的百分比(1vw代表视窗【高度】的1%)vmin:当前 vw 和 vh 较小的一个值。vmax:当前 vw 和…...

【Linux】yum安装docker指定版本

🍁博主简介 🏅云计算领域优质创作者   🏅华为云开发者社区专家博主   🏅阿里云开发者社区专家博主 💊交流社区:运维交流社区 欢迎大家的加入! 文章目录卸载已有的docker部署指定版本docker安…...

SpringBoot相关操作

01-今日内容 Spring概述、快速入门SpringBoot配置SpringBoot整合 02-SpringBoot概述 SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的…...

Python super()函数:调用父类的构造方法

Python 中子类会继承父类所有的类属性和类方法。严格来说,类的构造方法其实就是实例方法,因此毫无疑问,父类的构造方法,子类同样会继承。 但我们知道,Python 是一门支持多继承的面向对象编程语言,如果子类…...

@ConfigurationProperties在方法上的使用

文章目录1. 前言2. 先说结论3. 代码解释1. Component ConfigurationProperties2. EnableConfigurationProperties ConfigurationProperties3. Bean ConfigurationProperties1. 前言 在学习spring的时候,ConfigurationProperties应该经常被使用到,作用…...

【QT】如何查找和获取界面上的子部件(findChild 和 findChidren)

目录1. findChild()函数2. findChildren()函数3. 示例1. findChild()函数 函数原型: T QObject::findChild(const QString &name QString(), Qt::FindChildOptions options Qt::FindChildrenRecursively) const返回该对象的子对象,该子对象可以转…...

MIT 6.S081学习笔记

计划花25天时间学完6.S081课程,从2月20日-3月20日。课程主页Link   xv6 book   GDB User Manual Lecture 1: Introduction and Examples课程主题:设计和实现操作系统   OS的三大功能:多路复用、隔离和交互。 Lab: Xv6 and Unix utiliti…...

《网络安全入门到精通》 - 2.1 - Windows基础 - DOS命令Windows防火墙Windows共享文件

「作者简介」:CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「订阅专栏」:此文章已录入专栏《网络安全入门到精通》 Windows基础一、DOS命令1、目录文件操作dir 列出目录文件cd 切换目录md 创建目录rd 删除目录move 移动文件或目…...

八、Vben框架动态生成可编辑Table

开发过程中产品经理提出了一些奇怪的需求,让人很摸不着头脑,一问就是客户的需求就是这样,那么我们开发只能想各种办法啦。 最近就提出了两个需求, 第一个是需要在日期选择的时候根据时间选择的不同让底下table中增加两个时间中间的…...

浅谈ERP数据的重要性

影响一个ERP项目的因素有很多,数据无疑是其中很重要的一项,正所谓“正确的诊断源于准确的信息,准确的信息基于可靠的采集”,当我们抓住数据这个根基,大处着眼,小处着手的时候,我们距离ERP成功的日子就不会太…...

【RabbitMQ笔记06】消息队列RabbitMQ七种模式之Topics主题模式

这篇文章,主要介绍消息队列RabbitMQ七种模式之Topics主题模式。 目录 一、消息队列 1.1、主题模式(Topics) 1.2、案例代码 (1)引入依赖 (2)编写生产者 (3)编写消费…...

ChatGPT似乎有的时候并不能搞懂Java的动态分派,你懂了吗?

目录 碎碎念 ChatGPT 中出现的问题 那么正确答案应该是什么呢? 分派的相关知识点总结: 分派是什么? 静态分派与动态分派: Java语言是静态多分派,动态单分派的; 静态分派:静态重载多分派…...

【C++初阶】vector的模拟实现

大家好我是沐曦希💕 文章目录一、前言二、无参构造&析构三、基础接口1.empty和clear2.size和capacity3.[]和iterator四、reserve和resize五、尾插尾删六、其他构造1.迭代器区间构造2.拷贝构造七、memcpy问题八、完整代码一、前言 在模拟实现容器时候&#xff0…...

微信小程序、小游戏的流量主一般可以赚多少钱?

本篇文章主要科普小程序、小游戏流量主一般赚钱的实际情况,通过在下长期运营的经验汇总而成。 日期:2023年2月26日 作者:任聪聪 小程序、小程序满1000用户后即可开通流量主,但实际上很多人并没有传说中的那种日赚几千的流量收入的…...

jni-Demo-基于linux(c++ java)

跑一个jni 的最简单的Demo需要提前准备 VsCode 编译器、win10下,vscode中集成linux操作系统、c编译器(gcc、g),java编译器(jdk1.8)参考:https://mangocool.com/1653030123842.htmlJniDemo类&…...

指针的进阶——(1)

本次讲解重点: 1、字符指针 2、数组指针 3、指针数组 4、数组传参和指针传参 5、函数指针 关于指针这个知识点的主题,我们在前面已经初级阶段已经对指针有了大致的理解和应用了。我们知道了指针的概念: 1、指针就是地址,但口…...

电商平台的促销活动如何抵御大流量的ddos攻击

每一次活动大促带来的迅猛流量,对技术人而言都是一次严峻考验。如果在活动期间遭受黑产恶意 DDoS 攻击,无疑是雪上加霜。电商的特性是业务常态下通常不会遭受大流量 DDoS 攻击,且对延迟敏感,因此只需要在活动期间按需使用 DDoS 防…...

代码随想录-48-104. 二叉树的最大深度

目录前言题目1.层序迭代思路2. 本题思路分析:3. 算法实现4. pop函数的算法复杂度5. 算法坑点前言 在本科毕设结束后,我开始刷卡哥的“代码随想录”,每天一节。自己的总结笔记均会放在“算法刷题-代码随想录”该专栏下。 代码随想录此题链接 …...

【Vue3源码】第六章 computed的实现

【Vue3源码】第六章 computed的实现 上一章节我们实现了 ref 及其它配套的isRef、unRef 和 proxyRefs API。这一章开始实现computed计算属性。 认识computed 接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。…...

Java基础之注解

3.注解 3.1概述【理解】 概述 对我们的程序进行标注和解释 注解和注释的区别 注释: 给程序员看的注解: 给编译器看的 使用注解进行配置配置的优势 代码更加简洁,方便 3.2自定义注解【理解】 格式 public interface 注解名称 { ​ public 属性类型 属性名() default 默认值…...

三、线性表

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 前言 提示:这里可以添加本文要记录的大概内容: 自学JAVA数据结构笔记,跟学视频为:黑马程序员Java数据结构与java算法全套教程…...

C++统计方形

统计方形 内存限制:256 MB 时间限制:1 S 题目描述 有一个n*m方格的棋盘,求其方格包含多少正方形、长方形(此处长方形不包含正方形) 输入格式 输入存在多组测试数据。每组测试数据输入两个整数n,m,数字不超…...

Tina_Linux配网开发指南

OpenRemoved_Tina_Linux_配网_开发指南 1 概述 1.1 编写目的 介绍Allwinner 平台上基于wifimanager-v2.0 的WiFi 配网方式,包括softap(WiFi ap 模式热点配网),soundwave(声波配网),BLE(蓝牙低功耗配网)。 1.2 适用范围 • allwinner 软件平台tina v5.0 版本及以…...

高频面试题|RabbitMQ如何防止消息的重复消费?

一. 前言最近有很多小伙伴开始找工作,在面试时,面试官经常会问我们这样一个题目:RabbitMQ如何防止重复消费?有很多小伙伴这个时候都在想,消息怎么还会重复消费呢???.......所以他们在面试后就跑来问壹哥,针对这个比…...

wap网站如何建设/进入百度

2019独角兽企业重金招聘Python工程师标准>>> 如下是tomcat的配置文件server.xml中配置Http11NioProtocol协议的示例 <Connector connectionTimeout"20000" maxThreads"1000" port"8080" protocol"org.apache.coyote.http11.H…...

ppt做的比较好的网站/网页制作的软件

我们常见的执行js代码都是放入到HTML引入后然后通过HTML文件来执行胡查看代码&#xff0c;显然这是比较麻烦的事情&#xff0c; 如果你的电脑里面安装了node.js&#xff0c;你可以使用node来直接使用node来运行你想要运行的js文件&#xff0c; 具体的操作如图所示&#xff1a…...

网站推广链接怎么做/微信管理系统登录入口

数据科学正快速成为各行各业开发人员和管理人员的关键技能&#xff0c;同时它似乎也非常有趣。但它也相当复杂——有太多的工程分析技术&#xff0c;你很难知道自己做得是否正确或者哪里存在陷阱。在该系列文章中&#xff0c;我们将探讨如何利用数据科学——从已经采用并成功实…...

wordpress lucene/大连seo关键词排名

作者&#xff1a;武沛齐 出处&#xff1a;http://www.cnblogs.com/wupeiqi/ 版本控制说到版本控制&#xff0c;脑海里总会浮现大学毕业是写毕业论文的场景&#xff0c;你电脑上的毕业论文一定出现过这番景象&#xff01;毕业论文_初稿.doc 毕业论文_修改1.doc 毕业论文_修改2.d…...

用fw做网站页面/b2b外链代发

设备&#xff1a;iOS10.3.2的5s一&#xff1a;设备越狱1.使用g0blin 进入官网下载ipa2.然后使用Impactor 安装到手机3.打开应用进行越狱。4.越狱安装OpenSSL和Openssh发现电脑链接不上&#xff0c;网上有个大神已经提出解决方案。a->首先卸载OpenSSL和Opensshb->在手机cy…...

网站建设wuliankj/网站优化公司哪家效果好

1.SQL前面加 EXPLAIN 定位到sql级别 各个属性的含义 id select查询的序列号 select_type select查询的类型&#xff0c;主要是区别普通查询和联合查询、子查询之类的复杂查询。 table 输出的行所引用的表。 type 联合查询所使用的类型。 type显示的是访问类型&#xff0c;是较为…...