设计模式、面向对象八:如何利用基于充血模型的DDD开发一个虚拟钱包系统?

钱包业务背景

一般具有支付、购买功能的应用,都支持钱包的功能。应用为每个用户开设一个系统内的虚拟钱包账户,支持用户重置、提现、支付、冻结、透支、转账、查询账户余额、查询交易流水。一般来讲,每个虚拟钱包账户都会对应用户的一个真实的支付账号,有可能是银行卡账户,也有可能是三方支付账户(支付宝、微信钱包)。

在这里插入图片描述

钱包的五大核心功能的业务
  1. 充值
    用户通过三方支付渠道,把自己银行卡账户内的钱,充值到虚拟钱包账户。
    流程
    • 第一是从用户的银行卡账户转账到应用的公共银行卡账户
    • 第二是将用户的充值金额加到虚拟钱包余额上
    • 第三是记录刚刚这笔交易流水
      在这里插入图片描述
  2. 支付
    用户用钱包内的余额,支付购买应用内的商品,支付的过程就是个转账的过程。
    流程
    • 第一 从用户的虚拟钱包账户划钱到商家的虚拟钱包账户上
    • 第二 触发真正的银行转账操作,从应用的公共银行账户转钱到商家的银行账户(注意,这儿并不是从用户的银行账户转到商家的银行账户)
    • 第三 记录这笔支付的交易流水
      在这里插入图片描述
  3. 提现
    用户可以将虚拟钱包中的余额,提现到自己的银行卡中。
    流程
    • 第一 从用户的虚拟钱包账户金额减去提现的金额
    • 第二 触发银行转账操作,从应用的公共银行卡赚钱到用户的银行卡
    • 第三 记录这笔支付的交易流水
      在这里插入图片描述
  4. 查询余额
    用户查询虚拟钱包的余额数字即可
  5. 查询交易流水
    用户根据条件查询之前记录的交易即可
钱包系统的设计思路

根据上面的业务流程,我们可以把钱包系统的业务 划分为两部分

  • 一部分单纯的跟应用内的虚拟钱包账户打交道
  • 一部分单纯的跟银行账户打交道
    基于这样的业务划分,给系统解耦,将整个钱包系统拆分为两个子系统:虚拟钱包系统和三方支付系统
    在这里插入图片描述
    下面代码只聚焦于虚拟钱包系统的设计和实现。
  1. 基于贫血模型的传统开发模式
    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) { ...} // 转账
    }
    
    public class VirtualWalletBo {// 省略 getter/setter/constructor 方法
      private Long id;
      private Long createTime;
      private BigDecimal balance;
    }
    
    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 virtualWalletRepo.getBalance(walletId);
      }
      
      public void debit(Long walletId, BigDecimal amount) {
        VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
        BigDecimal balance = walletEntity.getBalance();
        if (balance.compareTo(amount) < 0) {
          throw new NoSufficientBalanceException(...);
        }
        walletRepo.updateBalance(walletId, balance.subtract(amount));
      }
      
      public void credit(Long walletId, BigDecimal amount) {
        VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
        BigDecimal balance = walletEntity.getBalance();
        walletRepo.updateBalance(walletId, balance.add(amount));
      }
      
      public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) {
        VirtualWalletTransactionEntity transactionEntity = new VirtualWalletTransactionEntity();
        transactionEntity.setAmount(amount);
        transactionEntity.setCreateTime(System.currentTimeMillis());
        transactionEntity.setFromWalletId(fromWalletId);
        transactionEntity.setToWalletId(toWalletId);
        transactionEntity.setStatus(Status.TO_BE_EXECUTED);
        Long transactionId = transactionRepo.saveTransaction(transactionEntity);
        try {
          debit(fromWalletId, amount);
          credit(toWalletId, amount);
        } catch (InsufficientBalanceException e) {
          transactionRepo.updateStatus(transactionId, Status.CLOSED);
          ...rethrow exception e...
        } catch (Exception e) {
          transactionRepo.updateStatus(transactionId, Status.FAILED);
          ...rethrow exception e...
        }
        transactionRepo.updateStatus(transactionId, Status.EXECUTED);
      }
    }
    
  2. 基于充血模型的DDD开发模式
    
    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.subtract(amount);
      }
      
      public void credit(BigDecimal amount) {
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
          throw new InvalidAmountException(...);
        }
        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 virtualWalletRepo.getBalance(walletId);
      }
      
      public void debit(Long walletId, BigDecimal amount) {
        VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
        VirtualWallet wallet = convert(walletEntity);
        wallet.debit(amount);
        walletRepo.updateBalance(walletId, wallet.balance());
      }
      
      public void credit(Long walletId, BigDecimal amount) {
        VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId);
        VirtualWallet wallet = convert(walletEntity);
        wallet.credit(amount);
        walletRepo.updateBalance(walletId, wallet.balance());
      }
      
      public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) {
        //... 跟基于贫血模型的传统开发模式的代码一样...
      }
    }
    
    这种充血模型的设计思路,看起来领域模型VitualWallet类很单薄,包含的业务逻辑很简单,这种思路貌似没太大优势。
    这也是大部分业务系统都使用基于贫血模型开发的原因。
    不过当虚拟钱包需要支持更复杂的业务的时候,它的优势就会显现出来。
    比如要支持透支一定额度和冻结部分余额的功能,这是重新设计VirtualWallet类的实现代码
    
    public class VirtualWallet {
      private Long id;
      private Long createTime = System.currentTimeMillis();;
      private BigDecimal balance = BigDecimal.ZERO;
      private boolean isAllowedOverdraft = true;
      private BigDecimal overdraftAmount = BigDecimal.ZERO;
      private BigDecimal frozenAmount = BigDecimal.ZERO;
      
      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 void debit(BigDecimal amount) {
        BigDecimal totalAvaliableBalance = getAvaliableBalance();
        if (totoalAvaliableBalance.compareTo(amount) < 0) {
          throw new InsufficientBalanceException(...);
        }
        this.balance.subtract(amount);
      }
      
      public void credit(BigDecimal amount) {
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
          throw new InvalidAmountException(...);
        }
        this.balance.add(amount);
      }
    }
    
