是否认为域代码请求数据需要的不良做法? [英] Is it considered a bad practice for domain code to request data it needs?

查看:254
本文介绍了是否认为域代码请求数据需要的不良做法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

域实体不应包含与持久性相关的代码,因此它们应该是持久性无知 PI



域名模型 DM 感兴趣的数据可以通过域实体发送到 DM / em>导航属性或上层(即 UI层服务层)。



但是,我也假设在特定的域实体必须动态决定需要哪些数据的情况下,实体通过诸如资源库的组件请求数据。



如果这个存储库完全与持久层分离,那么我们的实体不违反 PI ,因为它仍然不知道如何获取该数据,它只知道通过从存储库请求它获取数据:

  class Customer 
{
public string InterestedWhatOtherCustomerOrdered(...)
{
..
var orders = repository.Find ...;
...
}
...
}

因此,为什么它被认为是域代码的不良做法,也可以从存储库请求其所需的数据,而不是从上层层或导航属性?即,即使根据Fowler (数据映射器的PEAA章节),也可以从数据映射器 之后的域代码所需的任何方法,然后可以使用域代码 p>

回覆塞巴斯蒂安好:



1)


这个想法是,您的域模型不应该关注这些数据来自哪里的细节




但是,如果域实体遵守PI规则,那么我们可以争辩说,他们不知道数据实际来自哪里的细节。


2)您还需要决定如何加载数据,但是您可以将
设置为应用程序服务(通常)


a)假设真实世界实体具有搜索特定的功能数据,你还会认为请求数据的域实体是有问题的(我很抱歉,我知道很难回答这样的一般性问题)?



)最重要的是,我很难理解应用程序服务层如何可能预见到域实体可能需要处理的所有不同类型的数据。



即,不会使应用层服务完全负责加载数据,这意味着,只要我们更改内部逻辑域实体(这样现在这个实体需要不同类型的数据)也意味着我们必须相应地更改应用程序服务,以便他们现在提供给实体新的数据类型而不是旧的数据?



回覆Eulerfx:



1)



a)应用程序服务不仅可以提供数据,还可以提供检索数据的机制,在最好放置用于确定数据实例的逻辑的情况下需要在域中



所以在最好放置逻辑来确定域中所需数据的确切实例的情况下,我应该封装对 S 内的存储库的访问,然后将 S 作为参数传递给域实体?因此,在我们的示例中,我应该在 ordersSelectorService 服务中封装访问 OrderRepository ,然后通过 orderSelectorService 作为参数 Customer.InterestedWhatOtherCustomerOrdered

  class Customer 
{
public string InterestedWhatOtherCustomerOrdered(OrdersSelectorService ordersSelectorService)
{
...
var orders = ordersSelectorService.Select ...;
...
}
...
}



class CustomerService
{
OrdersSelectorService orderSelectorService;
CustomerRepository customerRepository;

public void()
{
var customer = this.customerRepository.Get ...;
...

customer.InterestedWhatOtherCustomerOrdered(ordersSelectorService);
...

}
}

b)如果这确实是你所建议的,除了你已经提到的那些之外,还有其他的好处,只是将 OrderRepository 作为一个参数传递给 Customer.InterestedWhatOtherCustomerOrdered

  class Customer 
{
public string感兴趣的其他客户端(CustomerRepository orderRepository)
{
...
var orders = orderRepository.Select ...;
...
}
...
}

2)以下问题只是这样,我可以肯定我已经正确地理解了你的文章:



所以如果具体行为需要访问某些服务,使应用程序服务提供该服务的抽象,作为相应行为方法的参数。这样,在方法签名中明确地陈述了对服务的依赖。



a)通过具体行为你是指域实体(即客户)?!



b )我不太清楚你的意思是提供这个服务的抽象作为参数的应用服务。也许,而不是将服务 S 本身(即 OrderRepository )作为方法的参数(即 Customer.InterestedWhatOtherCustomerOrdered ),我们应该有一些 C (即 OrdersSelectorService )封装 S ,然后将 C 作为参数传递给方法?



