2008-12-03 47 views
17

我的ASP.NET MVC Web应用程序中的控制器开始变得有点臃肿与业务逻辑。 Web上的示例都显示简单的控制器操作,只需将数据从存储库中提取出来并传递给视图即可。但是如果你还需要支持业务逻辑呢?ASP.NET MVC Web应用程序中的控制器应该调用存储库,服务还是两者?

说,例如,该履行订单的动作也需要发送电子邮件了。我是否将这一点放在控制器中,并将此逻辑复制/粘贴到任何其他同样符合命令的操作?我的第一个直觉就是创建一个像OrderFulfillerService这样的服务来处理所有这些逻辑,并让控制器动作调用它。但是,对于像从数据库中检索用户列表或订单这样的简单操作,我想直接与存储库进行交互,而不是将该调用包含在服务中。

这是一个可以接受的设计模式?控制器操作在需要数据访问时需要业务逻辑和存储库时调用服务?

回答

0

你的业务逻辑应该被封装在业务对象 - 如果你有一个订单对象(和你做什么,不是吗?),以及业务规则规定,在履行订单后的电子邮件应发送,然后您的Fulfill方法(或者,如果更合适的话,IsFulfilled的setter)应该触发该操作。我可能会拥有配置信息,这些信息指向业务对象的适当应用程序的电子邮件服务,或者更一般地指向“通知程序”服务,以便在必要时添加其他通知类型。

2

如果你打算有一个业务层,那么我认为最好是只有这个业务层与数据层对话。我可以理解为什么在一个简单的用例中,您会将表示层(控制器)直接与数据层进行交谈,但是一旦您确定需要一个隔离的业务层,那么将这两者的用法混合到更高级别危险。

例如,如果控制器A调用业务层中的方法来获取对象A的列表(并且此方法应用业务规则 - 可能是一些过滤或排序),但控制器B出现时需要同一块数据,但忘记业务层并直接调用数据层?

1

这似乎讨厌看到很多这方面的业务服务:

public Customer GetCustomer(int id) 
{ 
    return customerRepository.Get(id); 
} 

而且它自然有强烈的冲动绕过服务。但从长远来看,您最好能够让控制器和存储库之间的业务服务处于中间状态。现在

,一个非常简单的CRUD类型的应用程序,你可以有你的控制器消耗资源库,而不是直接通过企业服务去。您仍然可以拥有像EmailerService这样的东西,但IMO在提取和处理实体时最好不要在控制器中混用和匹配业务服务和存储库调用。

至于有实体(业务对象)通话服务或基础设施组件,我不会那样做。我更愿意保留实体POCO并且不受依赖。

1

这将有助于如果我们能够阻止一遍又一遍地看这个例子...

public ActionResult Index() 
{ 
    var widgetContext = new WidgetDataContext(); 
    var widgets = from w in widgetContext.Widget 
       select w; 
    return View(widgets); 
} 

我也知道这是不利于你的问题,但它似乎是一个很大的一部分演示软件,我认为这可能会引起误解。

+0

同意。作为一个例子,我认为它是“OK”,但如果他们提到这不是从架构的角度来看最好的做法,那将会很好。 – 2009-07-30 02:05:56

25

您的控制器(在MVC项目中)应该调用服务项目中的对象。服务项目是处理所有业务逻辑的地方。

一个很好的例子是这样的:

public ActionResult Index() 
{ 
    ProductServices productServices = new ProductServices(); 

    // top 10 products, for example. 
    IList<Product> productList productServices.GetProducts(10); 

    // Set this data into the custom viewdata. 
    ViewData.Model = new ProductViewData 
         { 
          ProductList = productList; 
         }; 

    return View(); 
} 

或依赖注入(我的最爱)

// Field with the reference to all product services (aka. business logic) 
private readonly ProductServices _productServices; 

// 'Greedy' constructor, which Dependency Injection auto finds and therefore 
// will use. 
public ProductController(ProductServices productServices) 
{ 
    _productServices = productServices; 
} 

public ActionResult Index() 
{ 
    // top 10 products, for example. 
    // NOTE: The services instance was automagically created by the DI 
    //  so i din't have to worry about it NOT being instansiated. 
    IList<Product> productList _productServices.GetProducts(10); 

    // Set this data into the custom viewdata. 
    ViewData.Model = new ProductViewData 
         { 
          ProductList = productList; 
         }; 

    return View(); 
} 

现在..有什么服务项目(或什么是ProductServices)?这是一个带有业务逻辑的类库。例如。

