MVC:业务逻辑放在哪里?

86

首先,我看到了很多类似的问题,但是没有足够的解释。如果我的问题不好或需要删除,我会理解的。

例如,我看过这个问题,有一个被投票赞同45次以上的回答说他建议将业务逻辑放在模型中,这听起来很合理。

然而,我第一个大项目中我把所有的业务逻辑都放在了控制器中,因为我没有质疑这些事情,并且看了一下AccountController是自动添加的,如果你选择MVC和表单认证。所有的方法看起来都很杂乱。或者也许这是可能添加的最少量的代码,我忽略了些什么?

一个YouTube上的人问我是否正确地将所有的逻辑都放到了他的模型中,起初我是不!然后我开始想,也许他是对的!?

那么,我的业务逻辑应该放在哪里呢? 如果是在模型类中,那么在控制器中的方法中应该考虑多少行代码才算健康?在控制器中调用模型中的某个方法并返回到视图中,最多只需一行代码吗?


1
业务逻辑放在控制器中。模型逻辑放在模型中。模型逻辑是专门处理模型的事物,如设置器/获取器/属性/添加器/删除器等。 - crush
1
@crush:我不同意。据我的阅读,“模型对象保存应用程序的数据和‘业务逻辑’”,而“控制器对象将模型和视图对象联系在一起”。 - ChiefTwoPencils
@BobbyDigital - 你能提供源链接吗? :) - Andrius Naruševičius
当然,这在《The Big Nerd Ranch Guide》中正确使用MVC的解释中有提到,但不幸的是,您必须购买该书以确认此内容。 - ChiefTwoPencils
我得说,刚刚在一本C#书里试图确认这个问题,在asp.net中业务逻辑层位于中间层,顶层是用户界面(UI),中间层是控制器(controller),底层是数据库(db)。但他们并没有特别/明确地讨论MVC。这来自于C#程序员必备 :) - ChiefTwoPencils
我很难想象像支付这样的逻辑工作流程在该模型中如何运作...另一个业务层选项是:http://blog.diatomenterprises.com/asp-net-mvc-business-logic-as-a-separate-layer/ - KCD
12个回答

58

我更喜欢将领域逻辑放在模型中,原因有几点。

  1. 模型应该没有UI代码,并且易于测试。每当可能时,我都会在编写任何UI代码之前拥有完全工作的模型(即测试覆盖率完整)。控制器可以信任模型正在做正确的事情,只需处理UI问题。

  2. 如果您将领域逻辑放在控制器中,则不容易在不同的应用程序或甚至不同的控制器之间共享。


2
是的,我真的很喜欢 #2 ,因为我发现自己很难在控制器之间共享它(不得不使用诸如静态方法之类的方法)! - Andrius Naruševičius
是的@AndriusNaruševičius,不要那样做。你应该尝试将依赖注入到控制器中,而不是依赖其他控制器。 - Mark Walsh
请注意,我认为这个答案谈到的是“领域模型”(经典MVC模式中的M部分),这与ASP.Net MVC“模型”(MVVM模式的一部分)有些不相关。 - Alexei Levenkov
1
那么,你会把依赖于多个模型的逻辑放在哪里呢? - Kellen Stuart
@KolobCanyon - 我最可能会创建一个依赖于其他模型的新模型,并在其中放置逻辑。 - Ferruccio
2
@Ferrucio那是一个糟糕的解决方案。你最终会陷入“依赖地狱”,你创建了依赖于其他对象创建的对象。这是个糟糕的解决方案,因为没有必要/未使用的数据被无缘无故地传递。你希望函数只执行他们需要完成工作的内容-否则代码很快就变得不清晰。更好的解决方案是创建业务逻辑类,只需最少的构造(或者更好的是,使用依赖注入来进行构造)。这样,你永远不会不得不获取几个不相关的对象来执行业务逻辑。 - Kellen Stuart

45

我喜欢让我的模型保持干净,即只包含属性而没有业务逻辑。我通常认为将依赖项注入到控制器中是很好的,这些依赖项包含我对模型执行的逻辑。我喜欢遵循单一职责原则,在可能的情况下尽可能贯彻这个原则。我发现具有大量方法的模型很快会变得臃肿。在注入大量依赖项方面有利有弊,虽然会增加额外开销,但允许我们进行隔离测试并使类保持简单,最终你将获得更为精简的控制器。尽管我的逻辑实际上不存在于我的模型类成员中,但它仍然属于业务逻辑。我往往不在控制器中定义业务逻辑,因为模拟像Httpcontext这样的东西有点困难且不必要。