c) strong> C (封装 S 的类问题))应始终是一个应用程序服务, S 应始终由 C 封装(除非 S 已经是应用程序服务)?如果是,为什么?



d)


这样,对服务的依赖是明确的在
方法签名中。


通过拥有对服务的依赖,我们获得了什么好处?在方法签名中明确说明?只有我们可以立即知道方法正在做什么,而不需要检查方法的代码? 3)有点偏离主题,但是当我们将 B 注入类 C 作为参数时,会显示出来到 M CM(B b); )的方法,那么我们不称它为依赖注入但是如果通过构造函数 setter 注入 B C B b = new B(); C c = new C(b); ),那么我们称之为一个依赖注入。为什么是这样?



第二次回复欧拉菲克斯:



1) p>


1ab)另一个选项是使用lambda而不是
OrdersSelectorService。


< blockquote>

我假设你的意思是,而不是传递给 OrdersSelectorService Customer.InterestedWhatOtherCustomerOrdered Customer.InterestedWhatOtherCustomerOrdered 中使用 Linq-to-Entities (这在很大程度上依赖于 lambda )。但是据我所知,这将违反持续无知规则(请参阅我之前的线程



2)


2c)否,C应该是一个包含所需
方法的接口。服务S可以实现该接口,或者可以在运行中提供
实现。


Aha,我错误地以为您建议 C 应该是应用程序服务。无论如何, C 应该在哪里?应该如何包装在应用程序服务程序集或域模型程序集中?



3)


2d)...声明依赖关系的好处在方法签名
而不是类的构造函数本身就是...另一个
的好处是,您的域类不需要是来自IoC容器的
依赖图的一部分 - 使事情更简单。


还不太了解IoC ,因此我必须问问域类成为IoC的依赖关系图的一部分?换句话说,这个域类必须在IoC的配置层中指定(我认为这个层仅用于指定依赖关系的接口之间的映射和一个实际的依赖关系的实现,因此我假定在这个层中甚至没有提到依赖类或者...?



4)我不是要引起任何麻烦,或者暗示你们中的一个人是错误的(你们两个都已经推理了你为什么喜欢你的设计),但我只是想成为确保我完全理解你的文章。实际上,你推荐的只是正确的,就是你所建议的同样的东西,那么我的理解能力是需要修复的:o)?



谢谢

解决方案

域对象并不糟糕要求他们需要的数据,但通常认为将存储库依赖性直接注入到实体中是不好的做法。一个原因是现在你的域对象成为依赖图的一部分,这是一个不必要的复杂性。此外,存储库通常携带环境依赖性,例如事务和工作单元。这增加了复杂性,使得对域逻辑的推理更加困难。



相反,按照Sebastian Good的说明,最好让应用程序服务提供实体需要的数据。应用程序服务是注入存储库和其他网关的好地方。应用程序服务不仅可以提供数据,还可以提供检索数据的机制,在最好放置用于确定域中所需数据的确切实例的逻辑的情况下。举个例子,看看这个问题。因此,如果特定行为需要访问某些服务,则应用程序服务将提供该服务的抽象作为相应行为方法的参数。这样,在方法签名中明确说明对服务的依赖。



更新



1ab)是的,这是正确的。另一个选择是使用lambda而不是 OrdersSelectorService 。如果lambdas不能用你的语言,那么它应该是一个接口。通过 OrderRepository 的好处基于界面隔离原则的目标是减少不必要的耦合。客户行为不太可能需要OrderRepository上的所有方法,而是需要一个特定的功能,所以要明确。



2a)是的, m指的是客户实体的行为,这只是其中一种方法。



2b)是,原因在1ab中说明。



2c)否, C 应该是一个包含所需方法的接口。服务 S 可以实现该接口,或者可以即时提供实现。



