2012-07-13 89 views
1

遵循CQRS(命令查詢責任分離)的概念,我直接在我的MVC應用程序中引用DAL,並通過ViewModels進行所有讀取。 然而,我的一位同事問我,當讀取任何業務邏輯時,你會怎麼做。 例如如果需要計算在場景的百分比值象下面這樣:CQRS:查詢端的業務邏輯

//Employee domain object 
class Employee 
{ 
    string EmpName; 
    Single Wages; 
} 

//Constant declared in some utility class. This could be stored in DB also. 
const Single Tax = 15; 

//View Model for the Employee Screen 
class EmployeeViewModel 
{ 
    string EmpName; 
    Single GrossWages; 
    Single NetWages; 
} 


// Read Facade defined in the DAL 
class ReadModel 
{ 
    List<EmployeeViewModel> GetEmployeeList() 
    { 
     List<EmployeeViewModel> empList = new List<EmployeeViewModel>; 
     string query = "SELECT EMP_NAME, WAGES FROM EMPLOYEE";  
     ... 
     .. 
     while(reader.Read()) 
     { 
      empList.Add(
       new EmployeeViewModel 
       { 
        EmpName = reader["EMP_NAME"], 
        GrossWages = reader["WAGES"], 
        NetWages = reader["WAGES"] - (reader["WAGES"]*Tax)/100 /*We could call a function here but since we are not using the business layer, the function will be defined in the DAL layer*/ 
       } 
      ); 
     } 
    } 
} 

在上面的例子中,存在其在DAL層存在的讀出的過程中發生的歷史測算。我們可以創建一個函數來進行計算,但是由於我們已經繞過了業務層來讀​​取數據,函數將位於DAL中。更糟糕的是,如果Tax的值存儲在數據庫中,則有人可能會直接在DB中的存儲過程中執行此操作。所以我們在其他層面上存在潛在的業務邏輯泄漏。

您可能會說,爲什麼不在執行命令時將計算值存儲在列中。所以讓我們稍微改變一下情況。讓我們假設您在報告中顯示員工的潛在淨工資與當前稅率並且工資尚未支付。
你將如何在CQRS中處理這個問題?

回答

3

請注意報告可以是一個整體有界的上下文在它自己的權利。因此,它的體系結構可能與您爲核心域選擇的體系結構完全不同。

也許CQRS非常適合核心域,但不適用於報告域。特別是當您想要在報告生成之前根據不同情況應用各種計算。想想BI。

請記住,CQRS可能不應用於整個應用程序。一旦你的應用程序足夠複雜,你應該識別它的有界上下文,併爲每個應用適當的架構模式,即使他們使用相同的數據源。

+0

我認爲報告對於CQR​​S來說可能是一個糟糕的情況,正是出於這些原因。將所有可能的報告歸一化是沒有意義的;有太多,而且生產成本太高。查詢命令分離的好處在於可以自由選擇不同的讀取模型來滿足您的需求。您可以將事件轉儲爲GUI的一些簡單模型,但也可以將它們轉儲到OLAP多維數據集中以執行假設分析報告。是的,「業務邏輯」在OLAP查詢中,但這就是報告。 – 2012-07-13 15:02:03

0

對於第一種情況,我不明白爲什麼您需要在查詢時進行計算,您也不需要使用計算字段。當相應的員工交易在域上完成時,該域可以產生計算出的淨工資。生成的數據被查詢端使用,並存儲在查看模型字段中以便查詢。

如果稅率發生變化,一旦收到通知(事件),查詢方將不得不重新計算所有員工視圖模型的淨工資字段。這將作爲保存的一部分(與域事務異步)發生,而不是作爲查詢請求的一部分。雖然查詢方正在做這個計算,但是它是基於域提供的數字來做的,所以我沒有看到這個問題。

要點:在任何查詢之前,所有計算都應該通過域或查詢端事件處理程序完成。基於

編輯 - 在評論

因此,對於特定的「假設性」分析場景,假設所需的數據已經在查詢方 - 即沒有包含小時「EmployeeTimesheet」表通過員工的工作,有兩個選項:

  1. 對查詢側的組件,週期性和骨料/數據總和爲一個「潛在的工資」的視圖模型表,準備投票的員工數據管理看目前的工資支出。此輪詢的頻率取決於信息需要的頻率。也許他們需要這些數據在一小時內有效,或者每天的數據可能令人滿意。

  2. 再次,有一個'潛在的工資'表,但是隨着員工更新他們的時間表或任何時候員工的工資被更改,這個表會被更新。有了這個選項,數據將保持接近實時。

無論哪種方式,計算出的聚集的數據是使用由所述域產生的附圖和之前的查詢,以便查詢是超級簡單和最重要的是,超快速完成。

編輯2 - 只是爲了總結

在我的腦海裏,域應該是負責做計算確定,由此需要決定了這樣的計算結果進行。對於查詢/讀取方面,爲了總計總和&彙總數據以給出他們需要的數據給屏幕/報告,,只要這不是查詢自身的一部分就完成了計算。

+0

潛在工資我的意思是假設系統中有一個報告顯示管理層本月有哪些工資符合支付條件,但尚未支付。更像是一個「假設」分析,其中計算顯示潛在的結果。 – devanalyst 2012-07-13 09:22:09

+0

@devanalyst - 好吧,我明白了 - 我已經更新了答案,我如何處理該場景 – 2012-07-13 09:39:14

+0

做了另一個編輯來總結我關於查詢方面邏輯的要點 – 2012-07-13 09:56:17

2

我的理解是CQRS與DDD結合會產生一個查詢端,它在有界上下文和命令端聚合數據,嚴格地針對該特定命令的有界上下文。

這會讓您的報告檢索其所需的數據。

然後,您可以在讀取端的查詢處理程序中注入一些ICalculator以執行業務邏輯計算。

E.g:

public class EmployeeQueryHandler : EmployeeIQueryHandler 
{ 
    private readonly INetWageCalculator _calculator; 
    private readonly IEmployeeRepository _repo; 

    public Repository(INetWageCalculator calculator, IEmployeeRepository repo) 
    { 
     _calculator = calculator; 
     _repo = repo; 
    } 

    public List<EmployeeViewModel> ExecuteQuery() 
    { 
     var employees = _repo.GetEmployeeList(); 

     foreach(var emp in employees) 
     { 
      // You have to get tax from somewhere, perhaps its passed in as 
      // a parameter... 
      emp.NetWages = _calculator.Calculate(emp.GrossWages, Tax); 
     } 

     return employees; 
    } 
} 


public class EmployeeRepository : IEmployeeRepository 
{ 

    List<EmployeeViewModel> GetEmployeeList() 
    { 
     List<EmployeeViewModel> empList = new List<EmployeeViewModel>; 
     string query = "SELECT EMP_NAME, WAGES FROM EMPLOYEE";  
     ... 
     .. 
     while (reader.Read()) 
     { 
      empList.Add(
       new EmployeeViewModel 
       { 
        EmpName = reader["EMP_NAME"], 
        GrossWages = reader["WAGES"], 

        // This line moves to the query handler. 
        //NetWages = reader["WAGES"] - (reader["WAGES"] * Tax)/100 /*We could call a function here but since we are not using the business layer, the function will be defined in the DAL layer*/ 
       } 
      ); 
     } 
    } 
} 

這樣您就可以重複使用計算淨工資的其他地方使用相同的計算器服務的業務邏輯。

爲了表現的緣故,如果您不想循環遍歷結果兩次,也可以將計算器注入到存儲庫中。