5
完全同意。我原以为只有我喜欢将模型的责任最小化! - Lee
1
当然,这是我的登录控制器的要点 https://gist.github.com/markwalsh-liverpool/8fb361a9df0dcf034caf - Mark Walsh
1
如果您不将逻辑放在模型或控制器中,那么您应该将其放在哪里呢? - niico
1
你如何将参数传递给控制器的构造函数?控制器的初始化通常不是在幕后完成的吗? - Jeff
1
我使用依赖注入。 - Mark Walsh
显示剩余2条评论

26

业务逻辑属于问题领域,所有属于问题领域的内容都应该放在MVC的模型中。

控制器应该负责将数据从模型传递到视图,以及从视图返回到模型。因此,控制器是用户与程序交互以及程序建模和存储问题状态之间的桥梁。可以说,它是管道

关键在于业务逻辑和管道逻辑的区别。在我看来,自动生成的账户控制器所做的大部分工作都是管道逻辑,而不是真正的业务逻辑。请记住,管道逻辑不一定很短,因此您无需强制规定人为的限制(例如“控制器中最多调用X个函数”)。


1
我同意这一切。然而,我认为很多混淆来自于如何在模型中构建类的结构,特别是在使用EF时。例如:您是否使用部分类并在不同的C#文件中构建逻辑?一个文件用于EF,另一个文件用于逻辑? - S1r-Lanzelot

16
这个主题似乎存在一些混淆。大多数人倾向于将MVC模式和N层架构混淆为一种二选一的情况。事实上,这两种方法可以结合使用,但它们之间没有相互依赖性,也不是必需的。
N层架构关注将应用程序分为多个层。一个简单的例子是将应用程序分成展示层、业务逻辑层和数据访问层。
MVC是一种处理应用程序展示层的设计模式。完全可以使用MVC方法进行应用程序的设计,而不将业务逻辑和数据访问逻辑与展示层分离,从而得到单层设计。
如果你在使用MVC方法时没有同时将应用程序分层,那么结果就是Models、Views 和 Controllers 混杂着一些业务规则和数据访问逻辑。
按照定义,N层架构中只有展示层和业务逻辑层之间可以通信,因此任何 MVC 组件只能与业务逻辑层通信。
如果你正在构建不涉及展示的应用程序,即没有展示层,那么你就不必考虑MVC模式。然而,你很可能仍然将应用程序分成多个层次,从而遵循N层设计,即使没有展示层涉及。

13

我们团队从WebForms(ASP.NET)迁移到MVC时进行了大量的研究,并得出了以下结构。在我看来,这不是关于应用程序大小的问题,而是与保持代码清晰有关。

DALProject

AccountsDAL.cs --- > Calls SP or any ORM if ur using any

BLLProject

AccountsBLL.cs ---> Calls DAL

WebProject

(网络项目)
Model
    AccountsModel --- > Contains properties And call BLL
Controllers
    IndexController ---> Calls Models and returns View
Views
    Index

控制器应该负责在模型和视图之间传递数据。除此之外,不应该有任何不必要的代码。例如,如果你要记录日志,应该在模型层级别上完成,而不是控制器。


10
业务逻辑不应该放在模型视图或控制器中。应该有一个独立的业务逻辑层;这个层的唯一目的是处理你的业务逻辑。这更符合SOLID原则。
如果您将业务逻辑放在M V或C中,最终会得到难以测试/重用的代码。
把逻辑放在模型里面呢? 那是一个糟糕的解决方案。 您会陷入依赖地狱,其中对象依赖于对象。 enter image description here 即使您有一个死简单的函数,仍然必须满足所有依赖关系才能调用它。
这还会导致不必要未使用的数据被传递,没有任何理由。这也可能会影响性能,具体取决于情况有多糟糕。 我还应该提到,单元测试因为您必须模拟多个对象才能测试一个简单的函数而变得非常麻烦。 适用的干净代码原则
  1. 类/函数只需要获取完成工作所需的内容。
  2. 如果可能,函数应该只有三个或更少的参数。
  3. 智能命名类/函数/变量(遵循Microsoft的标准)。
  4. 不要将业务逻辑与模型视图或控制器耦合。

控制器

在您的控制器中,您应该能够使用依赖注入来注入业务逻辑层。确保您的控制器仅用于将信息路由到业务逻辑层。 控制器不应直接包含业务逻辑。任何验证都需要通过模型上的IValidatable进行处理。任何业务逻辑都需要路由到单独的层。


