十一、设计模式之美-11| 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?

一.钱包业务背景介绍

一个简单的钱包业务功能如下

1、充值

用户通过三方支付渠道,把自己银行卡账户内的钱,充值到虚拟钱包账号中。这整个过程,我们可以分解为三个主要的操作流程:第一个操作是从用户的银行卡账户转账到应用的公共银行卡账户;第二个操作是将用户的充值金额加到虚拟钱包余额上;第三个操作是记录刚刚这笔交易流水。

2、支付

用户用钱包内的余额,支付购买应用内的商品。实际上,支付的过程就是一个转账的过程,从用户的虚拟钱包账户划钱到商家的虚拟钱包账户上。除此之外,我们也需要记录这笔支付的交易流水信息。

3、提现

除了充值、支付之外,用户还可以将虚拟钱包中的余额,提现到自己的银行卡中。这个过程实际上就是扣减用户虚拟钱包中的余额,并且触发真正的银行转账操作,从应用的公共银行账户转钱到用户的银行账户。同样,我们也需要记录这笔提现的交易流水信息。

4、查询余额

查询余额功能比较简单,看一下虚拟钱包中的余额数字即可。

5、查询交易流水

查询交易流水也比较简单。我们只支持三种类型的交易流水:充值、支付、提现。在用户充值、支付、提现的时候,我们会记录相应的交易信息。在需要查询的时候,我们只需要将之前记录的交易流水,按照时间、类型等条件过滤之后,显示出来即可。

二.钱包系统的设计思路

根据刚刚讲的业务实现流程和数据流转图,我们可以把整个钱包系统的业务划分为两部分,其中一部分单纯跟应用内的虚拟钱包账户打交道,另一部分单纯跟银行账户打交道。我们基于这样一个业务划分,给系统解耦,将整个钱包系统拆分为两个子系统:虚拟钱包系统和三方支付系统。

1、虚拟钱包系统设计

2、虚拟钱包的核心功能


,虚拟钱包系统要支持的操作非常简单,就是余额的加加减减。其中,充值、提现、查询余额三个功能,只涉及一个账户余额的加减操作,而支付功能涉及两个账户的余额加减操作:一个账户减余额,另一个账户加余额。交易流水号的记录和查询较为复杂一些,交易流水首先要包含以下信息:

交易流水的数据格式包含两个钱包账号,一个是入账钱包账号,一个是出账钱包账号。为什么要有两个账号信息呢?这主要是为了兼容支付这种涉及两个账户的交易类型。不过,对于充值、提现这两种交易类型来说,我们只需要记录一个钱包账户信息就够了。

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

1.Controller

Controller暴露接口,并且调用Service 的方法。


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的接口
}

2.Service和Entity

Service负责业务逻辑,Entity负责数据存储。


public class VirtualWalletBo {//省略getter/setter/constructor方法private Long id;private Long createTime;private BigDecimal balance;
}public Enum TransactionType {DEBIT,CREDIT,TRANSFER;
}public class VirtualWalletService {// 通过构造函数或者IOC框架注入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) {VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);BigDecimal balance = walletEntity.getBalance();if (balance.compareTo(amount) < 0) {throw new NoSufficientBalanceException(...);}VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();transactionEntity.setAmount(amount);transactionEntity.setCreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.DEBIT);transactionEntity.setFromWalletId(walletId);transactionRepo.saveTransaction(transactionEntity);walletRepo.updateBalance(walletId, balance.subtract(amount));}@Transactionalpublic void credit(Long walletId, BigDecimal amount) {VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();transactionEntity.setAmount(amount);transactionEntity.setCreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.CREDIT);transactionEntity.setFromWalletId(walletId);transactionRepo.saveTransaction(transactionEntity);VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);BigDecimal balance = walletEntity.getBalance();walletRepo.updateBalance(walletId, balance.add(amount));}@Transactionalpublic void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) {VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();transactionEntity.setAmount(amount);transactionEntity.setCreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.TRANSFER);transactionEntity.setFromWalletId(fromWalletId);transactionEntity.setToWalletId(toWalletId);transactionRepo.saveTransaction(transactionEntity);debit(fromWalletId, amount);credit(toWalletId, amount);}
}

3.Mapper

负责与数据库交互

4、基于充血模型的 DDD 开发模式

