`

贫血模式 领域模式

阅读更多

参考:http://mabusyao.iteye.com/blog/467704

 

 

 

贫血模型


我们首先用贫血模型来实现。所谓贫血模型就是模型对象之间存在完整的关联(可能存在多余的关联),但是对象除了get和set方外外几乎就没有其它的方法,整个对象充当的就是一个数据容器,用C语言的话来说就是一个结构体,所有的业务方法都在一个无状态的Service类中实现,Service类仅仅包含一些行为。这是Java Web程序采用的最常用开发模型,你可能采用的就是这种方法,虽然可能不知道它有个“贫血模型”的称号,这要多亏Martin Flower(这个家伙惯会发明术语!)。

 

包结构


在讨论具体的实现之前,我们先来看来贫血模型的包结构,以便对此有个大概的了解。
 

贫血模型的实现一般包括如下包:

  • dao:负责持久化逻辑
  • model:包含数据对象,是service操纵的对象
  • service:放置所有的服务类,其中包含了所有的业务逻辑
  • facade:提供对UI层访问的入口

代码实现


先看model包的两个类,Account和TransferTransaction对象,分别代表帐户和一次转账事务。由于它们不包含业务逻辑,就是一个普通的Java Bean,下面的代码省略了get和set方法。

Java代码
  1. public   class  Account {  
  2.     private  String accountId;  
  3.     private  BigDecimal balance;  
  4.   
  5.     public  Account() {}  
  6.     public  Account(String accountId, BigDecimal balance) {  
  7.         this .accountId = accountId;  
  8.         this .balance = balance;  
  9.     }  
  10.     // getter and setter ....   
  11.   
  12. }  
Java代码 复制代码
  1. public class Account {   
  2.     private String accountId;   
  3.     private BigDecimal balance;   
  4.   
  5.     public Account() {}   
  6.     public Account(String accountId, BigDecimal balance) {   
  7.         this.accountId = accountId;   
  8.         this.balance = balance;   
  9.     }   
  10.     // getter and setter ....   
  11.   
  12. }  
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 ....

}

 

Java代码
  1. public   class  TransferTransaction {  
  2.     private  Date timestamp;  
  3.     private  String fromAccountId;  
  4.     private  String toAccountId;  
  5.     private  BigDecimal amount;    
  6.   
  7.     public  TransferTransaction() {}  
  8.   
  9.     public  TransferTransaction(String fromAccountId, String toAccountId, BigDecimal amount, Date timestamp) {  
  10.         this .fromAccountId = fromAccountId;  
  11.         this .toAccountId = toAccountId;  
  12.         this .amount = amount;  
  13.         this .timestamp = timestamp;  
  14.     }  
  15.   
  16.     // getter and setter ....   
  17. }  
Java代码 复制代码
  1. public class TransferTransaction {   
  2.     private Date timestamp;   
  3.     private String fromAccountId;   
  4.     private String toAccountId;   
  5.     private BigDecimal amount;     
  6.   
  7.     public TransferTransaction() {}   
  8.   
  9.     public TransferTransaction(String fromAccountId, String toAccountId, BigDecimal amount, Date timestamp) {   
  10.         this.fromAccountId = fromAccountId;   
  11.         this.toAccountId = toAccountId;   
  12.         this.amount = amount;   
  13.         this.timestamp = timestamp;   
  14.     }   
  15.   
  16.     // getter and setter ....   
  17. }  
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则提供了转账服务的实现。

Java代码
  1. public   interface  TransferService {  
  2.     TransferTransaction transfer(String fromAccountId, String toAccountId, BigDecimal amount)   
  3.             throws  AccountNotExistedException, AccountUnderflowException;  
  4. }  
Java代码 复制代码
  1. public interface TransferService {   
  2.     TransferTransaction transfer(String fromAccountId, String toAccountId, BigDecimal amount)    
  3.             throws AccountNotExistedException, AccountUnderflowException;   
  4. }  
public interface TransferService {
	TransferTransaction transfer(String fromAccountId, String toAccountId, BigDecimal amount) 
			throws AccountNotExistedException, AccountUnderflowException;
}

 

Java代码
  1. public   class  TransferServiceImpl  implements  TransferService {  
  2.     private  AccountDAO accountDAO;  
  3.     private  TransferTransactionDAO transferTransactionDAO;  
  4.   
  5.     public  TransferServiceImpl(AccountDAO accountDAO,   
  6.             TransferTransactionDAO transferTransactionDAO) {  
  7.         this .accountDAO = accountDAO;  
  8.         this .transferTransactionDAO = transferTransactionDAO;  
  9.   
  10.     }  
  11.   
  12.     public  TransferTransaction transfer(String fromAccountId, String toAccountId,  
  13.             BigDecimal amount) throws  AccountNotExistedException, AccountUnderflowException {     
  14.     Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0 );        
  15.   
  16.         Account fromAccount = accountDAO.findAccount(fromAccountId);  
  17.         if  (fromAccount ==  null throw   new  AccountNotExistedException(fromAccountId);  
  18.         if  (fromAccount.getBalance().compareTo(amount) <  0 ) {  
  19.             throw   new  AccountUnderflowException(fromAccount, amount);  
  20.         }         
  21.   
  22.         Account toAccount = accountDAO.findAccount(toAccountId);  
  23.         if  (toAccount ==  null throw   new  AccountNotExistedException(toAccountId);  
  24.         fromAccount.setBalance(fromAccount.getBalance().subtract(amount));  
  25.         toAccount.setBalance(toAccount.getBalance().add(amount));                 
  26.   
  27.         accountDAO.updateAccount(fromAccount);      // 对Hibernate来说这不是必须的   
  28.         accountDAO.updateAccount(toAccount);        // 对Hibernate来说这不是必须的   
  29.         return  transferTransactionDAO.create(fromAccountId, toAccountId, amount);  
  30.     }  
  31. }  
Java代码 复制代码
  1. public class TransferServiceImpl implements TransferService {   
  2.     private AccountDAO accountDAO;   
  3.     private TransferTransactionDAO transferTransactionDAO;   
  4.   
  5.     public TransferServiceImpl(AccountDAO accountDAO,    
  6.             TransferTransactionDAO transferTransactionDAO) {   
  7.         this.accountDAO = accountDAO;   
  8.         this.transferTransactionDAO = transferTransactionDAO;   
  9.   
  10.     }   
  11.   
  12.     public TransferTransaction transfer(String fromAccountId, String toAccountId,   
  13.             BigDecimal amount) throws AccountNotExistedException, AccountUnderflowException {      
  14.     Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0);         
  15.   
  16.         Account fromAccount = accountDAO.findAccount(fromAccountId);   
  17.         if (fromAccount == nullthrow new AccountNotExistedException(fromAccountId);   
  18.         if (fromAccount.getBalance().compareTo(amount) < 0) {   
  19.             throw new AccountUnderflowException(fromAccount, amount);   
  20.         }          
  21.   
  22.         Account toAccount = accountDAO.findAccount(toAccountId);   
  23.         if (toAccount == nullthrow new AccountNotExistedException(toAccountId);   
  24.         fromAccount.setBalance(fromAccount.getBalance().subtract(amount));   
  25.         toAccount.setBalance(toAccount.getBalance().add(amount));                  
  26.   
  27.         accountDAO.updateAccount(fromAccount);      // 对Hibernate来说这不是必须的   
  28.         accountDAO.updateAccount(toAccount);        // 对Hibernate来说这不是必须的   
  29.         return transferTransactionDAO.create(fromAccountId, toAccountId, amount);   
  30.     }   
  31. }  
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就会膨胀。

 

优缺点

 

贫血模型的优点是很明显的:

  1. 被许多程序员所掌握,许多教材采用的是这种模型,对于初学者,这种模型很自然,甚至被很多人认为是java中最正统的模型。
  2. 它非常简单,对于并不复杂的业务(转帐业务),它工作得很好,开发起来非常迅速。它似乎也不需要对领域的充分了解,只要给出要实现功能的每一个步骤,就能实现它。
  3. 事务边界相当清楚,一般来说service的每个方法都可以看成一个事务,因为通常Service的每个方法对应着一个用例。(在这个例子中我使用了facade作为事务边界,后面我要讲这个是多余的)


其缺点为也是很明显的:

  1. 所有的业务都在service中处理,当业越来越复杂时,service会变得越来越庞大,最终难以理解和维护。
  2. 将所有的业务放在无状态的service中实际上是一个过程化的设计,它在组织复杂的业务存在天然的劣势,随着业务的复杂,业务会在service中多个方法间重复。
  3. 当添加一个新的UI时,很多业务逻辑得重新写。例如,当要提供Web Service的接口时,原先为Web界面提供的service就很难重用,导致重复的业务逻辑(在贫血模型的分层图中可以看得更清楚),如何保持业务逻辑一致是很大的挑战。

 


领域模型

 


接下来看看领域驱动模型,与贫血模型相反,领域模型要承担关键业务逻辑,业务逻辑在多个领域对象之间分配,而Service只是完成一些不适合放在模型中的业务逻辑,它是非常薄的一层,它指挥多个模型对象来完成业务功能。

 

包结构

 

领域模型的实现一般包含如下包:

  • infrastructure: 代表基础设施层,一般负责对象的持久化。
  • domain:代表领域层。domain包中包括两个子包,分别是model和service。model中包含模型对象,Repository(DAO)接口。它负责关键业务逻辑。service包为一系列的领域服务,之所以需要service,按照DDD的观点,是因为领域中的某些概念本质是一些行为,并且不便放入某个模型对象中。比如转帐操作,它是一个行为,并且它涉及三个对象,fromAccount,toAccount和TransferTransaction,将它放入任一个对象中都不好。
  • application: 代表应用层,它的主要提供对UI层的统一访问接口,并作为事务界限。


 

代码实现

 

现在来看实现,照例先看model中的对象:

Java代码
  1. public   class  Account {  
  2.     private  String accountId;  
  3.     private  BigDecimal balance;  
  4.       
  5.     private  OverdraftPolicy overdraftPolicy = NoOverdraftPolicy.INSTANCE;  
  6.       
  7.     public  Account() {}  
  8.       
  9.     public  Account(String accountId, BigDecimal balance) {  
  10.         Validate.notEmpty(accountId);  
  11.         Validate.isTrue(balance == null  || balance.compareTo(BigDecimal.ZERO) >=  0 );  
  12.           
  13.         this .accountId = accountId;  
  14.         this .balance = balance ==  null  ? BigDecimal.ZERO : balance;  
  15.     }  
  16.       
  17.     public  String getAccountId() {  
  18.         return  accountId;  
  19.     }  
  20.   
  21.     public  BigDecimal getBalance() {  
  22.         return  balance;  
  23.     }  
  24.       
  25.     public   void  debit(BigDecimal amount)  throws  AccountUnderflowException {  
  26.         Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0 );  
  27.           
  28.         if  (!overdraftPolicy.isAllowed( this , amount)) {  
  29.             throw   new  AccountUnderflowException( this , amount);  
  30.         }  
  31.         balance = balance.subtract(amount);  
  32.     }  
  33.       
  34.     public   void  credit(BigDecimal amount) {  
  35.         Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0 );  
  36.           
  37.         balance = balance.add(amount);  
  38.     }  
  39.       
  40. }  
Java代码 复制代码
  1. public class Account {   
  2.     private String accountId;   
  3.     private BigDecimal balance;   
  4.        
  5.     private OverdraftPolicy overdraftPolicy = NoOverdraftPolicy.INSTANCE;   
  6.        
  7.     public Account() {}   
  8.        
  9.     public Account(String accountId, BigDecimal balance) {   
  10.         Validate.notEmpty(accountId);   
  11.         Validate.isTrue(balance == null || balance.compareTo(BigDecimal.ZERO) >= 0);   
  12.            
  13.         this.accountId = accountId;   
  14.         this.balance = balance == null ? BigDecimal.ZERO : balance;   
  15.     }   
  16.        
  17.     public String getAccountId() {   
  18.         return accountId;   
  19.     }   
  20.   
  21.     public BigDecimal getBalance() {   
  22.         return balance;   
  23.     }   
  24.        
  25.     public void debit(BigDecimal amount) throws AccountUnderflowException {   
  26.         Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0);   
  27.            
  28.         if (!overdraftPolicy.isAllowed(this, amount)) {   
  29.             throw new AccountUnderflowException(this, amount);   
  30.         }   
  31.         balance = balance.subtract(amount);   
  32.     }   
  33.        
  34.     public void credit(BigDecimal amount) {   
  35.         Validate.isTrue(amount.compareTo(BigDecimal.ZERO) > 0);   
  36.            
  37.         balance = balance.add(amount);   
  38.     }   
  39.        
  40. }  
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类:

Java代码
  1. public   class  TransferServiceImpl  implements  TransferService {  
  2.     private  AccountRepository accountRepository;  
  3.     private  TransferTransactionRepository transferTransactionRepository;  
  4.       
  5.     public  TransferServiceImpl(AccountRepository accountRepository,   
  6.             TransferTransactionRepository transferTransactionRepository) {  
  7.         this .accountRepository = accountRepository;  
  8.         this .transferTransactionRepository = transferTransactionRepository;  
  9.     }  
  10.       
  11.     public  TransferTransaction transfer(String fromAccountId, String toAccountId,  
  12.             BigDecimal amount) throws  AccountNotExistedException, AccountUnderflowException {  
  13.         Account fromAccount = accountRepository.findAccount(fromAccountId);  
  14.         if  (fromAccount ==  null throw   new  AccountNotExistedException(fromAccountId);  
  15.         Account toAccount = accountRepository.findAccount(toAccountId);  
  16.         if  (toAccount ==  null throw   new  AccountNotExistedException(toAccountId);  
  17.   
  18.         fromAccount.debit(amount);  
  19.         toAccount.credit(amount);  
  20.           
  21.         accountRepository.updateAccount(fromAccount);   // 对Hibernate来说这不是必须的   
  22.         accountRepository.updateAccount(toAccount);     // 对Hibernate来说这不是必须的   
  23.         return  transferTransactionRepository.create(fromAccountId, toAccountId, amount);  
  24.     }  
  25.       
  26. }  
Java代码 复制代码
  1. public class TransferServiceImpl implements TransferService {   
  2.     private AccountRepository accountRepository;   
  3.     private TransferTransactionRepository transferTransactionRepository;   
  4.        
  5.     public TransferServiceImpl(AccountRepository accountRepository,    
  6.             TransferTransactionRepository transferTransactionRepository) {   
  7.         this.accountRepository = accountRepository;   
  8.         this.transferTransactionRepository = transferTransactionRepository;   
  9.     }   
  10.        
  11.     public TransferTransaction transfer(String fromAccountId, String toAccountId,   
  12.             BigDecimal amount) throws AccountNotExistedException, AccountUnderflowException {   
  13.         Account fromAccount = accountRepository.findAccount(fromAccountId);   
  14.         if (fromAccount == nullthrow new AccountNotExistedException(fromAccountId);   
  15.         Account toAccount = accountRepository.findAccount(toAccountId);   
  16.         if (toAccount == nullthrow new AccountNotExistedException(toAccountId);   
  17.   
  18.         fromAccount.debit(amount);   
  19.         toAccount.credit(amount);   
  20.            
  21.         accountRepository.updateAccount(fromAccount);   // 对Hibernate来说这不是必须的   
  22.         accountRepository.updateAccount(toAccount);     // 对Hibernate来说这不是必须的   
  23.         return transferTransactionRepository.create(fromAccountId, toAccountId, amount);   
  24.     }   
  25.        
  26. }  
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类来实现。对于这样一个简单的例子,领域模型没有太多优势,但是仍然可以看到代码的实现要简单一些。当业务变得复杂之后,领域模型的优势就体现出来了。

 

优缺点

 

其优点是:

  1. 领域模型采用OO设计,通过将职责分配到相应的模型对象或Service,可以很好的组织业务逻辑,当业务变得复杂时,领域模型显出巨大的优势。
  2. 当需要多个UI接口时,领域模型可以重用,并且业务逻辑只在领域层中出现,这使得很容易对多个UI接口保持业务逻辑的一致(从领域模型的分层图可以看得更清楚)。

其缺点是:

  1. 对程序员的要求较高,初学者对这种将职责分配到多个协作对象中的方式感到极不适应。
  2. 领域驱动建模要求对领域模型完整而透彻的了解,只给出一个用例的实现步骤是无法得到领域模型的,这需要和领域专家的充分讨论。错误的领域模型对项目的危害非常之大,而实现一个好的领域模型非常困难。
  3. 对于简单的软件,使用领域模型,显得有些杀鸡用牛刀了。

 

我的看法

 

 

这部分我将提出一些可能存在争议的问题并提出自己的看法。

 

软件分层


理解软件分层、明晰每层的职责对于理解领域模型以及代码实现是有好处的。软件一般分为四层,分别为表示层,应用层,领域层和基础设施层。软件领域中另外一个著名的分层是TCP/IP分层,分为应用层,运输层,网际层和网络接口层。我发现它们之间存在对应关系,见下表:

 

TCP/IP分层 软件分层
表示层 负责向用户显示信息。
应用层 负责处理特定的应用程序细节。如FTP,SMTP等协议。 应用层 定义软件可以完成的工作,指挥领域层的对象来解决问题。它不负责业务逻辑,是很薄的一层。
运输层 两台主机上的应用程序提供端到端的通信。主要包括TCP,UDP协议。 领域层 负责业务逻辑,是业务软件的核心。
网际层 处理分组在网络中的活动,例如分组的选路。主要包括IP协议。
网络接口层 操作系统中的设备驱动程序和计算机中对应的网络接口卡。它们一起处理与电缆(或其他任何传输媒介)的物理接口细节。 基础设施层 为上层提供通用技术能力,如消息发送,数据持久化等。

 

对于TCP/IP来说,运输层和网际层是最核心的,这也是TCP/IP名字的由来,就像领域层也是软件最核心的一层。可以看出领域模型的包结构与软件分层是一致的。在软件分层中,表示层、领域层和基础设施层都容易理解,难理解的是应用层,很容易和领域层中Service混淆。领域Service属于领域层,它需要承担部分业务概念,并且这个业务概念不易放入模型对象中。应用层服务不承担任何业务逻辑和业务概念,它只是调用领域层中的对象(服务和模型)来完成自己的功能。应用层为表示层提供接口,当UI接口改变一般也会导致应用层接口改变,也可能当UI接口很相似时应用层接口不用改变,但是领域层 (包括领域服务)不能变动。例如一个应用同时提供Web接口和Web Service接口时,两者的应用层接口一般不同,这是因为Web Service的接口一般要粗一些。可以和TCP/IP的层模型进行类比,开发一个FTP程序和MSN聊天程序,它们的应用层不同,但是可以同样利用 TCP/IP协议,TCP/IP协议不用变。与软件分层不同的是,当同样开发一个FTP程序时,如果只是UI接口不同,一个是命令行程序,一个是图形界面,应用层不用变(利用的都是FTP服务)。下图给出领域模型中的分层:

 

 

 

Repository接口属于领域层

 

可能有人会将Repository接口,相当于贫血模型中的DAO接口,归于基础设施层,毕竟在贫血模型中DAO是和它的实现放在一起。这就涉及 Repository 接口到底和谁比较密切?应该和domain层比较密切,因为Repository接口是由domain层来定义的。用TCP/IP来类比,网际层支持标准以太网、令牌环等网络接口,支持接口是在网际层中定义的,没有在网际层定义的网络接口是不能被网际层访问的。那么为什么在贫血模型中DAO的接口没有放在 model包中,这是因为贫血模型中DAO的接口是由service来定义的,但是为什么DAO接口也没有放在service包中,我无法解释,按照我的观点DAO接口放在service包中要更好一些,将DAO接口放在dao包或许有名称上对应的考虑。对于领域模型,将Repository接口放入 infrastructure包中会引入包的循环依赖,Repository依赖Domain,Domain依赖Repository。然而对于贫血模型,将DAO接口放入dao包中则不会引入包循环依赖,只有service对DAO和model的依赖,而没有反方向的依赖,这也导致service包很不稳定,service又正是放置业务逻辑的地方。JDepend这个工具可以检测包的依赖关系。

 

贫血模型中Facade有何用?

 

我以前的做一个项目使用的就是贫血模型,使用了service和facade,当我们讨论service和facade有什么区别时,很少有人清楚,最终结果facade就是一个空壳,它除了将方法实现委托给相应的service方法,不做任何事,它们的接口中的方法都一样。Facade应该是主要充当远程访问的门面,这在EJB时代相当普遍,自从Rod Johson叫嚷without EJB之后,大家对EJB的热情降了很多,对许多使用贫血模型的应用程序来说,facade是没有必要的。贫血模型中的service在本质上属于应用层的东西。当然如果确实需要提供远程访问,那么远程Facade(或许叫做Remote Service更好)也是很有用的,但是它仍然属于应用层,只不过在技术层面上将它的实现委托给对应的Service。下图是贫血模型的分层:

 

贫血模型分层

 

从上面的分层可以看出贫血模型实际上相当于取消掉了领域层,因为领域层并没有包含业务逻辑。

 

 

DAO到底有没有必要?

 

贫血模型中的DAO或领域模型中的Repository到底有没有必要?有人认为DAO或者说Repository是充血模型的大敌,对此我无论如何也不赞同。DAO或Repository是负责持久化逻辑的,如果取消掉DAO或Repository,将持久化逻辑直接写入到model对象中,势必造成model对象承担不必要的职责。虽然现在的ORM框架已经做得很好了,持久化逻辑还是需要大量的代码,持久化逻辑的掺入会使model中的业务逻辑变得模糊。允许去掉DAO的一个必要条件就是Java的的持久化框架必须足够先进,持久化逻辑的引入不会干扰业务逻辑,我认为这在很长一段时间内将无法做到。在rails中能够将DAO去掉的原因就是rail中实现持久化逻辑的代码很简洁直观,这也与ruby的表达能力强有关系。DAO的另外一个好处隔离数据库,这可以支持多个数据库,甚至可以支持文件存储。基于DAO的这些优点,我认为,即使将来Java的持久化框架做得足够优秀,使用DAO将持久化逻辑从业务逻辑中分离开来还是十分必要的,况且它们本身就应该分离。

 

 

 

结束语

 

在这篇文章里,我使用了一个转帐例子来描述领域模型和贫血模型的不同,实现代码可以从附件中下载,我推荐你看下附件代码,这会对领域模型和贫血模型有个更清楚的认识。我谈到了软件的分层,以及贫血模型和领域模型的实现又是怎样对应到这些层上去的,最后是对DAO(或Repository)的讨论。以上只是我个人观点,如有不同意见欢迎指出。

 

 

 

 

 

争论的焦点到了DAO上啊,呵呵。确实,实践中很多项目最终实施DDD的结果就是把所有的DAO重命名为Repository。但是我认为DAO和Repository很像,但是不是一个东西,因为它们出发点不同。

为什么要有DAO?
因为之前,很早之前,我们对于框架中立性还很受用。DAO给了我们可以随时把Hibernate换成ibatis的幻觉,所以我们要有一个地方隔离了框架。
而且DAO集中了所有的查询,方便了性能调优人员。同时也鼓励了查询的重用,同样方便了调优。

为什么要有Repository?
在我看来,与其说PublicationRepository,不如说Publications。Repository是一个集合对象,它封装了集合的逻辑。因为它具有封装性,所以它应该负责保持这个集合的状态,比如拒绝一些非法的的修改

Java代码
  1. class  PublicationRepository {  
  2.   public   void  save(Publication pub) {  
  3.    if  (hasSameName(pub)) {  
  4.      throw   new  InvalidPublicationException();  
  5.    }  
  6.    dao.save(pub);  
  7.   }  
  8. }  
Java代码 复制代码
  1. class PublicationRepository {   
  2.   public void save(Publication pub) {   
  3.    if (hasSameName(pub)) {   
  4.      throw new InvalidPublicationException();   
  5.    }   
  6.    dao.save(pub);   
  7.   }   
  8. }  
class PublicationRepository {
  public void save(Publication pub) {
   if (hasSameName(pub)) {
     throw new InvalidPublicationException();
   }
   dao.save(pub);
  }
}


另外,Repository只应该负责Aggregate Root。对于被Aggregate的对象,应该用Navigation,也就是在关系之间游走来获取。所以不是所有的查询都必须由Repository来完成,比如说:

Java代码
  1. class  Contact {  
  2.   private  List<ContactNote> contactNotes =  new  ArrayList<ContactNote>();  
  3.   public   void  contactedBy(User accountManager, DateTime time){  
  4.     ContactNote contactNote = new  ContactNote( this , accountManager, time);  
  5.     if  (isDuplicated(contactNote)) {  
  6.       throw   new  InvalidContactNote();  
  7.     }  
  8.     contactNotes.add(contactNote);  
  9.   }  
  10.   private   boolean  isDuplicated(ContactNote contactNote) {  
  11.     // 查询contactNotes   
  12.     return  xxx;  
  13.   }  
  14. }  
Java代码 复制代码
  1. class Contact {   
  2.   private List<ContactNote> contactNotes = new ArrayList<ContactNote>();   
  3.   public void contactedBy(User accountManager, DateTime time){   
  4.     ContactNote contactNote = new ContactNote(this, accountManager, time);   
  5.     if (isDuplicated(contactNote)) {   
  6.       throw new InvalidContactNote();   
  7.     }   
  8.     contactNotes.add(contactNote);   
  9.   }   
  10.   private boolean isDuplicated(ContactNote contactNote) {   
  11.     // 查询contactNotes   
  12.     return xxx;   
  13.   }   
  14. }  
class Contact {
  private List<ContactNote> contactNotes = new ArrayList<ContactNote>();
  public void contactedBy(User accountManager, DateTime time){
    ContactNote contactNote = new ContactNote(this, accountManager, time);
    if (isDuplicated(contactNote)) {
      throw new InvalidContactNote();
    }
    contactNotes.add(contactNote);
  }
  private boolean isDuplicated(ContactNote contactNote) {
    // 查询contactNotes
    return xxx;
  }
}


现状是,对象之间的关联不可查询导致了,很多这样的查询必须通过xxxDao,xxxRepository来完成。其实它们都不应该插手。

理想情况下,只有业务开始的时候用repository加载对象,在结束的时候用repository把对象存储回去,中间都是领域对象在互相作用。而DAO,可以以Generic Query Builder的形式存在,不过和它之前被发明出来的意图已经不是一个东西了。

 

DAO原本的作用就是隔离数据库的影响,没有业务逻辑。而Repository更抽象,从概念上来说是一个可以全局访问的集合,从这个意义上来讲对你所举 PublicationRepository,使用add(Publication pub)作为方法签名要更好一些。Repository也负责保持完整对象的完整性,PublicationRepository的例子也说明了这一点,另外一个例子,但从数据库重建一个对象时,由于外部原因,对象已经变得不完整,将它恢复为一个完整的对象或者直接抛异常也是Repostory的责任,它可以将这种保证对象完整性的责任委托给别的对象(如Factory)。将Repository和DAO联合起来用应该很有用,谢谢你的提醒!


至于

引用
Java代码
  1. class  Contact {  
  2.   private  List<ContactNote> contactNotes =  new  ArrayList<ContactNote>();  
  3.   public   void  contactedBy(User accountManager, DateTime time){  
  4.     ContactNote contactNote = new  ContactNote( this , accountManager, time);  
  5.     if  (isDuplicated(contactNote)) {  
  6.       throw   new  InvalidContactNote();  
  7.     }  
  8.     contactNotes.add(contactNote);  
  9.   }  
  10.   private   boolean  isDuplicated(ContactNote contactNote) {  
  11.     // 查询contactNotes   
  12.     return  xxx;  
  13.   }  
  14. }  
分享到:
评论

相关推荐

    对贫血和充血模型的理解

    贫血模型和充血模型是两种在软件开发,尤其是面向对象编程中常见的设计策略,主要应用于领域驱动设计(Domain-Driven Design, DDD)中。这两种模型主要关注于业务逻辑和数据之间的关系,以及如何在软件架构中有效地...

    领域驱动设计案例-盒马实践

    依赖注入是领域模型中的一种设计模式,它指的是在 runtime 时将对象之间的依赖关系注入到对象中。在 Spring 框架中,我们可以使用 @Autowired 注解来实现依赖注入。 测试友好 在领域模型中,我们需要考虑测试友好...

    失血贫血充血胀血模型.docx

    失血模型是一种软件设计模式,其中领域对象(Domain Object)仅包含数据字段的getter和setter方法,不包含任何业务逻辑。这种设计将业务规则和操作完全分离到独立的服务或管理类中,通常称为Transaction Script。在...

    领域模型驱动设计1553265830.pdf

    - 领域模型设计:采用充血模型而非贫血模型,并且在设计中融合设计模式、流程编排、事件驱动等元素。 - 强化单测:确保代码的质量,通过单元测试来保证各个领域模型的正确性和稳定性。 - 持续重构:在业务生命周期内...

    领域模型说明及范例代码.zip

    领域模型(Domain Model)和贫血模型(Anemic Domain Model)是两种常见的模型设计模式,它们各有特点,适用于不同的场景。本资料包旨在通过实例对比,帮助初学者理解这两种模型的区别和概念,并提供实际的Java代码...

    大白话领域驱动设计DDD视频教程

    传统模式下如何合理的划分各种域 基于DDD的方式进行域划分 什么是通用语言 什么是限界上下文? 限界上下文和子域的关系 基于电商系统按流程时间线发现限界上下文 限界上下文怎么做上下文映射? 防腐层的概念和作用 ...

    java web模式设计之道

    本书旨在帮助开发者提升在Web开发领域的专业素养,通过学习和应用各种模式,提高代码的可读性、可维护性和扩展性。标签“java”、“web”和“模式设计”揭示了本书的主要内容,即Java编程语言在Web环境下的模式应用...

    领域驱动设计中的实现方式

    在描述的实现方式中,我们关注的是一个被称为“贫血模型”的领域对象实现。 贫血模型,由 Martin Fowler 提出,是指领域对象仅仅作为数据容器,包含了属性的 getter 和 setter,但不包含任何业务逻辑。在这种模式下...

    领域驱动设计和实践

    3. **避免“贫血”模型**:“贫血”模型是指那些缺乏业务逻辑的领域对象。这样的设计会导致业务逻辑散落在各个服务或控制器中,不利于维护和扩展。 4. **灵活运用模式**:领域驱动设计中有多种模式可以使用,如聚合...

    DDD领域驱动设计day02.pdf

    领域驱动设计中的领域模型包括充血模型和贫血模型两种不同的建模方式。贫血模型主要存在于传统分层架构中,其特点是实体类中几乎没有业务逻辑,主要通过getter和setter方法来访问属性。这种模式下,业务逻辑分散在...

    WEB开发模式:Mode I与Mode II

    在IT行业中,Web开发是构建基于互联网应用程序的关键领域。这里我们关注的是两种主要的Web开发模式:Mode I和Mode II。这两种模式对于理解和优化Java Web应用程序的开发流程至关重要。 首先,让我们深入理解“Web...

    11丨实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?1

    在IT行业中,MVC(Model-View-Controller)架构是一种广泛应用的软件设计模式,尤其在Web开发中。这种模式将应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)。模型处理数据和业务逻辑...

    电信设备-地中海贫血分子流行病学调查信息数据分析方法和系统.zip

    6. 未来趋势与挑战:探讨了电信设备在地中海贫血研究领域的未来发展方向,如物联网、大数据分析、人工智能等新技术的融合,以及在数据共享、伦理法规等方面的挑战。 通过对这些内容的深入理解和实践,研究者可以更...

    基于数据挖掘的肿瘤相关性贫血中医用药规律分析.pdf

    肿瘤相关性贫血是肿瘤患者常见的并发症之一,其治疗在现代医学和传统中医领域都有研究。本研究主要利用数据挖掘技术分析近20年中医治疗肿瘤相关性贫血的用药规律,从而为临床提供参考。 首先,研究团队收集了过去20...

    领域驱动设计和开发实战

    贫血模型,即只有getter和setter方法的领域对象,往往导致业务逻辑过于集中于服务层,降低代码的可读性和可维护性。因此,投入时间和资源构建和实现领域模型对于大型企业的软件应用至关重要,因为它可以提高响应业务...

    java+ddd+领域驱动实践

    5. **贫血模型与富领域模型** - 在传统的Java应用中,模型通常很“瘦”,业务逻辑在服务层实现(贫血模型)。而在DDD中,提倡将业务逻辑放在模型本身(富领域模型),使模型更具有表现力。 6. **Repository模式** ...

    企业应用程序架构模式:基于Martin Fowler的企业应用程序架构模式的解释

    企业应用程序架构模式是软件开发领域中的重要概念,它是一系列经过实践验证的解决方案,用于解决在构建企业级应用时常见的设计挑战。这些模式由著名软件工程师Martin Fowler在他的著作《企业应用架构模式》中提出,...

    贫血

    贫血,这个词在IT行业中通常不会直接与编程语言如Java关联,但我们可以假设这是一个关于Java项目或者框架的讨论,其中“贫血”可能是指一种设计模式或系统状态。在软件开发中,“贫血模型”常用于描述业务对象(BO)...

    论文研究 - 刚果民主共和国金夏沙稳定型镰状细胞病患者的血压模式和与相对高血压相关的因素:跨领域研究

    目的:确定BP模式的患病率并评估与相对高血压镰状细胞性贫血(SCA)成年患者相关的因素。 方法:从在金沙萨的四个提供SCD特异性护理的医疗中心连续103名稳态SCA成年患者(平均年龄26±7.9岁,女性66%,羟基脲22.3...

    基于GO的六边形架构框架,可支撑充血的领域模型范式代码实现.rar

    在传统的贫血模型中,领域对象通常只包含数据,而业务逻辑则分散在服务层或其他地方。然而,在充血模型中,领域对象不仅包含了数据,还封装了大量的业务逻辑。这种方法使得领域模型更加生动且有力量,因为它们可以...

Global site tag (gtag.js) - Google Analytics