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

标签 architecture domain-driven-design cqrs business-logic

遵循 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)存储库。

关于architecture - CQRS:查询端的业务逻辑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11465025/

相关文章:

design-patterns - 跨多个服务器/应用程序共享全局变量

java - 在实体对象中创建服务层的新实例是否正确?

asynchronous - 有哪些流行的框架可用于实现 CQRS、Event Sourcing 和 Saga 以实现数据一致性和分布式事务?

CQRS 事件不包含更新读取模型所需的详细信息

node.js - 微服务创建实体实现

c# - 在 MVC 4 中为 3 层架构配置 Ninject

design-patterns - 长对象调用链有天生的错误吗?

.net - 我可以每 3 秒轮询一次数据库吗?

architecture - DDD-聚合根/在这种情况下我的内部实体是否需要本地身份?

domain-driven-design - DDD : can a Repository return entities inside an aggregate?