此处,在我的理解来看,贫血模型将数据存储放在了entity,将所有的业务逻辑放在了service,DDD是直接设计合理的对象,对象既有数据存储功能,也对外提供了所设计的业务逻辑,service层主要负责使用该对象去进行业务逻辑操作。


public class VirtualWallet { // Domain领域模型(充血模型)private Long id;private Long createTime = System.currentTimeMillis();;private BigDecimal balance = BigDecimal.ZERO;public VirtualWallet(Long preAllocatedId) {this.id = preAllocatedId;}public BigDecimal balance() {return this.balance;}public void debit(BigDecimal amount) {if (this.balance.compareTo(amount) < 0) {throw new InsufficientBalanceException(...);}this.balance = this.balance.subtract(amount);}public void credit(BigDecimal amount) {if (amount.compareTo(BigDecimal.ZERO) < 0) {throw new InvalidAmountException(...);}this.balance = this.balance.add(amount);}
}public class VirtualWalletService {// 通过构造函数或者IOC框架注入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) {VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);VirtualWallet wallet = convert(walletEntity);wallet.debit(amount);VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();transactionEntity.setAmount(amount);transactionEntity.setCreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.DEBIT);transactionEntity.setFromWalletId(walletId);transactionRepo.saveTransaction(transactionEntity);walletRepo.updateBalance(walletId, wallet.balance());}@Transactionalpublic void credit(Long walletId, BigDecimal amount) {VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);VirtualWallet wallet = convert(walletEntity);wallet.credit(amount);VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();transactionEntity.setAmount(amount);transactionEntity.setCreateTime(System.currentTimeMillis());transactionEntity.setType(TransactionType.CREDIT);transactionEntity.setFromWalletId(walletId);transactionRepo.saveTransaction(transactionEntity);walletRepo.updateBalance(walletId, wallet.balance());}@Transactionalpublic void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) {//...跟基于贫血模型的传统开发模式的代码一样...}
}

对于简单功能来讲,贫血模型的设计更加方便,但是对于复杂功能来说,很有必要设计成充血模型。

三.辩证思考与灵活应用

1、在基于充血模型的 DDD 开发模式中,将业务逻辑移动到 Domain 中,Service 类变得很薄,但在我们的代码设计与实现中,并没有完全将 Service 类去掉,这是为什么?或者说,Service 类在这种情况下担当的职责是什么?哪些功能逻辑会放到 Service 类中?

和我之前的理解一样,对于贫血模型,核心在于1、对象的字段设计2、Service的业务逻辑,但是如果使用充血模型,那么就淡化了Service的部分业务,但是并不是代表Service是毫无意义的,并不是所有的业务逻辑都适合直接放在对象设计之中。

  1. Service 类负责与 Repository 交流。在我的设计与代码实现中,VirtualWalletService 类负责与 Repository 层打交道,调用 Respository 类的方法,获取数据库中的数据,转化成领域模型VirtualWallet,然后由领域模型 VirtualWallet 来完成业务逻辑,最后调用 Repository类的方法,将数据存回数据库。
  2. Service 类负责跨领域模型的业务聚合功能。VirtualWalletService 类中的 transfer()
    转账函数会涉及两个钱包的操作,因此这部分业务逻辑无法放到 VirtualWallet 类中,所以,我们暂且把转账业务放到VirtualWalletService类中了。当然,虽然功能演进,使得转账业务变得复杂起来之后,我们也可以将转账业务抽取出来,设计成一个独立的领域模型。
  3. Service 类负责一些非功能性及与三方系统交互的工作。比如幂等、事务、发邮件、发消息、记录日志、调用其他系统的 RPC接口等,都可以放到 Service 类中。

2、在基于充血模型的 DDD 开发模式中,尽管 Service 层被改造成了充血模型,但是 Controller 层和 Mapper层还是贫血模型,是否有必要也进行充血领域建模呢?

没有必要,因为Controller 和Mapper都没什么业务逻辑,主要是拥有不同的职责,Controller负责对外暴露接口,Mapper负责与数据库打交道。

