你可以,不代表你应该。
(Just because you can, doesn’t mean you should.)
——施莉琳•凯尼恩
在第6章中,我们简要介绍了什么是模型、模型在软件开发中的重要性,以及一些常用的建模方式在软件工程中的应用。本章将重点讲解领域驱动设计(Domain Driven Design,DDD),包括DDD的重要概念,以及如何进行领域建模。
7.1 什么是DDD
DDD是Eric Evans在2003年出版的《领域驱动设计:软件核心复杂性应对之道》(Domain-Driven Design: Tackling Complexity in the Heart of Software)一书中提出的具有划时代意义的重要概念,是指通过统一语言、业务抽象、领域划分和领域建模等一系列手段来控制软件复杂度的方法论。
DDD的革命性在于领域驱动设计是面向对象分析的方法论,它可以利用面向对象的特性(封装、多态)有效地化解复杂性,而传统J2EE或Spring+Hibernate等事务性编程模型只关心数据。这些数据对象除了简单的setter/getter方法外,不包含任何业务逻辑,业务逻辑都是以过程式的代码写在Service中。这种方式极易上手,但随着业务的发展,系统也很容易变得混乱复杂。
7.2 初步体验DDD
在介绍DDD之前,我喜欢用这个银行转账的案例来做一个DDD和事务脚本(Transaction Script)的简单对比。我们要实现一个银行转账的功能,如果用传统的事务脚本方式实现,业务逻辑通常会被写在MoneyTransferService中,而Account仅仅是getters和setters的数据结构,也就是所谓的“贫血模式”。其代码如下所示:
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; }}
上述代码有些读者可能会比较眼熟,因为大部分系统都是这么写的。评审完需求,工程师画几张UML图完成设计,就开始像上面这样写业务代码了,这样写基本不用太动脑筋,完全是过程式的代码风格。
同样的业务逻辑,接下来看使用领域建模是怎么做的。在使用DDD之后,Account实体除账号属性之外,还包含了行为和业务逻辑,比如debit()和credit()方法。
public class Account { 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; }}
透支策略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) { }}