在我工作的地方,我们有一个业务层,而我只是希望我们没有它。业务层纯粹就是一团糟,逻辑应该在模型中。 - Mateus Felipe
@MateusFelipe 那么,你在哪里放置需要多个模型的逻辑(例如:Payment和Product)?您是否创建一个新模型,其中具有“Payment”和“Product”作为实例变量?您会给那个对象起什么名字?如果模型未在视图中使用,则不再是模型。它是单独层的一部分。理想情况下,您制作的类应该只从Payment和Product获取所需内容以完成其工作。如果它只需要“productName”和“price”,则应仅获取这两个参数,而不是整个对象。 - Kellen Stuart
MVC中的M通常指“应用程序模型”,这只是一种简单的说法,即“整个应用程序的其余部分”,但除非您了解其余部分,否则这并不真正有帮助。 MVC中的V传统上指用户正在查看的内容,有时该视图在“ViewModel”类中进行建模,但这仍然意味着它在架构上是视图的一部分。 C是控制器,指代码库中控制请求应用逻辑和查看结果之间流量的任何内容。 “多个模型”暴露了对MVC的基本误解。 - WhiteleyJ

9
一般而言,业务逻辑不应该驻留在MVC的任何一个组件中;它只应该被你的控制器动作所使用。正如许多人所提到的,最好创建一个库来托管业务逻辑,作为一组客户端不可知、可重用的组件。通过这种方式完成,我们极大地增加了软件的可重用性、兼容性、可扩展性和可测试性。同时,我们还减少了对某些框架特性的依赖,这使得迁移到新的/不同的技术更加容易。将业务逻辑抽象成一个独立的程序集(或多个程序集)多年来一直为我们服务。然后,我们的业务逻辑可以被几乎任何.NET技术(ASP.NET MVC/API/Core,WPF,Win Forms,WCF,UWP,WF,Console等)所使用。此外,我们喜欢我们的中间层处理业务规则和验证逻辑,以减少对.NET MVC Framework的依赖。例如,我们避免使用.NET MVC的验证帮助程序,而是依靠自己的验证程序。这是另一个因素,使我们能够轻松地从任何.NET技术中使用我们的业务逻辑。以这种逻辑方式设计我们的中间层,使我们能够轻松地实现这种物理架构:

enter image description here

它是使用Peasy.NET编写的,多年来一直为我们服务。实际上,它表现得非常好,以至于我们决定开源它。
如果有人想知道我们的中间层看起来是什么样子的,这里有一个示例,它是客户端不可知的业务层。它还展示了它被多个.NET客户端(ASP.NET MVC、Web Api和WPF)使用的情况。
希望这能帮助到某些人!

大多数情况下,没有必要重复使用逻辑。比如说,如果我决定在Web API中使用ASP.NET Core MVC,我永远不会想在WPF或WinForms中使用那个业务逻辑。因为客户端无论如何都会与服务器通信。将业务逻辑,特别是数据库访问逻辑放在客户端是不好的。 - Konrad
我认为,你添加的层数越多,就会降低可维护性和可测试性。最终,集成测试更加重要。 - Konrad

5

我一般的回答是,业务逻辑通常分为两类:

面向对象的业务逻辑:被建模为对象(在模型中),通常作为存储库进行注入。

过程式业务逻辑:放在具有接口的服务中,可以将其注入到控制器中。

控制器逻辑:控制命令如何接收并传递给处理业务逻辑的模型/服务,然后再将这些结果传递给视图。

控制器不应包含任何业务逻辑,它是设计模式中非常特定的部分,用于控制用户界面如何将输入传递给处理业务逻辑的模型(如果您的问题更加过程化,则是服务)。


2

我也喜欢保持我的模型整洁(参考:@Mark Walsh)。如果在控制器中嵌入逻辑不能重用,可以通过依赖注入轻松解决,或者,如果您认为这样做太多,可以通过接口公开业务/领域逻辑,并在控制器中使用外观模式。这样你就可以获得所需的功能,同时保持控制器和模型都很干净。


1
正如ahanusa所说,您应该将业务逻辑放入单独的DLL或单独的目录中。
我经常在与Models和Controllers相同级别的位置使用一个名为Logics的目录,其中我放置执行业务逻辑的类。
通过这种方式,我使得Models和Controllers都更加清晰。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接