域实体不应包含与持久性相关的代码,因此它们应该是Persistence Ignorant PI
领域模型 DM感兴趣的数据可以通过领域实体的导航属性或上层(即UI 层或服务层)传递给DM。
但我还假设,在特定域实体必须动态决定它需要什么数据的情况下,该实体通过诸如Repository之类的组件请求该数据是完全可以接受的。
如果这个Repository与持久层完全解耦,那么我们的实体就没有违反PI,因为它仍然不知道它是如何获取数据的,它只知道它通过从Repository请求数据来获取数据:
class Customer
{
public string InterestedWhatOtherCustomerOrdered( ... )
{
...
var orders = repository.Find...;
...
}
...
}
因此,为什么域代码也能够从存储库请求它需要的数据而不是仅仅从上层或导航属性接收它被认为是一种不好的做法?
也就是说,即使根据Fowler(关于 Data Mapper 的 PEAA 章节),也可以从Data Mapper中提取域代码所需的任何方法到接口类中,然后域代码可以使用该接口类。
回复塞巴斯蒂安·古德:
1)
这个想法是你的域模型不应该关心数据来自哪里的细节。
但是,如果域实体遵守 PI 规则,那么我们可以说他们不知道数据实际来自何处的详细信息。
2)您仍然必须决定如何加载该数据,但是您让“应用程序服务”(通常)担心它。
a)假设现实世界的实体确实具有搜索特定数据的功能,您是否仍会认为请求数据的域实体存在问题(我很抱歉,我知道很难回答这样的一般性问题)?
b) 最重要的是,我很难理解应用程序服务层如何能够预见域实体可能需要处理的所有不同类型的数据。
也就是说,不让应用层服务单独负责加载数据意味着我们随时更改域实体的内部逻辑(例如,现在该实体 需要不同类型的数据)也意味着我们必须更改应用程序服务因此,他们现在将向实体提供新类型的数据而不是旧数据?!
回复 Eulerfx:
1)
一个)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
因此,如果最好放置逻辑以确定域中所需的确切数据实例,我应该封装对服务S内的存储库的访问,然后将S作为参数传递给域实体的方法?因此,在我们的示例中,我应该封装对内部服务的访问,然后作为参数传递给: OrderRepository
ordersSelectorService
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);
...
}
}
OrderRepository
b)如果这确实是您的建议,那么除了简单地作为参数传递给之外,还有其他好处(除了您已经提到的那些)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)我不确定您所说的“应用服务提供该服务的抽象作为参数”是什么意思。也许我们不应该提供服务 S本身(即OrderRepository
)作为方法(即Customer.InterestedWhatOtherCustomerOrdered
)的参数,而应该让一些类 C(即OrdersSelectorService
)封装S,然后将C作为参数传递给方法?
c)我假设C(封装S <--参见b)问题的类)应该始终是应用程序服务,并且S应该始终由C封装(除非S已经是应用程序服务)?如果是,为什么?
d)
这样,对服务的依赖在方法签名中明确说明。
通过依赖方法签名中明确说明的服务,我们可以获得什么好处?只有我们可以立即知道方法在做什么而不需要检查方法的代码?
3) 有点跑题了,但是当我们将行为B作为方法M ( ) 的参数注入到类C中时就会出现这种情况,那么我们不称它为依赖注入,而是通过构造函数或设置器将B注入到C中( ),那么我们称之为依赖注入。这是为什么?C.M(B b);
B b=new B();C c=new C(b);
对 Eulerfx 的第二次回复:
1)
1ab) ... 另一种选择是使用 lambda 而不是 OrdersSelectorService。
我假设您的意思是,我们应该在Customer.InterestedWhatOtherCustomerOrdered中使用Linq-to-Entities(严重依赖lambda)而不是传递OrdersSelectorService
给?但据我所知,这将违反持久性无知规则(请参阅我以前的帖子)Customer.InterestedWhatOtherCustomerOrdered
2)
2c) 不,C 应该只是一个包含所需方法的接口。服务 S 可以实现该接口,也可以即时提供一个实现。
啊哈,我误以为你在建议C应该是Application service。无论如何,C应该住在哪里?它应该打包在Application Services 程序集中还是域模型程序集中?
3)
2d) ... 在方法签名中声明依赖项而不是类本身的构造函数的好处是...另一个好处是您的域类不需要成为 IoC 容器的依赖关系图的一部分 - 使得事情更简单。
对IoC还不太了解,因此我必须问一下,域类究竟是如何成为IoC 依赖图的一部分的?换句话说,是否必须在IoC 的配置层中指定此域类(我认为该层仅用于指定依赖项的接口与依赖项的实际实现之间的映射,因此我假设依赖类甚至不是在这一层内提到)或......?
4)我并不是要引起任何麻烦或暗示你们中的一个人是错的(你们俩都已经解释了为什么你喜欢你的设计),但我想确保我完全理解你的帖子。实际上,您的推荐与nwang0的建议相反(即,如果你们两个都推荐相同的东西,那么我的理解能力需要一些修复 :o )?!
谢谢你