最近公司架构师一直在组织关于DDD的培训,也正在研读《领域驱动设计》一书,也在新项目中逐步实践,但是感觉领域驱动很抽象,其实好多项目在做的时候,我发现虽然整体的架构设计,都把领域模型单独提出来作为一个单独的模块去编写、实现,但是具体的实现内容还是相对较为简单,比如 存放一些pojo,在manger层做一些关于事务的处理,但是核心业务还是写在和service层,这样编写出来的模型,是典型的贫血模型。导致的问题是当业务越来越复杂,service层的逻辑越来越多,则越来越难读懂,今日发现一文,详细的阐述了贫血模型和领域模型的区别,俗话说,talk is cheap,show me the code,更容易帮助大家理解DDD所带来的的好处。
我要举的是一个银行转帐的例子,又是一个被用滥了的例子。但即使这个例子也不是自己想出来的,而是剽窃的《POJOs in Action》中的例子,原谅我可怜的想像力 。当钱从一个帐户转到另一个帐户时,转帐的金额不能超过第一个帐户的存款余额,余额总数不能变,钱只是从一个账户流向另一个帐户,因此它们必须在一个事务内完成,每次事务成功完成都要记录此次转帐事务,这是所有的规则。
我们首先用贫血模型来实现。所谓贫血模型就是模型对象之间存在完整的关联(可能存在多余的关联),但是对象除了get和set方外外几乎就没有其它的方 法,整个对象充当的就是一个数据容器,用C语言的话来说就是一个结构体,所有的业务方法都在一个无状态的Service类中实现,Service类仅仅包 含一些行为。这是Java Web程序采用的最常用开发模型,你可能采用的就是这种方法,虽然可能不知道它有个“贫血模型”的称号,这要多 亏Martin Flower(这个家伙惯会发明术语!)。
包结构
在讨论具体的实现之前,我们先来看来贫血模型的包结构,以便对此有个大概的了解。
贫血模型的实现一般包括如下包:
代码实现
先看model包的两个类,Account和TransferTransaction对象,分别代表帐户和一次转账事务。由于它们不包含业务逻辑,就是一个普通的Java Bean,下面的代码省略了get和set方法。
public class Account {
private String accountId;
private BigDecimal balance;
public Account() {}
public Account(String accountId, BigDecimal balance) {
this .accountId = accountId;
this .balance = balance;
}
// getter and setter ....
}
public class Account {
private String accountId;
private BigDecimal balance;
public Account() {}
public Account(String accountId, BigDecimal balance) {
this.accountId = accountId;
this.balance = balance;
}
// getter and setter ....
}
public class TransferTransaction {
private Date timestamp;
private String fromAccountId;
private String toAccountId;
private BigDecimal amount;
public TransferTransaction() {}
public TransferTransaction(String fromAccountId, String toAccountId, BigDecimal amount, Date timestamp) {
this .fromAccountId = fromAccountId;
this .toAccountId = toAccountId;
this .amount = amount;
this .timestamp = timestamp;
}
// getter and setter ....
}
public class TransferTransaction {
private Date timestamp;
private String fromAccountId;
private String toAccountId;
private BigDecimal amount;
public TransferTransaction() {}
public TransferTransaction(String fromAccountId, String toAccountId, BigDecimal amount, Date timestamp) {
this.fromAccountId = fromAccountId;
this.toAccountId = toAccountId;
this.amount = amount;
this.timestamp = timestamp;
}
// getter and setter ....
}
这两个类没什么可说的,它们就是一些数据容器。接下来看service包中TransferService接口和它的实现 TransferServiceImpl。TransferService定义了转账服务的接口,TransferServiceImpl则提供了转账服 务的实现。
public interface TransferService {
TransferTransaction transfer(String fromAccountId, String toAccountId, BigDecimal amount)
throws AccountNotExistedException, AccountUnderflowException;
}
public interface TransferService {
TransferTransaction transfer(String fromAccountId, String toAccountId, BigDecimal amount)
throws AccountNotExistedException, AccountUnderflowException;
}
public class TransferServiceImpl implements TransferService {
private AccountDAO accountDAO;
private TransferTransactionDAO transferTransactionDAO;
public TransferServiceImpl(AccountDAO accountDAO,
TransferTransactionDAO transferTransactionDAO) {
this .accountDAO = accountDAO;
this .transferTransactionDAO = transferTransactionDAO;
}
public TransferTransaction transfer(String fromAccountId, String toAccountId,
BigDecimal amount) throws AccountNotExistedException, AccountUnderflowException {
Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0 );
Account fromAccount = accountDAO.findAccount(fromAccountId);
if (fromAccount == null ) throw new AccountNotExistedException(fromAccountId);
if (fromAccount.getBalance().compareTo(amount) < 0 ) {
throw new AccountUnderflowException(fromAccount, amount);
}
Account toAccount = accountDAO.findAccount(toAccountId);
if (toAccount == null ) throw new AccountNotExistedException(toAccountId);
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountDAO.updateAccount(fromAccount); // 对Hibernate来说这不是必须的
accountDAO.updateAccount(toAccount); // 对Hibernate来说这不是必须的
return transferTransactionDAO.create(fromAccountId, toAccountId, amount);
}
}
public class TransferServiceImpl implements TransferService {
private AccountDAO accountDAO;
private TransferTransactionDAO transferTransactionDAO;
public TransferServiceImpl(AccountDAO accountDAO,
TransferTransactionDAO transferTransactionDAO) {
this.accountDAO = accountDAO;
this.transferTransactionDAO = transferTransactionDAO;
}
public TransferTransaction transfer(String fromAccountId, String toAccountId,
BigDecimal amount) throws AccountNotExistedException, AccountUnderflowException {
Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0);
Account fromAccount = accountDAO.findAccount(fromAccountId);
if (fromAccount == null) throw new AccountNotExistedException(fromAccountId);
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new AccountUnderflowException(fromAccount, amount);
}
Account toAccount = accountDAO.findAccount(toAccountId);
if (toAccount == null) throw new AccountNotExistedException(toAccountId);
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountDAO.updateAccount(fromAccount); // 对Hibernate来说这不是必须的
accountDAO.updateAccount(toAccount); // 对Hibernate来说这不是必须的
return transferTransactionDAO.create(fromAccountId, toAccountId, amount);
}
}
TransferServiceImpl类使用了AccountDAO和TranferTransactionDAO,它的transfer方法负责整个 转帐操作,它首先判断转帐的金额必须大于0,然后判断fromAccountId和toAccountId是一个存在的Account的 accountId,如果不存在抛AccountNotExsitedException。接着判断转帐的金额是否大于fromAccount的余额,如 果是则抛AccountUnderflowException。接着分别调用fromAccount和toAccount的setBalance来更新它 们的余额。最后保存到数据库并记录交易。TransferServiceImpl负责所有的业务逻辑,验证是否超额提取并更新帐户余额。一切并不复杂,对 于这个例子来说,贫血模型工作得非常好!这是因为这个例子相当简单,业务逻辑也不复杂,一旦业务逻辑变得复杂,TransferServiceImpl就 会膨胀。
优缺点
贫血模型的优点是很明显的:
其缺点为也是很明显的:
接下来看看领域驱动模型,与贫血模型相反,领域模型要承担关键业务逻辑,业务逻辑在多个领域对象之间分配,而Service只是完成一些不适合放在模型中的业务逻辑,它是非常薄的一层,它指挥多个模型对象来完成业务功能。
包结构
领域模型的实现一般包含如下包:
代码实现
现在来看实现,照例先看model中的对象:
public class Account {
private String accountId;
private BigDecimal balance;
private OverdraftPolicy overdraftPolicy = NoOverdraftPolicy.INSTANCE;
public Account() {}
public Account(String accountId, BigDecimal balance) {
Validate.notEmpty(accountId);
Validate.isTrue(balance == null || balance.compareTo(BigDecimal.ZERO) >= 0 );
this .accountId = accountId;
this .balance = balance == null ? BigDecimal.ZERO : balance;
}
public String getAccountId() {
return accountId;
}
public BigDecimal getBalance() {
return balance;
}
public void debit(BigDecimal amount) throws AccountUnderflowException {
Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0 );
if (!overdraftPolicy.isAllowed( this , amount)) {
throw new AccountUnderflowException( this , amount);
}
balance = balance.subtract(amount);
}
public void credit(BigDecimal amount) {
Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0 );
balance = balance.add(amount);
}
}
public class Account {
private String accountId;
private BigDecimal balance;
private OverdraftPolicy overdraftPolicy = NoOverdraftPolicy.INSTANCE;
public Account() {}
public Account(String accountId, BigDecimal balance) {
Validate.notEmpty(accountId);
Validate.isTrue(balance == null || balance.compareTo(BigDecimal.ZERO) >= 0);
this.accountId = accountId;
this.balance = balance == null ? BigDecimal.ZERO : balance;
}
public String getAccountId() {
return accountId;
}
public BigDecimal getBalance() {
return balance;
}
public void debit(BigDecimal amount) throws AccountUnderflowException {
Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0);
if (!overdraftPolicy.isAllowed(this, amount)) {
throw new AccountUnderflowException(this, amount);
}
balance = balance.subtract(amount);
}
public void credit(BigDecimal amount) {
Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0);
balance = balance.add(amount);
}
}
与贫血模型的区别在于Account类中包含业务方法(credit,debit),注意没有set方法,对Account的更新是通过业务方法来更新 的。由于“不允许从帐户取出大于存款余额的资金”是一条重要规则,将它放在一个单独的接口OverdraftPolicy中,也提供了灵活性,当业务规则 变化时,只需要改变这个实现就可以了。
TransferServiceImpl类:
public class TransferServiceImpl implements TransferService {
private AccountRepository accountRepository;
private TransferTransactionRepository transferTransactionRepository;
public TransferServiceImpl(AccountRepository accountRepository,
TransferTransactionRepository transferTransactionRepository) {
this .accountRepository = accountRepository;
this .transferTransactionRepository = transferTransactionRepository;
}
public TransferTransaction transfer(String fromAccountId, String toAccountId,
BigDecimal amount) throws AccountNotExistedException, AccountUnderflowException {
Account fromAccount = accountRepository.findAccount(fromAccountId);
if (fromAccount == null ) throw new AccountNotExistedException(fromAccountId);
Account toAccount = accountRepository.findAccount(toAccountId);
if (toAccount == null ) throw new AccountNotExistedException(toAccountId);
fromAccount.debit(amount);
toAccount.credit(amount);
accountRepository.updateAccount(fromAccount); // 对Hibernate来说这不是必须的
accountRepository.updateAccount(toAccount); // 对Hibernate来说这不是必须的
return transferTransactionRepository.create(fromAccountId, toAccountId, amount);
}
}
public class TransferServiceImpl implements TransferService {
private AccountRepository accountRepository;
private TransferTransactionRepository transferTransactionRepository;
public TransferServiceImpl(AccountRepository accountRepository,
TransferTransactionRepository transferTransactionRepository) {
this.accountRepository = accountRepository;
this.transferTransactionRepository = transferTransactionRepository;
}
public TransferTransaction transfer(String fromAccountId, String toAccountId,
BigDecimal amount) throws AccountNotExistedException, AccountUnderflowException {
Account fromAccount = accountRepository.findAccount(fromAccountId);
if (fromAccount == null) throw new AccountNotExistedException(fromAccountId);
Account toAccount = accountRepository.findAccount(toAccountId);
if (toAccount == null) throw new AccountNotExistedException(toAccountId);
fromAccount.debit(amount);
toAccount.credit(amount);
accountRepository.updateAccount(fromAccount); // 对Hibernate来说这不是必须的
accountRepository.updateAccount(toAccount); // 对Hibernate来说这不是必须的
return transferTransactionRepository.create(fromAccountId, toAccountId, amount);
}
}
与贫血模型中的TransferServiceImpl相比,最主要的改变在于业务逻辑被移走了,由Account类来实现。对于这样一个简单的例子,领域模型没有太多优势,但是仍然可以看到代码的实现要简单一些。当业务变得复杂之后,领域模型的优势就体现出来了。
优缺点
其优点是:
其缺点是: