MVC DDD:可以将存储库与控制器中的服务一起使用吗?
大多数时候,在服务代码中我都会有这样的内容:
public SomeService : ISomeService
{
ISomeRepository someRepository;
public Do(int id)
{
someRepository.Do(id);
}
}
所以它有点多余
所以我开始直接在控制器中使用存储库,
这样可以吗?有没有一些架构是这样做的?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我不同意这一点。
如果业务逻辑位于它应该在的位置 - 在域模型中,那么在控制器中调用存储库(或者更好 - 使用模型绑定器)来获取聚合根并调用它的方法对我来说似乎完全没问题。
当涉及太多技术细节而导致控制器混乱时,应该使用应用程序服务。
我相信我们在这里讨论的是两件不同的事情。我怀疑你的“模型绑定器”意味着同时使用模型作为视图模型,并将 UI 中的更改值直接绑定回它(这本身并不是一件坏事,在某些情况下我会走那条路)。
我的“模型绑定器”是一个实现“IModelBinder 的类',它在构造函数中获取存储库(它被注入,因此 - 如果我们需要使用一些基本组合进行缓存,则可以对其进行扩展)并在调用操作之前使用它来检索聚合根并替换
int id 或
Guid id
或string slug
或whatever
具有真实域对象的操作参数。将其与输入视图模型参数相结合可以让我们编写更少的代码。像这样的事情:在我的实际代码中,它有点复杂,因为它包括 ModelState 验证和一些可能从域模型内部抛出的异常处理(提取到控制器扩展方法中以供重用)。但仅此而已。到目前为止 - 最长的控制器操作约为 10 行。
您可以看到工作实现(非常复杂并且(对我来说)不必要的复杂)这里。
正如您(希望)看到的,这种方法实际上几乎迫使我们转向基于任务应用程序而不是基于 CRUD 的应用程序。
...并拥有新的抽象层,邀请我们将基础设施与领域逻辑混合在一起,并失去领域模型的隔离。
我不确定我是否做到了。我不认为我自己开悟了。 :)
这里是我当前的模型绑定器基类。 这是我当前项目的控制器操作之一。并且这里“缺乏”业务逻辑。
I disagree with this one.
If business logic is where it should be - in domain model, then calling repo in controller (or better - use model binder for that) to get aggregate root and call method on it seems perfectly fine to me.
Application services should be used when there's too much technical details involved what would mess up controllers.
I believe we are talking about 2 different things here. I suspect that Your 'model binder' means using model simultaneously as a view model too and binding changed values from UI directly right back to it (which is not a bad thing per se and in some cases I would go that road).
My 'model binder' is a class that implements 'IModelBinder', that takes repository in constructor (which is injected and therefore - can be extended if we need caching with some basic composition) and uses it before action is called to retrieve aggregate root and replace
int id
orGuid id
orstring slug
orwhatever
action argument with real domain object. Combining that with input view model argument lets us to write less code. Something like this:In my actual code it's a bit more complex cause it includes ModelState validation and some exception handling that might be thrown from inside of domain model (extracted into Controller extension method for reuse). But not much more. So far - longest controller action is ~10 lines long.
You can see working implementation (quite sophisticated and (for me) unnecessary complex) here.
As You can (hopefully) see, this kind of approach actually almost forces us to move towards task based app instead of CRUD based one.
...and having new abstraction layer that invites us to mix infrastructure with domain logic and lose isolation of domain model.
I'm not sure if I did. I don't think that I'm enlightened myself. :)
Here is my current model binder base class. Here's one of controller actions from my current project. And here's "lack" of business logic.
如果您在控制器中使用存储库,您将直接从数据层进入表示层。您将失去在两者之间保留业务逻辑的能力。
现在,如果您说仅在需要业务逻辑时才使用服务,而在其他地方使用存储库,那么您的代码将成为一场噩梦。表示层现在同时调用业务层和数据层,并且您没有很好地分离关注点。
我总是会走这条路:
Repositories ->服务->用户界面
。一旦您认为不需要业务层,需求就会发生变化,您将不得不重写所有内容。If you use repositories in your controllers, you are going straight from the Data Layer to the Presentation Layer. You lose the ability to have business logic in between.
Now, if you say you will only use Services when you need business logic, and use Repositories everywhere else, your code becomes a nightmare. The Presentation Layer is now calling both the Business and Data Layer, and you don't have a nice separation of concerns.
I would always go this route:
Repositories -> Services -> UI
. As soon as you don't think you need a business layer, the requirements change, and you will have to rewrite EVERYTHING.我自己对于 DDD/MVC 的粗略实践:
这迫使我保留我的业务代码和数据-从我的控制器访问。我发现这是一个很好的纪律,考虑到当我不遵守上述规定时我是多么的松散!
My own rough practices for DDD/MVC:
This forces me to keep my business code, and data-access out of my controllers. I find it a good discipline, given how loose I am when I don't follow the above!
事情是这样的。
“业务逻辑”应该驻留在您的实体和值对象中。
存储库仅处理 AggregateRoots。因此,直接在控制器中使用存储库感觉就像将该操作视为您的“服务”。另外,由于您的 AggregateRoot 可能仅通过其 ID 引用其他 AR,因此您可能需要调用一个以上的存储库。它真的很快就会变得令人讨厌。
如果您要提供服务,请确保公开 POCO 而不是实际的 AggregateRoot 及其成员。
除了创建、检索、更新和删除内容之外,您的存储库不应执行任何操作。您可能会根据特定条件进行一些自定义检索,但仅此而已。因此,在您的存储库中拥有一种与您的服务中的方法相匹配的方法...代码气味就在那里。
您的服务是面向 API 的。想一想...如果您将该服务打包在 .dll 中供我使用,您将如何以一种易于我了解您的服务可以做什么的方式创建您的方法? Service.Update(object) 没有多大意义。
我什至还没有谈到 CQRS...事情变得更有趣。
您的 Web Api 只是您服务的客户端。您的服务可以被其他服务使用,对吗?所以,想一想。您很可能需要一个服务来封装 AggregateRoots 上的操作,通常是通过创建它们或从存储库检索它们,执行一些操作,然后返回结果。通常。
有道理吗?
Here is the thing.
"Business Logic" should reside in your entities and value objects.
Repositories deal with AggregateRoots only. So, using your repositories directly in your Controllers kinda feels like you are treating that action as your "service". Also, since your AggregateRoot may only refer to other ARs by its ID, you may have to call one more than one repo. It really gets nasty very quickly.
If you are going to have services, make sure you expose POCOs and not the actual AggregateRoot and its members.
Your repo shouldn't have to do any operations other than creating, retrieving, updating and deleting stuff. You may have some customized retrieve based on specific conditions, but that's about it. Therefore, having a method in your repo that matches one in your service... code smell right there.
Your service are API oriented. Think about this... if you were to pack that service in a .dll for me to use, how would you create your methods in a way that is easy for me to know what your service can do? Service.Update(object) doesn't make much sense.
And I haven't even talked about CQRS... where things get even more interesting.
Your Web Api is just a CLIENT of your Service. Your Service can be used by another service, right? So, think about it. You most likely will need a Service to encapsulate operations on AggregateRoots, usually by creating them, or retrieving them from a repo, do something about it, then returning a result. Usually.
Makes sense?
即使使用“丰富的域模型”,您仍然需要域服务来处理涉及多个实体的业务逻辑。我也从未见过没有一些业务逻辑的 CRUD,而是简单的示例代码。我总是想走 Martin 的路线,以保持我的代码简单。
Even with "rich domain model" you will still need a domain service for handling business logic which involves several entities. I have also never seen CRUD without some business logic, but in simple sample code. I'd always like to go Martin's route to keep my code straightforward.