设计模式之美-11| 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?相关推荐

  1. Windows下Eclipse配置基于MinGW的C/C++开发环境

    Eclipse安装CDT及其他插件的方法 1.搜索CDT线上安装地址 进入Ecllipse插件下载官网https://www.eclipse.org/cdt/downloads.php,在官网页面列表 ...

  2. ubuntu16.04下安装dnw和fastboot工具,解决开发板只有uboot系统,没有网络的情况下,通过dnw和fastboot传送文件到开发板

    当我们拿到一块裸板,这时板上没有系统.此时pc机和开发板就没法通过ftp,nfs等网络协议来通信,就没法将我们的pc机端上的文件传输到裸板上.这时我们先自己配置编译bootloader,如u-boot ...

  3. java代下订单管理模块_用java语言开发一个订单管理系统

    管理员登陆窗体(LoginForm):窗体中包含"管理员姓名","管理员密码",按钮:"确定","取消"操作主窗体(Ma ...

  4. 目标检测YOLO实战应用案例100讲-基于YOLO模型的遥感影像 飞机目标检测技术研究

    目录 基于YOLOv5模型的遥感影像飞机目标检测 基于深度学习的目标检测算法 POLO系列模型原理

  5. 本人亲自整理的极客时间设计模式之美的硬核笔记

    由于笔记内容过多,我把它放到语雀上了. 点击我 以下内容是为了让搜索引擎,检测到这篇文章.要阅读体验,请点击上面的连接"点击我",去我的语雀看.对了,我看到语雀那里有投诉的功能,请 ...

  6. 设计模式之美总结(面向对象篇)

    title: 设计模式之美总结(面向对象篇) date: 2022-10-11 17:02:54 tags: 设计模式 categories: 技术书籍及课程 cover: https://cover ...

  7. 设计模式之美-王争-极客时间-返现24元 限时优惠

    极客时间出品的<设计模式之美>由王争所作,王争是前Google工程师手把手教你写高质量代码 前Google工程师,<数据结构与算法之美>专栏作者.本专栏前Google工程师手把 ...

  8. 设计模式之美总结(设计原则篇)

    title: 设计模式之美总结(设计原则篇) date: 2022-10-27 17:31:42 tags: 设计模式 categories: 技术书籍及课程 cover: https://cover ...

  9. 代码技巧--设计模式之美

    设计模式之美 零.文章目录 一.概述 1.学习导读 本文是极客时间专栏<设计模式之美>的学习笔记,详情请看原文. 学习算法 :是为了写出 高效 的代码: 学习设计模式 :是为了写出 高质量 ...

最新文章

  1. Tomcat企业级应用
  2. 关于字符串指针不可修改的问题
  3. Bootstrap4+MySQL前后端综合实训-Day02-AM【Bootstrap4(入门、环境搭建、文字排版、颜色、表格、图片、进度条、折叠、输入框组、模态框)、Font Awesome字体图标】
  4. 如何判断Socket连接失效
  5. 游戏会记录某个api的调用_专家坐诊丨老出BUG怎么办?游戏服务器常见问题的解决方法分享...
  6. 数据结构括号匹配代码_栈:如何实现有效括号的判断?
  7. pathinfo函数获取非UTF-8字符集文件名的问题
  8. Flex4中使用WCF
  9. jQuery函数$(window).load事件
  10. jni直接转byte_JNI jbyteArray转char*
  11. xcode5 xcode6 xcode6.2beta网盘下载地址
  12. Axure 教程 | 微博分享
  13. EasyBoot教程一:制作WIN7原版多重启动盘方法
  14. 学生用计算机计算分数,Excel案例(十三)——学生计算机成绩表
  15. 基于 arduino 的两轮自行车
  16. 关于excel导入带图片
  17. 申请专利 服务器拒收怎么找回,找回或修改登录密码的方法及装置专利_专利申请于2014-10-23_专利查询 - 天眼查...
  18. 1. Java基础语法
  19. 邮件合并批量制作邀请函
  20. 亿可控_第1章_系统分析与设计

热门文章

  1. 断路器、单相电、三相电
  2. 教你一招,如何解决 “这台电脑无法运行 Windows 11” 的问题
  3. [图解] 孔明锁的正确安装方法
  4. mac电脑idea配置tfs并连接
  5. Type-c四合一多功能扩展坞TYPE C转HDMI+VGA+USB3.0+PD3.0
  6. unity开发之二:调用电脑外部键盘osk
  7. 前端MVC设计模式VSM,V,VM设计模式
  8. coreldraw粉刷的感觉_详解CorelDRAW中的涂抹笔刷工具
  9. bash脚本程序语法Linux
  10. Linux ALSA - 支持软件回采参考(playback)信号