很多APP当中都具有钱包的功能,为每一个用户开通一个单独的虚拟钱包账户,这个虚拟的钱包账户支持充值提现,查询账户余额,查询交易流水等功能
一般来说虚拟钱包账户都会对应的真实的金融账户,可能是一些第三方的支付(支付宝),也可能是一张银行卡,当然在这里面我们不考虑真实账户的整合,只考虑虚拟钱包账户的实现
我们主要实现的功能有以下5个,充值,支付,提现,查询余额和查询交易记录
充值主要是将真实账户中的钱转移到用户的虚拟账户当中,可以分为以下三个步骤,1.将用户的真实账户上的钱转账到我们的公用银行账户上,2.将用户的充值金额加到虚拟钱包上,3.记录如上的交易流水.
支付则是将用户的虚拟钱包账户上的钱转到商家的虚拟钱包账户上,其中也只有也会分为两步,一将用户账户上的钱转移到商家账户上,二记录本次记录
提现则是将用户虚拟钱包账户上的钱转移到自己的银行卡中,其实也就是先对用户的虚拟钱包账户进行减金额,然后从公共的银行卡上转出金额到用户私人的银行卡上.然后记住本次交易记录
查询账户余额不必说,只是简单的进行账户当中的余额查询
而查询交易也是根据交易的信息,比如说交易的时间,交易的类型,进行过滤后进行显示即可
那么我们可以将整个钱包系统分为两部分来做,1是虚拟钱包,2是真实钱包,也就是所谓的用户银行卡信息
今天我们主要做的是虚拟钱包相关的操作
简单来说,就是虚拟钱包中的余额的加加减减
充值 提现 查询 都只涉及一个虚拟钱包账户,交易涉及两个钱包账户
而接下来的交易流水,我们如何定义数据类型
我们可以按照上面的图来定义交易流水的数据格式,但是在其中我们包含两个钱包账号,一个是入账的,一个是出账的,这样的设计模式其实是有些冗余的,我们可以将交易这个类型改为两个子类型,支付和被支付,支付表示出账,被支付表是入账,这样的话就可以去掉入账钱包账号和出钱包账号这个区别,只记录一个钱包账号即可
但是我们就需要记录两条信息来表示一次转账操作了,往往会出现在一个账户上加金额成功在另一个账户上减金额失败的情况.于是我们只保证用账户的最终一致性,转账这种操作,我们可以先分别记录两个账户的加金额和减金额,
然后再加上一条包含交易类型转账的完成记录
在给两个钱包进行加减的过程中,只要有一个失败了,我们就将交易记录的状态标记为失败,然后重新进行执行或者是进行删除处理
而且为了避免虚拟钱包系统感知到具体的交易类型,我们只记录它是转入还是转出而将真正的交易类型记录在另外的一张表里面,就是交易流水表
我们可以通过查询上层钱包系统相关的流水信息去满足用户的查询,然后虚拟钱包当中的流水信息,用来保持数据的一致性
如何去实际开发一个虚拟钱包项目
基于贫血MVC开发模式开发:
贫血模型去开发一个项目,已经非常熟练了,这里我直接上代码就行了
书写了相对应的VO和Controller,以及Bo和相对应的Service,还有Repository和Entity负责数据存取
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 walletRepo.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); } } |
基于充血模型去开发一个虚拟钱包项目,则是如下代码
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 walletRepo.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) { //…跟基于贫血模型的传统开发模式的代码一样… } } |
由于Controller层和Repository层中的逻辑并不强,所以说在充血模型当中Controller层中Repository层的代码基本相同,但是在service中我们分为了Domain以及Service类进行相关的实现,我们在domain类当中移植了钱包内原有的入账出账,查询账户余额的操作,而将跟数据库也就是Repository层打交道的方法,放到了service层当中,并且将需要和另一个钱包账户进行操作的转账方法也放到了Service层中
就现在的情况来看充血模型和贫血模型,它们两者之间的区别并不是很大,并没有什么展现什么具体的优点,只是更加符合了面向对象变成的思想
但如果虚拟钱包类当中增加了新的逻辑,比如说冻结和透支这些逻辑的话,业务逻辑更还复杂,更适合充血模型,我们将数据和处理逻辑放在一起就更加符合面向对象的封装性
那么接下来我们可以思考两个问题
为什么?我们将业务逻辑移到了domain方法当中,并没有完全的移植,而是将一部分的业务逻辑仍然保留到了service层当中.
Service当中主要负责与数据库层,也就是Repository层进行交互,保证了类当中的独立性,不和其他层的代码进行耦合,并且跨领域的业务,比如说转账函数需要涉及到两个钱包的操作因此这部分的逻辑没法单独的分类的只能放了三次了如果转账业务复杂起来我们可以再抽取放到一个domain类中
接下来就是,对Service层和数据库层,是否也必有必要进行充血目前的改造呢?
但是没有必要去进行改造,因为这两个层当中包含的业务逻辑并不多,然后业务逻辑比较简单的话,就没有必要进行重新模型的改造,设计成重要模型的话,其实和面向过程的评选模型相比较来说也没有什么改变
啊,回顾一下本章,首先说了充血模型和贫血模型之间的区别,然后并且利用实际的一个小Demo来展示了充血模型和贫血模型之间的区别,也就是充血模型和贫血模型相比较,主要在于Service层,在基于充血模型的开发模式下,我们将部分处于service中的业务逻辑,存到了一个充血的图片模型当中,让所有的人都能实现依赖于这个独门类,在基于充血的模型开开发下,这辈子从并不会完全的益处,而是负责放一些不适合在对门当中的工作用用的,比如和副数据层进行打交道以及跨领域的聚合功能,幂等事物等非功能性食品工作
本章问题就是说一说对于DDD的看法