MVC DDD:可以将存储库与控制器中的服务一起使用吗?

发布于 2024-09-16 02:14:17 字数 283 浏览 6 评论 0 原文

大多数时候,在服务代码中我都会有这样的内容:

public SomeService : ISomeService
{
    ISomeRepository someRepository;
    public Do(int id)
    {
        someRepository.Do(id);
    }
}

所以它有点多余

所以我开始直接在控制器中使用存储库,

这样可以吗?有没有一些架构是这样做的?

most of the time in the service code I would have something like this:

public SomeService : ISomeService
{
    ISomeRepository someRepository;
    public Do(int id)
    {
        someRepository.Do(id);
    }
}

so it's kinda redundant

so I started to use the repositories directly in the controller

is this ok ? is there some architecture that is doing like this ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

伴梦长久 2024-09-23 02:14:17

您失去了在两者之间保留业务逻辑的能力。

我不同意这一点。

如果业务逻辑位于它应该在的位置 - 在域模型中,那么在控制器中调用存储库(或者更好 - 使用模型绑定器)来获取聚合根并调用它的方法对我来说似乎完全没问题。

当涉及太多技术细节而导致控制器混乱时,应该使用应用程序服务。


我最近看到几个人提到使用模型绑定器来调用存储库。这个疯狂的想法从何而来?

我相信我们在这里讨论的是两件不同的事情。我怀疑你的“模型绑定器”意味着同时使用模型作为视图模型,并将 UI 中的更改值直接绑定回它(这本身并不是一件坏事,在某些情况下我会走那条路)。

我的“模型绑定器”是一个实现“IModelBinder 的类',它在构造函数中获取存储库(它被注入,因此 - 如果我们需要使用一些基本组合进行缓存,则可以对其进行扩展)并在调用操作之前使用它来检索聚合根并替换 int idGuid idstring slugwhatever 具有真实域对象的操作参数。将其与输入视图模型参数相结合可以让我们编写更少的代码。像这样的事情:

public ActionResult ChangeCustomerAddress
 (Customer c, ChangeCustomerAddressInput inp){
  c.ChangeCustomerAddress(inp.NewAddress);
  return RedirectToAction("Details", new{inp.Id});
}

在我的实际代码中,它有点复杂,因为它包括 ModelState 验证和一些可能从域模型内部抛出的异常处理(提取到控制器扩展方法中以供重用)。但仅此而已。到目前为止 - 最长的控制器操作约为 10 行。

您可以看到工作实现(非常复杂并且(对我来说)不必要的复杂)这里

您只是使用 Linq To Sql 执行 CRUD 应用程序还是尝试使用真正的域逻辑?

正如您(希望)看到的,这种方法实际上几乎迫使我们转向基于任务应用程序而不是基于 CRUD 的应用程序。

通过在服务层中进行所有数据访问并使用 IOC,您可以获得 AOP 的许多好处,例如不可见的缓存、事务管理以及组件的轻松组合,这是我无法想象通过模型绑定程序获得的。

...并拥有新的抽象层,邀请我们将基础设施与领域逻辑混合在一起,并失去领域模型的隔离。

请赐教。

我不确定我是否做到了。我不认为我自己开悟了。 :)


这里是我当前的模型绑定器基类。 这是我当前项目的控制器操作之一。并且这里“缺乏”业务逻辑。

You lose the ability to have business logic in between.

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've seen several people mention using model binders to call into a repo lately. Where is this crazy idea coming from?

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 or Guid id or string slug or whatever action argument with real domain object. Combining that with input view model argument lets us to write less code. Something like this:

public ActionResult ChangeCustomerAddress
 (Customer c, ChangeCustomerAddressInput inp){
  c.ChangeCustomerAddress(inp.NewAddress);
  return RedirectToAction("Details", new{inp.Id});
}

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.

Are you just doing CRUD apps with Linq To Sql or trying something with real domain logic?

As You can (hopefully) see, this kind of approach actually almost forces us to move towards task based app instead of CRUD based one.

By doing all data access in your service layer and using IOC you can gain lots of benefits of AOP like invisible caching, transaction management, and easy composition of components that I can't imagine you get with model binders.

...and having new abstraction layer that invites us to mix infrastructure with domain logic and lose isolation of domain model.

Please enlighten me.

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.

情绪 2024-09-23 02:14:17

如果您在控制器中使用存储库,您将直接从数据层进入表示层。您将失去在两者之间保留业务逻辑的能力。

现在,如果您说仅在需要业务逻辑时才使用服务,而在其他地方使用存储库,那么您的代码将成为一场噩梦。表示层现在同时调用业务层和数据层,并且您没有很好地分离关注点。

我总是会走这条路: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.

温柔戏命师 2024-09-23 02:14:17

我自己对于 DDD/MVC 的粗略实践:

  • 控制器是特定于应用程序的,因此它们应该只包含特定于应用程序的方法,并调用 Services 方法。
  • 所有公共服务方法通常都是原子事务或查询,
  • 仅服务实例化和查询调用存储库
  • 我的域定义了一个 IContextFactory 和一个 IContext (大量泄漏抽象,因为 IContext 成员是 IDBSet)
  • 每个应用程序都有一种 Composition Root,主要是实例化一个上下文工厂以传递给服务(您可以使用 DI 容器,但没什么大不了的)

这迫使我保留我的业务代码和数据-从我的控制器访问。我发现这是一个很好的纪律,考虑到当我不遵守上述规定时我是多么的松散!

My own rough practices for DDD/MVC:

  • controllers are application-specific, hence they should only contain application-specific methods, and call Services methods.
  • all public Service methods are usually atomic transactions or queries
  • only Services instantiate & call Repositories
  • my Domain defines an IContextFactory and an IContext (massive leaky abstraction as IContext members are IDBSet)
  • each application has a sort-of Composition Root, which is mainly instantiating a Context Factory to pass to the Services (you could use DI container but not a big deal)

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!

在巴黎塔顶看东京樱花 2024-09-23 02:14:17

事情是这样的。

“业务逻辑”应该驻留在您的实体和值对象中。

存储库仅处理 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?

神爱温柔 2024-09-23 02:14:17

即使使用“丰富的域模型”,您仍然需要域服务来处理涉及多个实体的业务逻辑。我也从未见过没有一些业务逻辑的 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文