将存储库接口作为参数传递给域类上的方法是否被认为是糟糕的设计?
我们的领域模型现在非常贫乏。我们的实体大多是空壳,几乎纯粹是为了保存值和导航到集合而设计的。
我们正在使用 EF 4.1 代码优先 ORM,到目前为止的设计是为了让我们的新手开发人员在早期迭代期间查询上下文时免受可怕的“LINQ to Entities 无法将 blablabla 转换为存储表达式”异常的影响。
我们通过 EF 有各种聚合根存储库接口。然而,impls 中的一些代码块似乎应该是域的责任。只要存储库接口在域中声明,并且 impl 位于基础设施中(依赖注入),将存储库接口作为参数传递给实体(或其他域)类上的方法是否被认为是糟糕的设计?
例如,这会很糟糕吗?
public class EntityAbc {
public void SaveTo(IEntityAbcRepository repos) {...}
public void DeleteFrom(IEntityAbcRepository repos) {...}
}
如果特定实体需要访问其他聚合根存储库怎么办?这可以吗?为什么?
public void Save() {
var abcRepos = DependencyInjector.Current.GetService<IEntityAbcRepository>();
var xyzRepos = DependencyInjector.Current.GetService<IEntityXyzRepository>();
// work with repositories
}
更新 1
我没有提到将代码移至应用程序层,因为我认为某些使用 IEntityAbcRepository 的代码涉及业务规则执行。存储库实现应该尽可能简单,对吗?它的主要职责应该只是对 ORM 的简单抽象,允许您查找/添加/更新/删除实体。错误的?
此外,这个问题也适用于其他非实体域类上的方法——工厂、服务,任何可能合适的模式。重点是,我问的是关于域类上的任何方法的问题,而不仅仅是实体类。 @Eranga,这是您可以使用构造函数注入的地方,因为工厂 &服务不是 ORM 的一部分。
然后,应用程序层可以通过将存储库实现注入其构造函数并将其作为参数传递给域服务或工厂来协调流程。这是不好的做法吗?
更新 2
在此添加另一项说明。如果域只需要访问 IEntityAbcRepository 即可执行其 Find() 方法怎么办?在上面的示例中,SaveTo 和DeleteFrom 方法不会调用存储库接口上的任何添加/更新/删除方法。
到目前为止,为了简单起见,我们已将查找/添加/更新/删除方法组合在单个聚合根存储库接口上。但我想没有什么可以阻止我们将它们分成 2 个接口,如下所示:
- IEntityAbcReadRepository <-- 定义所有查找方法签名
- IEntityAbcWriteRepository <-- 定义所有添加/更新/删除方法签名
在这种情况下,会不会很糟糕练习将 IEntityAbcReadRepository 作为参数传递给域方法?
Our domain model is very anemic right now. Our entities are mostly empty shells, almost purely designed for holding values and navigating to collections.
We are using EF 4.1 code-first ORM, and the design so far has been to shield our novice developers against the dreaded "LINQ to Entities cannot translate blablabla to a store expression" exception when querying against the context during early iterations.
We have various aggregate root repository interfaces over EF. However some blocks of code in the impls seems like they should be the domain's responsibility. As long as the repository interface is declared in the domain, and the impl is in the infrastructure (dependency injected), is it considered bad design to pass a repository interface as an argument to a method on an entity (or other domain) class?
For example, would this be bad?
public class EntityAbc {
public void SaveTo(IEntityAbcRepository repos) {...}
public void DeleteFrom(IEntityAbcRepository repos) {...}
}
What if a particular entity needed access to other aggregate root repositories? Would this be ok or not, and why?
public void Save() {
var abcRepos = DependencyInjector.Current.GetService<IEntityAbcRepository>();
var xyzRepos = DependencyInjector.Current.GetService<IEntityXyzRepository>();
// work with repositories
}
Update 1
I did not mention moving code to an application layer because I consider some of the code that uses IEntityAbcRepository to involve business rule enforcement. The repository impl should be as vanilla as possible, right? Its main responsibility should just be a simple abstraction over the ORM, allowing you to find / add / update / delete entities. Wrong?
Also, this question applies to methods on other non-entity domain classes -- factories, services, whatever pattern may be appropriate. Point being, I'm asking the question about any method on a domain class, not just an entity class. @Eranga, this is one place where you can use constructor injection because factories & services are not part of the ORM.
The application layer could then coordinate flow by injecting a repository impl into its constructor, and passing it as an argument to a domain service or factory. Is this bad practice?
Update 2
Adding another clarification here. What if the domain only needs access to the IEntityAbcRepository in order to execute its Find() method(s)? In the example above, the SaveTo and DeleteFrom methods would not invoke any add / update / delete methods on the repository interface.
So far we've combined the find / add / update / delete methods on a single aggregate root repository interface for simplicity. But I suppose there's nothing stopping us from separating them out into 2 interfaces, like so:
- IEntityAbcReadRepository <-- defines all find method signatures
- IEntityAbcWriteRepository <-- defines all add / update / delete method sigs
In this case, would it be bad practice to pass IEntityAbcReadRepository as a parameter to a domain method?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
与使用“服务定位器”模式的第二种方法相比,您的第一种方法更好。第一种方法中的依赖性更为明显。
以下是一些链接,解释了为什么“服务定位器”是一个糟糕的选择
...
这两个解决方案源于 EF 不允许您使用构造函数注入这一事实。但是,您可以按照本答案中的说明使用属性注入。但这并不能保证存在强制依赖关系。
所以你的第一种方法是更好的解决方案。
Your first approach is better compared to the second approach which uses "Service Locator" pattern. Dependencies are more obvious in the first approach.
Here are some links that explains why "Service Locator" is a bad choice
...
Both of these solutions stem from the fact that EF does not allow you to use constructor injection. However you can use property injection as explained in this answer. But that does not guarantee that mandatory dependencies are present.
So your first approach is the better solution.
简短回答:是的!
长答案:
考虑在应用程序服务层中创建 AbcService。该服务层位于您的域和基础设施之间。您可以根据需要向 AbcService 中注入任意数量的存储库。然后让服务处理SaveTo 和DeleteFrom。
SaveTo 和DeleteFrom,除非您要保存到另一个实体并从另一个实体中删除,即不涉及数据访问,否则这些方法听起来不应该出现在域实体上,IMO。
Short answer: Yes!
Long answer:
Consider creating an AbcService in your application service layer. This service layer sits between your domain and your infrastructure. You can inject as many repositories into AbcService as you want. Then let the service handle SaveTo and DeleteFrom.
SaveTo and DeleteFrom, unless you are saving to and deleting from another entity, i.e. no data access is involved, are methods that sound like they shouldn't be on a domain entity, IMO.
在我看来,在域实体中拥有持久性逻辑首先就是糟糕的设计。良好的关注点分离应该意味着域/业务逻辑与持久性逻辑分离,因此您的域类应该是 坚持无知。
以前的实体框架版本可能不允许这样的分离,但我认为最新版本解决了这个问题。不过我对 EF 不太熟悉,所以我可能是错的。
话虽如此,您可以在哪里放置诸如 Save() 和 Delete() 之类的方法?
如果您想在存储库中添加或删除实体,Repository.Add() 和 Repository.Remove() 是不错的选择。存储库基本上充当实体的内存集合的幻象,因此它的行为就像具有适当方法的集合或列表一样是有意义的。
如果您想保留对现有实体所做的更改,还有其他方法可以实现。您可以有一个 Repository.Save() 方法,但是 一些 认为这是不好的做法。通常,更改是在类似事务的上下文(例如工作单元)中处理的更高级别操作的一部分,在这种情况下,您可以让操作在完成时保留其范围内的所有对象。例如,如果您使用在视图中打开会话方法对于您的 Web 应用程序,请求结束时更改会自动保留。
或者,您可以依赖对特定实体的 ORM 的 Save() 方法的临时调用,希望该方法不应该移植到实体代码本身上(例如,使用 NHibernate,它可以在代理实体上运行时使用)。
[更新]
将其与您后续的问题结合起来(尽管我不确定我是否理解所有这些问题):
我认为将存储库拆分为 ReadRepository 和 WriteRepository 没有任何价值。在 DDD 中,存储库的职责显然是提供一个集合以进行查询以及添加或删除。这样还是很有凝聚力的。
实体没有责任摆弄自己的持久性,因此它不应该意识到自己的存储库用于该精确目的。否则,一个实体很少需要了解自己的存储库(通常这意味着该实体与同一类型的另一个实体有关系,例如父/子,并且您希望从存储库中获取另一个实体)存储库)
但是,实体和其他域对象显然有时确实需要获取对其他实体的引用。在这种情况下,在查找存储库之前,请首先尝试通过遍历聚合边界内的其他对象来获取这些引用。如果您绝对需要一个存储库来获取所需的对象,那么最好通过您喜欢的任何注入方式来注入存储库。正如 Eranga 指出的那样,服务定位器可能会成为一个低于标准的依赖注入替代品。
最后一件事,您提到的那种注入 - SaveTo(IEntityAbcRepository repos) - 很奇怪,因为它既不是构造函数也不是 setter 注入,而是仅持续方法时间的短暂注入。这意味着无论谁调用您的方法,都必须知道在那个特定时刻要传递哪个存储库,这并不明显。它可能很有用,但我想说这不是您通常主要使用的注入形式。
Having persistence logic in your domain entities is IMO bad design in the first place. Good separation of concerns should mean that domain/business logic is separated from persistence logic, so your domain classes should be persistence ignorant.
Previous Entity Framwork versions might not have allowed such a separation but I think most recent versions solved that problem. I'm not that familiar with EF though, so I might be wrong.
With that said, where can you put methods such as Save() and Delete() ?
If you want to add to/remove your entity from its repository, Repository.Add() and Repository.Remove() are good choices. A repository basically serves as an illusion of an in-memory collection of your entities, so it makes sense for it to behave just like a collection or a list with the appropriate methods.
If you want to persist changes made to an existing entity, there are other ways to do that. You could have a Repository.Save() method but some consider it bad practice. Oftentimes the changes are part of a higher level operation handled in a transaction-like context such as a Unit of Work, in that case you can let the operation persist all the objects in its scope when it finishes. For instance, if you use an Open Session in View approach for your web application, changes are automatically persisted when the request ends.
Or you can rely on an ad-hoc call of your ORM's Save() method for your particular entity which hopefully shouldn't be grafted onto the entity code itself (with NHibernate, for instance, it's available at runtime on the proxied entity).
[Update]
Putting that in perspective with your subsequent questions (though I'm not sure I understand all of them well) :
I see no value in splitting your repository into a ReadRepository and a WriteRepository. In DDD, a repository's responsibility is clearly to provide a collection to query from as well as add to or remove from. It's still quite cohesive that way.
It's not an entity's responsibility to fiddle with its own persistence, so it shouldn't be aware of its own repository for that precise purpose. Otherwise, it's pretty rare that an entity rightfully needs to have knowledge of its own repository (usually it means that the entity has a relationship to another entity of the same type, like parent/child, and you want to get the other entity from the repository)
However, entities and other domain objects obviously do need to obtain references to other entities at times. In that case, try to get these references through traversal of other objects within the boundary of your aggregate first before looking for a repository. If you absolutely need a repository to get the object you want, it's a good idea to inject the repository through any flavour of injection you like. As Eranga pointed out, service locator might turn out to be a sub-par dependency injection ersatz though.
Last thing, the kind of injection you mentioned - SaveTo(IEntityAbcRepository repos) - is peculiar because it is neither constructor nor setter injection, but rather an ephemeral injection lasting just the time of a method. It implies that whoever calls your method must know what repository to pass at that precise moment, which is not obvious. It might be useful, but I'd say it's not the form of injection you would typically mainly use.