总结
  • 基于充血模型的DDD开发模式跟基于贫血模型的传统开发模式相比,主要区别在Service层。在基于充血模型的开发模式下,我们将部分原来在Service类中的业务逻辑移动到了一个充血的Doamin领域模型中,让Service类的实现依赖这个Domain类
  • 在基于充血模型的DDD开发模式下,Service类并不会完全移除,而是负责一些不适合放在Domain类中的功能。比如,负责和Repository层打交道、跨领域模型的业务聚合功能、幂等事务等非功能性的工作
  • 基于充血模型的DDD开发模式跟基于贫血模型的传统开发模式相比,Controller层和Repository层的代码基本上相同。 这是因为REpository层的Entity生命周期有限,Controller层的VO只是单纯作为一种DTO。两部分的业务逻辑都不会太复杂。业务逻辑主要集中在Service层。所以,Repository层和Controller层继续沿用贫血模型的设计思路是没有问题
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
面向过程的贫血模型面向对象充血模型软件开发中常见的两种设计模式。 面向过程的贫血模型是一种基于过程的设计方式,其中数据和处理逻辑被分离。在这种模型中,数据结构被定义为简单的数据对象,而处理逻辑则由一系列函数或过程来操作这些数据。贫血模型中的数据对象通常只包含属性,并且缺乏行为和方法。处理逻辑主要由外部的控制器函数或过程来实现。 相比之下,面向对象充血模型是一种将数据和处理逻辑封装在一起的设计方式。在这种模型中,数据被定义为对象,并且对象拥有自己的属性和方法。对象之间可以相互交互和通信,通过方法来操作和修改数据。充血模型更加注重对象的行为和方法,强调对象的内聚性和封装性。 面向对象充血模型相比于面向过程的贫血模型具有以下优点: 1. 更好的封装性和信息隐藏:对象将数据和相关操作封装在一起,外部无法直接访问和修改内部状态,提高了代码的安全性和可维护性。 2. 更好的扩展性和灵活性:通过继承、多态等特性,可以方便地扩展和修改代码,适应变化的需求。 3. 更高的可读性和可理解性:充血模型将逻辑和数据组织在一起,代码更加直观和易于理解。 4. 更好的复用性:通过对象的组合和复用,可以减少代码的重复性,提高开发效率。 然而,面向过程的贫血模型在某些场景下也有一定的优势,比如简单的算法实现或者临时脚本。选择使用哪种设计模式应该根据具体的项目需求和开发目标来决定。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值