【发布时间】:2012-10-04 18:58:21
【问题描述】:
域实体不应包含与持久性相关的代码,因此它们应该是持久性无知 PI
域模型DM感兴趣的数据可以通过域实体'传递给DM s 导航属性或由上层(即UI层或服务层)。
但我也假设在特定的域实体必须动态决定它需要什么数据的情况下,该实体通过组件请求数据是完全可以接受的,例如作为存储库。
如果这个Repository与持久层完全解耦,那么我们的entity没有违反PI,因为它仍然不知道它是如何获取数据的,它只知道它通过从 Repository 请求数据来获取数据:
class Customer
{
public string InterestedWhatOtherCustomerOrdered( ... )
{
...
var orders = repository.Find...;
...
}
...
}
因此,为什么 域代码 也能够从 Repository 请求它需要的数据而不是仅仅从任一上层接收它被认为是一种不好的做法图层或导航属性?
即,即使根据 Fowler(PEAA 关于 Data Mapper 的章节),也可以从 Data Mapper 中提取所需的任何方法将域代码转换成一个接口类,然后域代码可以使用。
回复塞巴斯蒂安·古德:
1)
这个想法是你的域模型不应该关心细节 关于这些数据的来源。
但如果域实体遵守 PI 规则,那么我们可以说他们不知道数据实际来自何处的详细信息。
2) 您仍然需要决定如何加载该数据,但是您需要 您的“应用程序服务”(通常)会担心它。
a) 假设 现实世界的实体 确实具有搜索特定数据的功能,您是否仍然认为 域实体 请求数据存在问题(我很抱歉,我知道很难回答这些一般性问题)?
b) 最重要的是,我很难理解应用程序服务层 是如何预测域实体 可能需要处理的所有不同类型的数据。
也就是说,不让应用层服务单独负责加载数据意味着我们随时更改域实体的内部逻辑(这样现在entity 需要不同类型的数据)也意味着我们必须相应地更改 应用程序服务,以便它们现在向 entity 提供新的数据类型而不是旧数据类型?!
回复 Eulerfx:
1)
a)The application service can provide not only data, but a mechanism for retrieving data as well, in cases where it is better to place logic for determining the exact instance of data needed in the domain
因此,如果最好放置逻辑来确定域中所需的确切数据实例,我应该将对 repository 的访问封装在 service S,然后将 S 作为参数传递给 域实体 的方法?因此,在我们的示例中,我应该在ordersSelectorService 服务中封装对OrderRepository 的访问,然后将ordersSelectorService 作为参数传递给Customer.InterestedWhatOtherCustomerOrdered:
class Customer
{
public string InterestedWhatOtherCustomerOrdered(OrdersSelectorService ordersSelectorService)
{
...
var orders = ordersSelectorService.Select...;
...
}
...
}
class CustomerService
{
OrdersSelectorService ordersSelectorService;
CustomerRepository customerRepository;
public void ()
{
var customer = this.customerRepository.Get...;
...
customer.InterestedWhatOtherCustomerOrdered(ordersSelectorService);
...
}
}
b) 如果这确实是您的建议,那么除了简单地将OrderRepository 作为参数传递给Customer.InterestedWhatOtherCustomerOrdered,还有其他好处(除了您已经提到的那些):
class Customer
{
public string InterestedWhatOtherCustomerOrdered(CustomerRepository orderRepository)
{
...
var orders = orderRepository.Select...;
...
}
...
}
2) 以下问题只是为了确保我完全正确理解了您的帖子:
So if a specific behavior requires access to some service, have the application service provide an abstraction of that service as an argument to the corresponding behavior method. This way, the dependency upon the service is explicitly stated in the method signature.
a) “特定行为”您指的是域实体(即Customer)?!
b) 我不确定您所说的“应用服务提供该服务的抽象作为参数”是什么意思。也许我们不应该提供 service S 本身(即OrderRepository)作为方法的参数(即Customer.InterestedWhatOtherCustomerOrdered),而是应该有一些类 C(即OrdersSelectorService)封装S,然后将C作为参数传递给方法?
c) 我假设 C (封装 S b) 问题的类)应该始终是一个 application service 和 S 应始终由 C 封装(除非 S 已经是 application service )?如果是,为什么?
d)
这样,对服务的依赖在 方法签名。
通过在方法签名中明确声明对服务的依赖,我们可以获得什么好处?只有我们可以立即知道方法在做什么而不需要检查方法的代码?
3) 有点离题,但是当我们将行为 B 注入到类 C 作为方法 M 的参数时,它就会出现( C.M(B b); ),那么我们就不叫它依赖注入,而是通过构造函数B注入C /em> 或 setter (B b=new B();C c=new C(b);),那么我们称之为依赖注入。这是为什么呢?
第二次回复 Eulerfx:
1)
1ab) ... 另一种选择是使用 lambda 而不是 OrdersSelectorService。
我假设您的意思是,我们应该使用 Linq-to-Entities (严重依赖 lambda )而不是传递给 OrdersSelectorService 到 Customer.InterestedWhatOtherCustomerOrdered Customer.InterestedWhatOtherCustomerOrdered?但据我所知,这将违反 Persistence Ignorance 规则(参见我之前的 thread)
2)
2c) 不,C 应该只是一个包含所需的接口 方法。服务 S 可以实现该接口,或者 可以即时提供实现。
啊哈,我误以为你在建议 C 应该是一个应用程序服务。无论如何,C应该住在哪里?它应该打包在 Application Services 程序集 中还是域模型程序集 中?
3)
2d) ... 在方法签名中声明依赖关系的好处 与类本身的构造函数相反的是......另一个 好处是您的域类不需要成为其中的一部分 来自 IoC 容器的依赖图 - 让事情变得更简单。
还不太了解IoC,因此我必须问一下域类究竟是如何成为IoC的依赖图的一部分 ?换句话说,这个域类必须在IoC的配置层中指定(我以为这个层只是用来指定一个依赖的接口之间的映射 和一个实际的依赖项的实现,因此我假设依赖类 甚至没有在这一层中提及)或者...?
4) 我并不是要引起任何麻烦或暗示你们中的一个人是错的(你们俩都已经解释了为什么你更喜欢你的设计),但我想确保我完全理解你的帖子.实际上,您的推荐与 nwang0 的建议正好相反(即,如果你们两个都推荐相同的东西,那么我的理解能力需要一些修复:o)?!
谢谢
【问题讨论】:
-
嗨,我认为让您的一个实体使用存储库是完全有效的,毕竟两者都是域模型的一部分。关于您在问题中所说的内容,我看不到任何部分说实体不应使用存储库。调用存储库的实体是 Persistence Ignorant,因为它不知道如何存储实体或使用哪种技术。
-
@Yves Reynhout:您能否详细说明“明确依赖关系”是什么意思?
-
隐式依赖在类外是不可发现的。 IE。它们将在类中实例化。从类外部可见显式依赖关系。通常这是使用依赖注入 (DI) 来处理的,它通常只是简单地设计接口并通过类构造函数传递您的依赖项。在您的情况下,您可能会通过构造函数传递一个
ICustomerRepository实例。如果您使用 IoC 容器,这将负责自动更新和传递这些依赖项。 -
或者只是将它传递给方法,或者让域服务承担这种依赖关系并与其他对象协作。
-
我个人不赞成从实体/域对象访问我认为是“技术资源”的东西,即使它们隐藏在接口后面。接下来你会做什么,从域模型发送一封电子邮件?写文件?毕竟,它在一个接口的后面。无论如何,您提到“导航属性”。我假设您的意思是遍历对象的依赖关系图。我只是想知道您是否可能对数据库中从实体 A 到相关事物 B 的关系进行建模。如果你可以这样做,那么你可以使用你的持久性框架来解决关联。
标签: design-patterns domain-driven-design repository-pattern