银行转账DDD案例

银行转账事务脚本实现方式

//两个账号间,转账金额计算逻辑全部在MoneyTransferService中,Account仅仅是数据载体
public class MoneyTransferServiceTransactionScriptImpl implements MoneyTransferService {
private AccountDao accountDao;
  
    private BankingTransactionRepository bankingTransactionRepository;
 

    @Override
    public BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) {
   Account fromAccount = accountDao.findById(fromAccountId);
    Account toAccount = accountDao.findById(toAccountId);
   double newBalance = fromAccount.getBalance() - amount;
    
    //是否支持透支
        switch (fromAccount.getOverdraftPolicy()) {
     case NEVER:
        if (newBalance < 0) {
          throw new DebitException("Insufficient funds");
        }
                break;
       case ALLOWED:
        if (newBalance < -limit) {
                    throw new DebitException(
                            "Overdraft limit (of " + limit + ") exceeded: " + newBalance);
         }
        break;
    }
       fromAccount.setBalance(newBalance);
        toAccount.setBalance(toAccount.getBalance() + amount);
        BankingTransaction moneyTransferTransaction =
                new MoneyTranferTransaction(fromAccountId, toAccountId, amount);
        bankingTransactionRepository.addTransaction(moneyTransferTransaction);
        return moneyTransferTransaction;
  }
}

 

银行转账DDD实现

public class Account {
  // @Id
  private String id;
  private double balance;
  private OverdraftPolicy overdraftPolicy;
  . . .
  public double balance() { return balance; }
  public void debit(double amount) {
    this.overdraftPolicy.preDebit(this, amount);
    this.balance = this.balance - amount;
    this.overdraftPolicy.postDebit(this, amount);
  }
  public void credit(double amount) {
    this.balance = this.balance + amount;
  }

 //构造器
 public Account() {
  //构造器中根据XX创建透支策略
  if(){
    this.overdraftPolicy = new NoOverdraftAllowed();
  }else{
    
this.overdraftPolicy = new XXXX();
  }
 }
}

透支策略OverdraftPolicy不仅仅是一个Enum了,而是被抽象成包含了业务规则并采用了策略模式的对象。

 

public interface OverdraftPolicy{
  void preDebit(Account account, double amount);
  void postDebit(Account account, double amount);
}

public class NoOverdraftAllowed implements OverdraftPolicy {
  public void preDebit(Account account, double amount) {
    double newBalance = account.balance() - amount;
    if (newBalance < 0) {
      throw new DebitException("Insufficient funds");
    }
  }
  public void postDebit(Account account, double amount) {
} }
public class LimitedOverdraft implements OverdraftPolicy { private double limit; public void preDebit(Account account, double amount) { double newBalance = account.balance() - amount; if (newBalance < -limit) { throw new DebitException("Overdraft limit (of " + limit + ") exceeded: " + newBalance); } } public void postDebit(Account account, double amount) {
} }

Domain Service 只需要调用相关的Entity对象完成业务

public class MoneyTransferServiceDomainModelImpl implements MoneyTransferService {
   private AccountRepository accountRepository;
   private BankingTransactionRepository  bankingTransactionRepository;

  @Override
  public BankingTransaction transfer(String fromAccountId, String toAccountId, double amount) {
    Account fromAccount = accountRepository.findById(fromAccountId);
    Account toAccount = accountRepository.findById(toAccountId);
    
    fromAccount.debit(amount);
    toAccount.credit(amount);
    BankingTransaction moneyTransferTransaction = new MoneyTranferTransaction(fromAccountId, toAccountId, amount);
    bankingTransactionRepository.addTransaction(moneyTransferTransaction);
    return moneyTransferTransaction;
  }
}

 

对比事务脚本,使用领域模型优势

面向对象

  • 封装:Account 账户相关操作,转出/传入操作封装在Account对象内,
  • 多态:策略模式的OveraftPolicy,提高代码拓展性

业务语义显性化

  • 显性化:将隐式业务逻辑从if...else中抽离出来,用通用语言去写代码,扩展,变成显性概念。举例说明,比如”透支策略“是账户Account的一种属性。使用事务脚本,其含义 

完全淹没在代码中,没有凸显出来

  • 通用语言:“一个团队,一种语言”,将模型作为语言的支柱。确保团队在内部的所有交流中,代码中,画图,写东西,特别是讲话的时候都要使用这种语言。例如账号,转账,透支策略,这些都是非常重要的领域概念,如果这些命名都和我们日常讨论以及PRD中的描述保持一致,将会极大提升代码的可读性,减少认知成本。

 

领域服务

有些领域动作,是一些动词,但是却不属于任何对象。它们代表了领域中的一个重要行为,所以不能忽略它们或简单把它们合并到某个实体或者值对象中。当这样的行为从领域中被识别出来,最佳方式是将它们声明成一个服务。它的作用是为了领域提供相应的功能。

Service通常以一个活动来命名,而不是Entity来命名。以转账为例,转账的这个行为就是一个领域概念,但是是发生在两个账号之间,归属于账号Entity并不合适,因为一个账号Entity没必要去关联他转账的账号Entity。这种情况下使用MoneyTransferDomainService更合适。

识别领域服务,主要看是否满足以下三个特征

  • 服务执行的操作代表了一个领域概念,这个领域概念无法自然的归属于一个实体或者值对象
  • 被执行的操作涉及到领域中的其他对象
  • 操作是无状态的

 

上一篇:阿里高级技术专家谈开源DDD框架:COLA4.0,分离架构和组件


下一篇:[.Net]使用Soa库+Abp搭建微服务项目框架(一):Abp与DDD相关知识回顾