在阅读了 Evan 和 Nilsson 的书后,我仍然不确定如何在域驱动项目中管理数据访问。CRUD 方法应该是存储库的一部分,即 OrderRepository.GetOrdersByCustomer(customer) 还是应该是实体的一部分:Customer.GetOrders()。后一种方法看起来更面向对象,但它会将单个实体类型的数据访问分配给多个对象,即 Customer.GetOrders()、Invoice.GetOrders()、ShipmentBatch.GetOrders() 等。插入和更新呢?
6 回答
CRUD-ish 方法应该是 Repository...ish 的一部分。但我认为你应该问为什么你有一堆 CRUD 方法。他们真正做什么?他们究竟是为了什么?如果您实际调用您的应用程序使用的数据访问模式,我认为它会使存储库更加有用,并且当您的域发生某些类型的更改时,您不必进行霰弹枪手术。
CustomerRepo.GetThoseWhoHaventPaidTheirBill()
// or
GetCustomer(new HaventPaidBillSpecification())
// is better than
foreach (var customer in GetCustomer()) {
/* logic leaking all over the floor */
}
“保存”类型的方法也应该是存储库的一部分。
如果您有聚合根,这可以防止您发生存储库爆炸,或者让逻辑遍布各处:您没有 4 x # 的实体数据访问模式,只有您在聚合根上实际使用的那些。
那是我的 0.02 美元。
DDD 通常更喜欢存储库模式,而不是您使用 Customer.Save 暗示的活动记录模式。
Active Record 模型的一个缺点是它几乎假定了一个单一的持久性模型,除非有一些特别侵入性的代码(在大多数语言中)。
存储库接口在域层中定义,但不知道您的数据是否存储在数据库中。例如,使用存储库模式,我可以创建一个 InMemoryRepository,以便我可以单独测试域逻辑,并在应用程序中使用依赖注入让服务层实例化一个 SqlRepository。
对很多人来说,拥有一个专门用于测试的存储库听起来很愚蠢,但是如果您使用存储库模型,您可能会发现您的特定应用程序并不真正需要数据库;有时一个简单的 FileRepository 就可以解决问题。在您知道自己需要数据库之前与自己结婚可能会受到限制。即使需要数据库,对 InMemoryRepository 运行测试也会快得多。
如果你没有太多的领域逻辑,你可能不需要 DDD。ActiveRecord 非常适合解决很多问题,特别是如果你有大部分数据和一点点逻辑。
让我们退后一步。Evans 建议存储库返回聚合根,而不仅仅是实体。因此,假设您的客户是包含订单的聚合根,那么当您从其存储库中获取客户时,订单随之而来。您将通过导航从客户到订单的关系来访问订单。
customer.Orders;
因此,为了回答您的问题,CRUD 操作存在于聚合根存储库中。
CustomerRepository.Add(customer);
CustomerRepository.Get(customerID);
CustomerRepository.Save(customer);
CustomerRepository.Delete(customer);
我已经完成了您所说的两种方式,我现在首选的方法是持久无知(或 PONO - Plain Ole' .Net Object)方法,您的域类只担心成为域类。他们不知道它们是如何被持久化的,或者即使它们被持久化了。当然,有时您必须对此保持务实,并允许使用诸如 Id 之类的东西(但即便如此,我也只是使用具有 Id 的层超类型,因此我可以有一个点,其中存在诸如默认值之类的东西)
主要原因是我努力遵循单一职责的原则。通过遵循这个原则,我发现我的代码更易于测试和维护。在需要时进行更改也容易得多,因为我只有一件事要考虑。
需要注意的一件事是存储库可能遭受的方法膨胀。GetOrderbyCustomer..GetAllOrders..GetOrders30DaysOld..等等等等。这个问题的一个很好的解决方案是查看查询对象模式。然后你的存储库可以只接受一个查询对象来执行。
我还强烈建议您研究 NHibernate 之类的东西。它包含许多使存储库如此有用的概念(身份映射、缓存、查询对象......)
即使在 DDD 中,我也会将数据访问类和例程与实体分开。
原因是,
- 可测试性提高
- 关注点分离和模块化设计
- 随着您添加实体、例程,从长远来看更易于维护
我不是专家,只是我的看法。
Nilsson 的 Applying DDD&P 令人讨厌的地方在于,他总是以“我不会在现实世界的应用程序中那样做,但是……”开头,然后他的例子就来了。回到主题:我认为 OrderRepository.GetOrdersByCustomer(customer) 是要走的路,但在 ALT.Net 邮件列表上也有讨论(http://tech.groups.yahoo.com/group/altdotnet/)关于 DDD。