仓储模式的实现
仓储Repository模式已经成为最主流的模式,数据库持久化很长时间以来是一个讨论热点,目前主要问题是:主流软件并不容易有效地将需要存储的数据映射到外部存储空间如关系数据库或NoSQL数据库。
技术难点虽然已经被一些ORM工具如Hibernate等解决了,过去,我们通常使用DAO或ORM来进行业务对象和持久化数据表之间进行转换,这些技术很好,但是他们还是属于底层技术,并不能透明地和我们统一语言整合。下面的问题是如何将持久化动作和对象获取方式以及领域模型Domain Model结合起来,更进一步地说:如何更加统一我们的语言(Ubiquitous Language)(不至于出现Hibernate等底层技术语言,统一为业务语言)。
一个整合持久化技术的好办法是仓储Repositories,在Evans书籍中定义它是:一种机制,用于封装存储获取搜寻一群对象集合等行为。这个定义很容易向领域专家解释,整合到统一语言中。
第一:命名
一般人仓储类如下命名:
class OrderRepository { List<Order> getordersFor(Account a){...} } |
而Rodrigo Yoshima 认为应该如下取名:
class AllOrders { List<Order> belongingTo(Account a){...} } |
似乎是一个小的改变,但是有帮助得多,如果两个仓储包含不属于他们的方法,我们如何分辨他们呢?
//classic naming style class UserRepository{ User retrieveUserByLogin(String login){...} void submitOrder(Order order){...} } |
客户端调用代码:
User u = userRepository.retrieveUserByLogin(“pcalcado”); userRepository.submitOrder(new Order()); |
而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()); |
一定要记住:你使用的语言方式会影响你如何思考。以retrieve或get为开始方法名是坏味道。
第二:避免方法爆炸
一个好的仓储将领域概念建模在其接口中,让我们看一个业务规则:
每个订单在周末都要增加10%的附加费。
如果我们要显示所有订单,如下:
List<Order> surchargedOrders = allOrders.placed(user,IN_A_SATURDAY); surchargedOrders.addAll(allOrders.placed(user,IN_A_SUNDAY)); return surchargedOrders; |
这虽然工作很好,但是忘记了抽象,增加附加费这个规则是不应该暴露给调用客户端的,如下会更好些:
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); |
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); |
第三:只有一个类型
如下代码,虽然使用了好的命名规则
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); } |
方法返回了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); } |
如果特别情形需要,你可以创建一个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) {...} |
最后:仓储不只是持久,仓储经常用于对象存储持久化,但是这不一定是这种情况,也适合于系统整合,甚至简单内存缓存中集合返回值对象等等。
一定要记住:仓储好处是高调说明对象来自某个地方,将这些对象作为统一语言的一部分,仓储尽可能贴近我们的领域模型。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。