architecture - CQRS:查询端的业务逻辑

遵循 CQRS(命令查询责任分离)的概念,我直接在我的 MVC 应用程序中引用 DAL,并通过 ViewModel 进行所有读取。 然而,我的一位同事问我,如果在读取时必须应用任何业务逻辑,你会怎么做。 例如如果您需要在如下场景中计算百分比值:

//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 的值存储在数据库中,有人可能会直接在存储过程中的数据库中执行此操作。因此,我们可能会在其他层中泄漏业务逻辑。

您可能会说为什么不在执行命令时将计算值存储在列中。因此,让我们稍微改变一下场景。假设您在一份报告中显示员工的潜在净工资和当前税率,而工资尚未支付。
你会如何在 CQRS 中处理这个问题?

最佳答案

我的理解是,CQRS 与 DDD 相结合会产生一个查询端,该查询端跨限界上下文聚合数据,而命令端则严格针对该特定命令的限界上下文执行命令。

这将使您的报告根据需要检索其数据。

然后您可以将一些 ICalculator 注入(inject)到读取端的查询处理程序中以进行业务逻辑计算。

例如:

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*/
                }
            );
        }
    }
}

这使您可以在其他地方使用相同的计算器服务重用计算净工资的业务逻辑。

为了性能,如果您不想循环遍历结果两次,您也可以将计算器注入(inject)存储库。

https://stackoverflow.com/questions/11465025/

相关文章:

wpf - 带有背景图像和颜色的文本框

javafx-2 - 是否可以禁止在 TextField 中进行选择?

ruby-on-rails - 限制 activerecord 查询返回的结果总数?

css - 使用 :nth-child(odd) selector on div image

css - 可缩放字体大小,SASS (LESS) with "px"vs plain ems

oracle - 执行 Oracle 过程时向用户显示注释

jquery - 获取 "removeattr is not a function"异常

PHPUnit - 数据库测试,如何管理它

php - 用空格定义类变量

sql-server - 当命令类型为 adCmdText 时是否可以使用命名参数?