模型臃肿/控制器单薄 vs 服务层

90
我多年来一直使用 .Net 开发企业应用程序。 我的应用程序通常具有包含映射到 SQL 数据库表的实体的领域模型。 我使用存储库模式、依赖注入和服务层。
最近我们开始开发 MVC 3 项目,并且就该将哪些逻辑放在哪里进行了辩论。 我接触到了“瘦控制器/厚模型”架构,并想知道服务层应该如何适配。
选项1-模型与服务交互 控制器很薄,调用模型上的方法。模型“知道”如何从 DB 加载自身并与存储库或服务交互。例如,customerModel 有一个 Load(id) 方法,并加载客户和一些子对象,比如 GetContracts()。
选项2-控制器与服务交互 控制器请求服务检索模型对象。加载/存储逻辑等都在服务层中。模型是仅具有数据的纯实体模型。
为什么选项1会是更好的选择,特别是当我们谈论企业应用程序时,我的经验告诉我要分离关注点,尽量使模型和控制器变得轻巧,让专门的服务执行业务逻辑(包括 DB 交互)。
感谢所有建议和推荐好资源。
4个回答

96
  • Receive input from the user via the View and pass that data onto the Service Layer for processing.
  • Receive results from the Service Layer and delegate rendering of the View to the appropriate view engine (Razor, AngularJS, etc.).
  • 调用与实体/领域模型进行交互的服务。

  • 为适当的视图准备视图模型。

  • 即使是经过身份验证/授权的控制器操作也是通过注入的服务/属性完成的。


    编辑1:

    请记住,这并不意味着您的实体/领域模型是或必须是贫血的(anemic)。 ORM、存储库和工厂、验证或状态机制都是可以接受的。它只意味着对于中等规模的应用程序,MVC中的模型代表控制器要交给视图使用的模型

    希望这一点能平息那些认为贫血数据模型是一种反面模式的Fowler门徒们。同时,它确实反映了比OOP稍微更加过程化的角度,在其中将行为包含在建模类中更加纯粹。

    没有"终极真理",但使用这种模式,您会发现很容易构建、测试和部署您的应用程序-同时保持大量的可重用性和可扩展性。


    编辑2:

    尽管如此,即使对于规模适中的应用程序,过度设计(这是单词“字典迷”们编出来的吗?)系统也太常见了。例如,将ORM与存储库模式包装起来,然后编写服务来使用存储库...所有这些都有益于关注点分离等,但如果您的项目不需要(并且不太可能很快需要)这些东西,请不要构建它。完全跳过存储库,在ORM上编写轻量级业务服务(例如查询类),甚至让您的控制器直接与它交互,都没有问题。一切取决于规模。


    编辑3:

    我想指出,这个解释和建议是针对服务器端MVC架构(如ASP.Net),而不是针对Knockout或Backbone等客户端框架。


    12
    除了控制器不知道仓库的信息外,这个设计模式与我使用的几乎完全相同。控制器只与服务进行交互,而服务再与仓库进行交互。 - Lester
    2
    @Lester,我进行了编辑以澄清这一点。我的应用程序95%的时间也不会使用IoC容器,但是这个想法是服务会使用它。对于小型应用程序来说,这可能有些过度,但这是一个好的实践方法,并且使用IoC容器更容易维护。 - one.beat.consumer
    1
    +1 @one.beat.consumer:这是我在项目中采取的相同方法...有时对规则过于纯粹会导致过于复杂的解决方案,而你可以从一个真实世界证明有效的解决方案中获得更多的好处,即使它并不完全遵循GOF模式。 - themarcuz
    7
    在MVC中,模型是指您的控制器准备并传递给视图的任何数据模型。这就是为什么您的“应用程序模型”(领域模型,模型层,无论您如何标记它)可以完全不知道MVC库,甚至存在于单独分布的系统上。在MVC中,请求被简单地路由到控制器。控制器组装一个视图模型(呈现层的数据)。如果该模型是您在持久性机制中使用的相同实例化对象,则这可能是不良实践,但允许,这意味着没有专属定义。 - one.beat.consumer
    2
    +1 的提示:请记住,MVC 模式中的“Model”最准确地指的是控制器知道的模型,因此它也是供视图使用的模型。 - Luiz Damim
    显示剩余2条评论

    17
    在讨论放置位置之前,您需要了解更多关于MVC的知识。如果您想遵循该模式,否则您可以停止阅读现在。
    该模式的定义非常宽松。没有任何规定控制器、视图或模型应该是什么样子或如何结构化。该模式只是说明您应该将部分分离并定义它们之间的交互方式。因此,让我们再看看它们具体是什么(我的解释)。
    MVC
    Model 模型可以是任何东西。它可以是Web服务、存储库、服务类或者仅仅是您的域模型。模型包括获取所需信息的所有内容。请将“模型”视为一个层而不仅仅是单个对象。
    Controller 控制器是一种粘合剂。它从模型中获取信息并将其适配到视图和反之。
    View 视图只应呈现用户所看到的内容。
    请注意,您不应将Model与View Model混淆。Microsoft实际上应该将“Model”文件夹命名为“ViewModels”,因为这就是它们的作用。我不会直接使用“Model”中的信息来编写视图。不遵循此原则将意味着如果更改视图,则必须更改模型,反之亦然。
    答案
    模型不是视图模型而是层。模型中的所有内容都用于获取视图所需的信息。控制器将该信息放入单个视图模型中。
    单个控制器操作可能会使用一个或多个对“Model”的调用,以便能够组装视图所需的信息。
    这意味着如果您想要获得一个易于维护和扩展的应用程序,则第二个选项是最正确的。
    请注意,并不总是需要服务层。您可以直接从控制器调用ORM。但是,如果发现自己在重复编写代码或是变得臃肿,请将逻辑移动到服务层。只有控制器会受到该更改的影响,因为您正在使用适当的视图模型。

    3
    我希望 ASP.NET MVC 被称为 ASP.NET Model-View-Controller,这个名字虽然不太好听,但至少能传达其真正的含义 :) - Hector Correa
    我认为我回答了这个问题。在我看来,他在问题中提到的customerModel是一个视图模型。如果他理解这一点,那么答案就更明显了。 - jgauffin
    2
    @jgauffin 语义在这里非常重要 - 在MVC中,“model”并不意味着“模型层”;它只意味着Controller传递给View的一种合适的模型对象。在大型应用程序中,MVC架构通常甚至不知道模型/数据层或您选择称之为什么。我的编辑答案试图解释这个混淆...主要是当应用程序很小时,通常没有必要进行额外的Model和ViewModel的分离,因此人们倾向于标记其模型并让控制器使用存储库等。在完整大小的应用程序中,这很少会发生。 - one.beat.consumer
    @jgauffin,想一想——如果我没有持久性,当有人点击操作时,我的应用程序会直接发送电子邮件怎么办?从面向对象编程的角度来看,没有“模型层”,只有一个微小的类似DTO的类(可能仅包含ViewData/ViewBag中的字符串消息),我的视图为用户呈现此类。这就是MVC中的“模型”......即使在具有“模型层”的应用程序中,它也始终存在。 - one.beat.consumer
    我的声誉与此无关。我的十五年开发和架构经验或我的大学学位可能有所裨益。如果一个应用程序实例化一个Book类对象,将其持久化在某个地方,并将其返回到视图中的客户端,则同一引用对象执行两个角色。不,仅仅因为你试图将其用作视图模型并不意味着它是视图模型。视图模型可以将书籍列表转换为IEnumerable<SelectListItem>,或为Book.Title返回n/a而不是空字符串。ORM实体/模型无法做到这一点,因此必须将此类逻辑放入视图中。 - jgauffin
    显示剩余5条评论

    0

    1
    请记住,将某些东西称为反模式需要更多的上下文!许多应用程序不需要领域模型,因为它们所做的大部分事情都是CRUD操作。 - Rookian
    领域模型只是带有“元数据”的数据,如果您没有元数据,那也没关系。我删除了“反模式”一词,因为您在这方面是正确的。我真的很喜欢被接受的答案,而我的回答应该是评论。 - Imre L

    0

    选项2被描述为“Fat Stupid Ugly Controllers”架构(引用此表达的作者)。这种解决方案通常违反MVC精神,因为它破坏了关注点分离。


    1
    如果你问我,public ActionResult FetchApple() { return View(_groceryService.GetApple("Granny Smith")); } 的代码非常简洁。 - one.beat.consumer
    5
    我对FSUC文章的阅读与上述第二种选项不符。FSUC作者提供的例子没有显示使用服务层来封装所有的订购逻辑。相反,它展示了控制器被业务逻辑拖累的情况。由于业务逻辑在控制器中,其可重用性现在已经丧失。 - Marvo

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