如何写好仓储Repository?

10-12-26 banq

How To Write A Repository

仓储Repository模式已经成为最主流的模式,数据库持久化很长时间以来是一个讨论热点,目前主要问题是:主流软件并不容易有效地将需要存储的数据映射到外部存储空间如关系数据库或NoSQL数据库。

技术难点虽然已经被一些ORM工具如Hibernate等解决了,过去,我们通常使用DAO或ORM来进行业务对象和持久化数据表之间进行转换,这些技术很好,但是他们还是属于底层技术,并不能透明地和我们统一语言整合。下面的问题是如何将持久化动作和对象获取方式以及领域模型Domain Model结合起来,更进一步地说:如何更加统一我们的语言(Ubiquitous Language)(不至于出现Hibernate等底层技术语言,统一为业务语言)。

一个整合持久化技术的好办法是仓储Repositories,在Evans书籍中定义它是:一种机制,用于封装存储 获取 搜寻一群对象集合等行为。这个定义很容易向领域专家解释,整合到统一语言中。

第一:命名

一般人仓储类如下命名:

class OrderRepository {
  List<Order> getOrdersFor(Account a){...}
}
<p>

而Rodrigo Yoshima 认为应该如下取名:

class AllOrders {
   List<Order> belongingTo(Account a){...}
}
<p>

似乎是一个小的改变,但是有帮助得多,如果两个仓储包含不属于他们的方法,我们如何分辨他们呢?

//classic naming style
class UserRepository{
 User retrieveUserByLogin(String login){...}
 void submitOrder(Order order){...}
}
<p>

客户端调用代码:

User u = userRepository.retrieveUserByLogin(“pcalcado”);
userRepository.submitOrder(new Order());
<p>

而Yoshima的取名方式如下:

//Yoshima’s naming style
class AllUsers{
 User withLogin(String login){...}
 void submitOrder(Order order){...}
}
 
//client code //非常符合英语风格,象说话一样。
User u = allusers.withLogin(“pcalcado”);
allusers.submitOrder(new Order());
<p>

一定要记住:你使用的语言方式会影响你如何思考。以retrieve或get为开始方法名是坏味道。

第二:避免方法爆炸

一个好的仓储将领域概念建模在其接口中,让我们看一个业务规则:

每个订单在周末都要增加10%的附加费。

如果我们要显示所有订单,如下:

List<Order> surchargedOrders = allOrders.placed(user, IN_A_SATURDAY);
surchargedOrders.addAll(allOrders.placed(user, IN_A_SUNDAY));
return surchargedOrders;
<p>

这虽然工作很好,但是忘记了抽象,增加附加费这个规则是不应该暴露给调用客户端的,如下会更好些:

return allOrders.surchargedFor(user);

为了实现这点,可能需要多个实体反复查询,你可以使用规格模式Specification来实现,如下:

Specification surcharged = specifications.surcharged();

return allOrders.thatAre(user, surchanged);

如果我们要获取没有附加费或者有附加费的订单,需要两个仓储AllOrders 和 SurchargedOrders.如下:

//returns all orders
return allOrders.from(user);
 
//returns only orders with applied surcharge
return surchargedOrders.from(user);
<p>

Subset Repositories子集仓储一般被建模成带有参数实例的仓储类,如下:

//a base Repository
class Users {
 private User.Status desiredStatus = null;
 
 public Users(){
   this(User.Status.ANY);
 }
 
 public Users(User.Status desiredStatus){
   this.desiredStatus= desiredStatus;
 }
 //methods go here...
}
 
//instantiated somewhere as
private Users allUsers = new Users();
private Users activeUsers = new Users(User.Status.ACTIVE);
private Users inactiveUsers = new Users(User.Status.INACTIVE);
<p>

第三:只有一个类型

如下代码,虽然使用了好的命名规则

public interface AllServices {
 
    List<Service> belongingTo(List<Account> accounts);
 
    Service withNumber(String serviceNumber);
 
    List<Service> relatedTo(Service otherService);
 
    List<Product> allActiveProductsBelongingTo(List<Account> accounts);
 
    List<Product> allProductsBelongingTo(List<Account> accounts);
 
    ContractDetails retrieveContractDetails(String serviceNumber);
}

<p>

方法返回了Service类型集合,还返回了Product集合,是多个集合,好的设计最好返回同一个类型集合:

public interface AllServices {
 
    List<Service> belongingTo(List<Account> accounts);
 
    Service withNumber(String serviceNumber);
 
    List<Service> relatedTo(Service otherService);
}
 
public interface AllProducts {
 
    List<Product> activeBelongingTo(List<Account> accounts);
 
    List<Product> belongingTo(List<Account> accounts);
}
 
public interface AllContractDetails {
   ContractDetails forServiceNumber(String serviceNumber);
}
<p>

如果特别情形需要,你可以创建一个Wrapper类,将这些方法都放入其中,如下:

public class BillingSystemGateway implements AllServices, AllProducts , AllContractDetails {
 
    List<Service> belongingTo(List<Account> accounts){...}
 
    Service withNumber(String serviceNumber) {...}
 
    List<Service> relatedTo(Service otherService) {...}
 
List<Product> activeBelongingTo(List<Account> accounts) {...}
 
List<Product> belongingTo(List<Account> accounts) {...}
 
ContractDetails forServiceNumber(String serviceNumber) {...}

<p>

最后:仓储不只是持久,仓储经常用于对象存储持久化,但是这不一定是这种情况,也适合于系统整合,甚至简单内存缓存中集合返回值对象等等。

一定要记住:仓储好处是高调说明对象来自某个地方,将这些对象作为统一语言的一部分,仓储尽可能贴近我们的领域模型。

[该贴被banq于2010-12-26 20:50修改过]

[该贴被admin于2011-01-02 09:03修改过]

                   

11
banq
2010-12-28 14:02

这篇文章很实用,很多人对仓储还没听说过,顶一下。

bloodrate
2011-01-04 23:01

一直都觉得Repository就是DAO在DDD里的另一种称呼,因为平常没用DDD的时候就是这么写DAO的,虽然不如这么严格。

cgttian
2011-01-25 23:48

2011年01月04日 23:01 "bloodrate"的内容
一直都觉得Repository就是DAO在DDD里的另一种称呼,因为平常没用DDD的时候就是这么写DAO的,虽然不如这么严格。 ...

我的理解是 DAO 是为Repository 服务的,DAO本身并不知道如何生产或组装一个领域对象 更多的是生产一些“配件”,然后在Repository中 将这些个“配件”组装成一个个对象.

不知道理解的对不对,还请大牛指导!!!

janwen
2011-01-26 15:18

没看懂,有深度了

2Go 1 2 下一页