2d)是的。这是支持依赖注入服务位置的一部分。声明方法签名中的依赖关系而不是类本身的构造函数的好处是因为该服务通常仅需要一个方法,并且将其作为该类的成员是浪费的。另一个好处是,您的域类不需要是IoC容器的依赖图的一部分 - 使事情更简单。



3)我将调用依赖注入)。 DI旨在对比服务位置,其中类构造函数或方法将负责通过服务定位器获取所需的服务。



更新2



1)这是一个C#代码示例:

  //这是存储库,但它不一定是一个接口,只是一些类封装数据访问
接口IOrderRepository
{
Order Get(string id);
void添加(订单);
IEnumerable< Order> GetOrdersBySomeCriteria(SomeCriteria条件);
}

class Customer
{
//选择器参数是一个lambda。
public string InterestedWhatOtherCustomerOrdered(Func< SomeCriteria,IEnumerable< Order>> selector)
{
//做选择器lambda的东西
}
}

//这是应用程序服务
class CustomerApplicationService
{
只读IOrderRepository orderRepository;

public void DoSomething()
{
var customer = this.customerRepository.Get ...;

//应用程序服务通过lambda,后者又指向存储库。
var result = customer.InterestedWhatOtherCustomerOrdered(criteria => this.orderRepository.GetOrdersBySomeCriteria(criteria));

}
}

这不违反持久性无知和非常分离 InterestedWhatOtherCustomerOrdered 方法中的lambda参数正好指定了该方法所需要的 - 没有什么更多的。而且它不在乎如何提供这个功能,就是这样。



2)在一个lamda的情况下,C并不存在任何地方,因为它由lambda整体指定。但是,如果您使用接口,例如 IOrderSelector ,该界面需要声明客户聚合存在的位置。它可以直接由 OrderRepository 实现,或者你可以有一个适配器类。



3)我提到IoC的原因是因为另一种方法是声明对订单选择器的依赖关系客户类。然后,每当创建一个类的新实例时,需要注入该依赖(顺序选择器)。一种方法是在实例化 Customer 类的地方使用IoC容器。这是有问题的原因是因为现在您必须确保在实例化 Customer 类的任何地方都可以访问IoC容器。这也是责任的错位,因为创建客户与订单选择器无关,只有一个行为需要它。



4)哲学的区别我想。由于上述原因以及其他原因,我不希望有域对象引用存储库。总体而言,如果您浏览SO或博客等,通常会感到皱眉。这是真的,存储库接口在域中声明,但并不意味着它们应该直接从域实体引用。


Domain entities shouldn't contain code related to persistence, thus they should be Persistence Ignorant PI

Data that the domain model DM is interested in can be delivered to DM either through domain entities's navigation properties or by upper layers ( ie UI layer or service layer).

But I also assumed that in scenarios where particular domain entity must dynamically decide what data it requires, it is perfectly acceptable for that entity to request that data via component such as Repository.

If this Repository is completely decoupled from the persistence layer, then our entity is not violating PI, since it still doesn't know how it gets that data, it only knows it gets the data by requesting it from the Repository:

class Customer
{
       public string InterestedWhatOtherCustomerOrdered( ... )
       {
                ...
                var orders = repository.Find...;
                ...
        }
       ...
}

As such, why is it considered a bad practice for domain code to also be able to request the data it needs from Repository instead of just receiving it from either upper layers or from navigation properties?

Namely, even according to Fowler ( PEAA chapter on Data Mapper), it is ok to extract from Data Mapper any methods needed by the domain code into an interface class, which domain code can then use.

REPLYING TO Sebastian Good:

1)

The idea is that your domain model shouldn't be concerned with details about where that data came from.

But if domain entities adhere to PI rule, then we could argue they don't know the details about where the data actually came from.

2) You do still have to decide how to load that data, but you make your "application services" (typically) worry about it.

a) assuming the real world entity does have the functionality of searching for particular data, would you still consider the domain entity requesting data as being problematic ( I apologize, I'm aware that it's hard to answer such general questions )?

