2008-12-03 49 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,並清理了你的雜亂控制器。

相關問題