public class ProductServices : IProductServices 
{ 
    private readonly ProductRepository _productRepository; 
    public ProductServices(ProductRepository productRepository) 
    { 
     _productRepository = productRepository; 
    } 

    public IList<Product> GetProducts(int numberOfProducts) 
    { 
     // GetProducts() and OrderByMostRecent() are custom linq helpers... 
     return _productRepository.GetProducts() 
      .OrderByMostRecent() 
      .Take(numberOfProducts) 
      .ToList(); 
    } 
} 

但可能是所有这么硬派和混乱......所以ServiceProduct类的简单版本可能是(但我不会真的建议)...

public class ProductServices 
{ 
    public IList<Product> GetProducts(int numberOfProducts) 
    { 
     using (DB db = new Linq2SqlDb()) 
     { 
      return (from p in db.Products 
        orderby p.DateCreated ascending 
        select p).Take(10).ToList(); 
     } 
    } 
} 

所以有你走。你可以看到所有的逻辑都在服务项目中,这意味着你可以在其他地方重用这些代码。

我从哪里学到的?

Rob ConeryMVC StoreFront媒体和tutorials。自切片面包以来最好的事情。 他的教程详细解释了(我所做的)完整的解决方案代码示例。他使用依赖注入,这是SOO kewl,现在我已经看到他在MVC中如何使用它。

HTH。

+0

很好解释 – redsquare 2008-12-04 11:00:42

+1

欢呼队友:)感谢罗布康里/菲尔哈克/斯科特Hanselman等教我。 – 2008-12-04 11:28:47

5

我不确定为此使用服务。

据我所知,DDD的原理之一(我现在正在阅读)是域对象组织成聚合,并且当你创建一个聚合根的实例时,它可以只能直接处理Aggregate中的对象(以帮助保持清晰的责任感)。

创建骨料应该执行任何不变量等

隔空Customer类的一个例子,客户可能是聚合根和骨料内的另一个类可能是地址。

现在,如果您要创建新的客户,您应该可以使用客户构造函数或工厂来完成此操作。这样做应该返回在Aggregate边界内完全有效的对象(所以它不能处理产品,因为它们不是Aggregate的一部分,但它可以处理地址)。

数据库是一个次要问题,只有将聚合持久化到数据库或从数据库中检索数据库时才起作用。

为了避免直接与数据库连接,您可以创建一个Repository接口(如上所述),给定一个Customer实例(其中包含对Address的引用)应该能够将Aggregate持久化到数据库。

问题是,Repository接口是你的领域模型/层的一部分(不是库的实现)。另一个因素是版本库可能最终应该调用与创建新对象相同的“创建”方法(以维护不变量等)。如果你使用的是构造函数,这很简单,因为当数据库从数据库“创建”对象时,最终你会调用构造函数。

应用程序层可以直接与域(包括存储库接口)进行通信。

所以,如果你想创建一个对象的新实例,你可以例如

Customer customer = new Customer(); 

如果应用程序需要检索库中的客户的实例,也没有特别的原因,我能想到的它不叫......

Customer customer = _custRepository.GetById(1) 

或...

Customer customer = _custRepository.GetByKey("AlanSmith1") 

最终,它将最终得到一个Customer对象的实例,它在它自己的限制和规则内运行,就像直接创建新的Customer对象一样。

我认为服务应该保留,当你试图使用的“东西”只是不是一个对象。大多数规则(约束等)可以写成域对象本身的一部分。

一个很好的例子是在我正在阅读的DDD快速pdf。在那里,他们对书架对象有一个限制,因此只能添加与书架可以包含的书一样多的书。

调用BookShelf对象上的AddBook方法会在将书籍添加到BookShelf的Book对象集合之前检查该空间是否可用。一个简单的例子,但业务规则是由域对象本身强制执行的。

我不是说以上任何一种都是正确的!我正试图让我的头在这一刻!

1

嗯,它真的取决于你,我喜欢保持控制器尽可能简单,并归档这我需要封装bussines逻辑在一个单独的层,这里是大事情,主要是你有2选项,假设你使用LINQ2SQL或实体框架:

  • 您可以使用扩展方法和 部分类来验证你的模型 只是保存更改(前的钩 方法,你可以看到的 一个例子这在Nerdinner样本中由Scott Gu)。

  • 另一种方法(我最喜欢的, 因为我觉得在应用程序流的更多控制 ),是用于bussines 逻辑像服务层一 完全独立的层(你可以 看到这个aprroach在由Stephen Walther在 asp.net/mvc专区撰写的 系列教程)。

有了这两种方法,你得到了DRY,并清理了你的杂乱控制器。

相关问题