b) Most importantly, I'm having hard time understanding how application service layer could possibly foresee all the different kinds of data that domain entities could require for processing.

Namely, wouldn't having application layer services be solely responsible for loading the data mean that anytime we change the internal logic of domain entity ( such that now this entity requires different type of data ) also mean we'd have to change the application services accordingly, so that they would now supply to entity the new type of data instead of the old one?!

REPLYING TO 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

So in cases where it is better to place logic for determining the exact instance of data needed in the domain, I should encapsulate an access to a repository inside service S and then pass S as an argument to a method of a domain entity? Thus, in our example I should encapsulate an access to OrderRepository inside ordersSelectorService service and then pass ordersSelectorService as an argument to 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) If that is indeed what you are suggesting, are there any other benefits ( besides those you've already mentioned ) over simply passing OrderRepository as an argument to Customer.InterestedWhatOtherCustomerOrdered:

class Customer
{
       public string InterestedWhatOtherCustomerOrdered(CustomerRepository orderRepository)
       {
                ...
                var orders = orderRepository.Select...;
                ...
       }
       ...
}

2) Following question are just so I can be sure I've correctly understood your post in its entirecy correctly:

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) By "specific behavior" you are referring to domain entity ( ie Customer )?!

b) I'm not exactly sure what you mean by "app service providing abstraction of that service as an argument". Perhaps that instead of providing the service S itself ( ie OrderRepository ) as an argument to a method ( ie Customer.InterestedWhatOtherCustomerOrdered ), we should have some class C ( ie OrdersSelectorService ) encapsulate S and then pass C as an argument to a method?

c) I assume C ( class which encapsulates S <-- see b) question) ) should always be an application service and S should always be encapsulated by C ( unless S is already an application service )? If yes, why?

d)

This way, the dependency upon the service is explicitly stated in the method signature.

What benefits do we get by having dependency upon the service being explicitly stated in the method signature? Only that we immediately can tell what the method is doing without the need to inspect code of the method?

3) A bit off topic, but it appears when we inject behavior B into class C as an argument to a method M ( C.M(B b); ), then we don't call it dependency injection, but if instead B was injected into C via constructor or setter ( B b=new B();C c=new C(b); ), then we call it a dependency injection. Why is that?

SECOND REPLY TO Eulerfx:

1)

1ab) ... Another option is to use a lambda instead of OrdersSelectorService.

I assume you mean that instead of passing to OrdersSelectorService to Customer.InterestedWhatOtherCustomerOrdered we should instead use Linq-to-Entities ( which relies heavily on lambda ) within Customer.InterestedWhatOtherCustomerOrdered? But as far as I can tell, this would violate Persistence Ignorance rule ( see my previous thread)

2)

2c) No, C should just be an interface that contains the required method. The service S could either implement that interface, or an implementation could be provided on the fly.

Aha, I mistakenly thought that you were suggesting that C should be an Application service. Anyways, where should C live? Should it be packed inside Application Services assembly or within Domain model Assembly?

3)

2d) ... A benefit of declaring the dependency in the method signature as opposed to the constructor of the class itself is ... Another benefit is that your domain class doesn't need to be part of dependency graph from IoC container - makes things simpler.

Don't yet know much about IoC, thus I must ask how exactly does domain class become a part of an IoC's dependency graph? In other words, must this domain class be specified within IoC's configuration layer ( I thought this layer is used only to specify the mapping between the interface of a dependency and an actual implementation of a dependency, thus I assumed the dependent class isn't even mentioned inside this layer ) or...?

4) I don't mean to cause any troubles or imply one of you guys is wrong ( both of you already reasoned why you prefer your design ), but I'd just like to be sure that I understood your post completely. You are in fact recommending just the opposite of what nwang0 is suggesting ( namely, if both of you guys are recommending the same thing, then my comprehension skills are in need of some repair :o )?!

