MVC业务逻辑组织

7

我正在尝试使用ASP.Net学习MVC,并阅读Steve Sanderson的书。有一件事让我感到困惑,那就是业务逻辑应该放在哪里?

例如,当删除一个产品时,Sanderson只在他的CartController中调用了productsRepository的Delete方法。这对我来说很奇怪,因为如果有任何业务逻辑,比如确保该产品首先不在任何人的购物车中等,它必须放在products repository或CartController中。

这两个地方都不适合放置业务逻辑;products repository旨在轻松替换(从使用db切换到使用session),而使用Controller意味着您将业务逻辑放在UI层中。

难道他不应该使用一个包含业务逻辑并调用repository的delete方法的类吗?repository是业务逻辑类的成员变量吗?


这个回答解决了你的问题吗?MVC:业务逻辑应该放在哪里? - Jonathan Hall
5个回答

8
我通常会按照以下方式组织我的MVC解决方案:
  • X.Core
    • 通用扩展方法、日志记录和其他非Web基础设施代码
  • X.Domain
    • 领域实体和存储库
  • X.Domain.Services
    • 用于协调复杂领域操作的领域服务,例如将产品添加到购物车中
  • X.Application.Core
    • 应用程序逻辑(初始化(路由注册、IoC配置等)、Web特定扩展、MVC过滤器、控制器基类、视图引擎等)
  • X.Application.Models
    • 视图模型类
  • X.Application.Services
    • 可以通过访问存储库或领域服务返回ViewModels的服务类,也可以通过反向方式进行更新
  • X.Application.Web
    • 控制器、视图和静态资源
其中一些可以合并,但将它们分开使得定位内容更容易,也能确保层之间的边界得到尊重。
一个典型的控制器操作以显示产品购物车为例可能如下:
public virtual ActionResult ProductCart()
{
    var applicationService = <obtain or create appropriate service instance>
    var userID = <obtain user ID or similar from session state>
    var viewModel = applicationService.GetProductCartModel( userID );
    return View( "Cart", viewModel );
}

一个典型的添加产品到购物车的控制器动作可能如下所示:
public virtual ActionResult AddProductToCart( int productID )
{
    var domainService = <obtain or create appropriate service instance>
    var userID = <obtain user ID or similar from session state>
    var response = domainService.AddProductToCart( userID, productID );
    return Json( new { Success = response.Success, Message = response.Message } );
}

谢谢,但我不确定这回答了我的问题。 - user660734
带有一些代码示例的更新,以说明放置逻辑的基本思想 - 希望这可以帮助 :) - Morten Mertner
看了你的项目结构,我不确定直观地理解哪些东西应该放在哪里。这可能更多与MVC有关,但它对我来说并不感觉合乎逻辑.. - flesh
我建议你了解领域驱动设计(DDD)。一旦你理解了聚合根与实体、应用程序与领域服务等术语,项目名称就会比你最初想象的更具有说明性。可以参考http://devlicio.us/blogs/casey/archive/2009/02/17/ddd-services.aspx以及相关系列文章作为入门。在MVC中,我更喜欢使用视图模型而不是暴露实体,并使用应用程序服务来进行翻译。 - Morten Mertner

3
我也读过Sanderson的第一版书,它非常棒 - 是学习和开始使用ASP.NET MVC的一种非常简单的方式。不幸的是,你不能直接从他的书中的概念跳到编写大型可维护应用程序。最大的障碍之一是找出在UI和持久存储之间放置业务逻辑和其他关注点的位置(称为Separation of Concerns或SOC的概念)。
如果您有兴趣,请考虑阅读领域驱动设计。我不会说我完全了解它,但它可以作为从Sanderson的示例应用程序转换到成功分离UI关注点、业务逻辑和存储关注点的东西。
我的解决方案有一个单独的服务层。控制器与服务层通信(使用依赖注入 - Ninject)。服务层可以访问我的领域对象/业务逻辑和我的存储库(NHibernate - 也使用Ninject启动)。我的视图或控制器中几乎没有逻辑 - 控制器起到协调员的作用,我努力保持我的控制器操作尽可能薄。
我的领域层(实体、业务逻辑等)没有依赖关系。它没有引用我的Web项目或我的Repository项目。它通常被称为POCO或Plain Old C#/CLR对象。
编辑:我注意到你在其中一条评论中使用了EF。EF不支持POCO,除非使用称为Code First的东西(当我上次检查时还处于社区技术预览状态)。只是提供信息。

一个好的回答 - 你可以加上你项目结构的图示吗? - flesh
我决定像下面这样做:1)创建一个“服务”类,该类具有存储库和实体成员变量。实体成员变量仅是我正在建模的对象的属性(POCO?)。控制器将实例化服务并设置其存储库和实体值。然后,它调用服务的Add()方法,例如。我还使用Ninject处理依赖关系。在我重新设计我的应用程序以按此方式工作之前,您有什么想法吗? - user660734
在我看来,你应该拥有一个ProductService类,其中包含产品和购物车仓库的引用作为私有成员。 这些仓库的实例可以通过依赖注入提供。 此类还应包含一种方法,它以产品和目标购物车作为参数,查询此产品是否在任何其他购物车中使用,并最终将产品添加到目标购物车中。 - Frank

1

现实情况是,这个问题没有银弹,而且它实际上是一个情境相关的问题。我喜欢尽可能地分开事物,业务逻辑其实只是应用程序中的另一层。

我设计了一个受BDD启发的决策引擎,并将其作为一个开源项目发布在NuGet上。该项目名为NDecision,你可以在NDecision项目主页上阅读相关信息。

NDecision使得决策树业务逻辑的实现变得非常简单,如果你喜欢Gherkin语法和流畅编程实践,使用它会让你感到非常舒适。下面的代码快照来自项目网站,展示了业务逻辑的实现方式。

NDecision example code

这可能对您来说不是一种解决方案,但是想法是,如果您已经在一个程序集中拥有了您的域模型 - 这是另一个回答中建议的一个很好的想法和常见做法,那么您完全可以有另一个程序集来处理您的“决策树”。NDecision 正是出于此目的而编写的,将逻辑分离成一个独立层。

希望这会对您有所帮助。


0
如果你想要可扩展性,直接从GUI使用数据访问对象是不好的实践。因此,在你提到的例子中,我认为你应该用一些支持业务操作的业务逻辑类替换直接读取或写入数据库的存储库。

什么是存储库模式呢?按定义来说,它是一种抽象化的概念。在我的情况下,它是对实体框架的封装。 - user660734

0
我正在尝试学习使用ASP.Net的MVC模式,并阅读Steve Sanderson的书。有一件事让我感到困惑,那就是业务逻辑应该放在哪里?
答案是在模型(M in MVC)或领域层中。这些是一组独立的类型(最好在一个单独的项目中),代表领域模型、服务和存储库。

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