钱包业务背景
一般具有支付、购买功能的应用,都支持钱包的功能。应用为每个用户开设一个系统内的虚拟钱包账户,支持用户重置、提现、支付、冻结、透支、转账、查询账户余额、查询交易流水。一般来讲,每个虚拟钱包账户都会对应用户的一个真实的支付账号,有可能是银行卡账户,也有可能是三方支付账户(支付宝、微信钱包)。
钱包的五大核心功能的业务
充值用户通过三方支付渠道,把自己银行卡账户内的钱,充值到虚拟钱包账户。
流程: 第一是从用户的银行卡账户转账到应用的公共银行卡账户第二是将用户的充值金额加到虚拟钱包余额上第三是记录刚刚这笔交易流水
支付
用户用钱包内的余额,支付购买应用内的商品,支付的过程就是个转账的过程。
流程: 第一 从用户的虚拟钱包账户划钱到商家的虚拟钱包账户上第二 触发真正的银行转账操作,从应用的公共银行账户转钱到商家的银行账户(注意,这儿并不是从用户的银行账户转到商家的银行账户)第三 记录这笔支付的交易流水
提现
用户可以将虚拟钱包中的余额,提现到自己的银行卡中。
流程: 第一 从用户的虚拟钱包账户金额减去提现的金额第二 触发银行转账操作,从应用的公共银行卡赚钱到用户的银行卡第三 记录这笔支付的交易流水
查询余额
用户查询虚拟钱包的余额数字即可查询交易流水
用户根据条件查询之前记录的交易即可
钱包系统的设计思路
根据上面的业务流程,我们可以把钱包系统的业务 划分为两部分
一部分单纯的跟应用内的虚拟钱包账户打交道一部分单纯的跟银行账户打交道
基于这样的业务划分,给系统解耦,将整个钱包系统拆分为两个子系统:虚拟钱包系统和三方支付系统
下面代码只聚焦于虚拟钱包系统的设计和实现。 基于贫血模型的传统开发模式
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 (pareTo(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);}}
基于充血模型的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 (pareTo(amount) < 0) {throw new InsufficientBalanceException(...);}this.balance.subtract(amount);}public void credit(BigDecimal amount) {if (pareTo(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 (pareTo(amount) < 0) {throw new InsufficientBalanceException(...);}this.balance.subtract(amount);}public void credit(BigDecimal amount) {if (pareTo(BigDecimal.ZERO) < 0) {throw new InvalidAmountException(...);}this.balance.add(amount);}}