thank you

解决方案

It isn't bad practice for domain object to request data they need, however it is usually considered bad practice to inject a repository dependencies directly into entities. One reason for this is that now your domain objects become part of a dependency graph which is a needless complexity. Furthermore, a repository typically carries with it ambient dependencies such as transactions and a unit of work. This adds complexity and makes reasoning about domain logic more difficult.

Instead, as specified by Sebastian Good, it is best to have application services provide the data that an entity needs. An application service is a great place to inject repositories and other gateways. 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. For an example, take a look at this question. 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.

UPDATE

1ab) Yes that is correct. Another option is to use a lambda instead of OrdersSelectorService. If lambdas aren't available in your language then it should be an interface. The benefit over passing OrderRepository is based on the interface segregation principle the goal of which is to reduce needless coupling. It is unlikely that a behavior on Customer needs all the methods on OrderRepository, instead it needs a specific function, so make that explicit.

2a) Yes, the behavior I'm referring to is a behavior on the Customer entity, which is just one of the methods on the class.

2b) Yes for reasons stated in 1ab.

2c) No, C should just be an interface that contains the required method. The service S could either implement that interface, or an implementation could be provided on the fly.

2d) Yes. This is the part of the argument favoring dependency injection over service location. A benefit of declaring the dependency in the method signature as opposed to the constructor of the class itself is because that service is typically needed for only a single method and it is wasteful to make it an member of the class. Another benefit is that your domain class doesn't need to be part of dependency graph from IoC container - makes things simpler.

3) I would call both dependency injection (DI). DI is meant to contrast service location wherein the class constructor or method would be responsible for obtaining required services via service locator.

UPDATE 2

1) Here's a C# code sample:

// this is is the repository, but it doesn't have to be an interface, just some class encapsulating data access
interface IOrderRepository
{
  Order Get(string id);
  void Add(Order order);
  IEnumerable<Order> GetOrdersBySomeCriteria(SomeCriteria criteria);
}

class Customer
{
   // the selector parameter is a lambda.
   public string InterestedWhatOtherCustomerOrdered(Func<SomeCriteria, IEnumerable<Order>> selector)
   {
      // do stuff with selector lambda
   }
}

// this is the app service
class CustomerApplicationService
{
  readonly IOrderRepository orderRepository;

  public void DoSomething()
  {
     var customer = this.customerRepository.Get ...;

     // the app service passes lambda which in turn points to repository.
     var result = customer.InterestedWhatOtherCustomerOrdered(criteria => this.orderRepository.GetOrdersBySomeCriteria(criteria));

  }
}

This does not violate persistence ignorance and is very decoupled. The lambda parameter on the InterestedWhatOtherCustomerOrdered method specifies exactly what the method needs - nothing less nothing more. And it doesn't care how that functionality is provided, just that it is.

2) In the case of a lamda, C doesn't really exist anywhere because it is specified in its entirety by the lambda. If however you were to use an interface, for example IOrderSelector, that interface needs to be declared where the Customer aggregate exists. It could be implemented directly by the OrderRepository, or you could have an adapter class.

3) The reason I mention IoC is because another approach would be to declare the dependency on an order selector in the constructor of the Customer class. Then, whenever a new instance of the class is created, that dependency (the order selector) would need to be injected. One way to do that would be to use an IoC container in places where the Customer class is instantiated. The reason this is problematic is because now you have to make sure to have access to the IoC container wherever you instantiate the Customer class. It is also a misalignment of responsibility, since creating a customer has nothing to do with an order selector, only one behavior needs it.

4) It is a difference of philosophy I suppose. I don't like to have domain objects reference repositories for reasons stated above and other reasons as well. Overall, it is typically frowned upon if you browser around SO or blogs, etc. It is true that repository interfaces are declared in the domain, but it doesn't mean that they should be referenced from domain entities directly.

这篇关于是否认为域代码请求数据需要的不良做法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