2

CQRS (Command Query Responsibility Segregation) の概念に従って、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 の値が DB に格納されている場合、誰かがストアド プロシージャの DB で直接実行する可能性があります。そのため、ここで他のレイヤーにビジネス ロジックが漏えいする可能性があります。

コマンドを実行しているときに、計算された値を列に保存しないのはなぜだと言うかもしれません。では、シナリオを少し変えてみましょう。現在の税率を含むレポートで従業員の潜在的な正味賃金を表示していて、賃金がまだ支払われていないとします。
これを CQRS でどのように処理しますか?

4

3 に答える 3

5

私の理解では、CQRSとDDDを組み合わせると、境界付けられたコンテキスト全体でデータを集約するクエリ側と、その特定のコマンドの境界付けられたコンテキストに対して厳密に実行されたコマンドのコマンド側が生成されるということです。

これにより、レポートは必要に応じてデータを取得できなくなります。

次に、読み取り側のクエリ ハンドラーに ICalculator を挿入して、ビジネス ロジックの計算を行うことができます。

例えば:

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

これにより、同じ計算サービスを使用して純賃金を計算するビジネス ロジックを別の場所で再利用できます。

パフォーマンスのために、結果を 2 回ループしたくない場合は、電卓をリポジトリに挿入することもできます。

于 2016-06-16T14:15:29.137 に答える
4

レポートは、それ自体が完全な境界コンテキストになる可能性があることを考慮してください。したがって、そのアーキテクチャは、コア ドメインに選択したものとは完全に異なる場合があります。

おそらく、CQRS はコア ドメインには適していますが、レポートのドメインには適していません。特に、レポートを生成する前に、さまざまなシナリオに基づいてさまざまな計算を適用したい場合。BIを考えてください。

CQRS はおそらくアプリケーション全体に適用すべきではないことに注意してください。アプリケーションが十分に複雑になったら、その境界コンテキストを特定し、同じデータ ソースを使用している場合でも、それぞれに適切なアーキテクチャ パターンを個別に適用する必要があります。

于 2012-07-13T14:58:09.067 に答える
1

最初のシナリオでは、クエリの時点でその計算を行う必要がある理由がわかりません。また、計算フィールドを使用する必要もありません。ドメインは、適切な従業員トランザクションがドメインで完了すると、計算された純賃金を生成できます。生成されたデータはクエリ側で消費され、クエリの準備が整ったビュー モデル フィールドに格納されます。

税率が変更された場合、通知 (イベント) を受信すると、クエリ側はすべての従業員ビュー モデルの正味賃金フィールドを再計算する必要があります。これは、クエリ要求の一部ではなく、保存の一部として (ドメイン トランザクションから非同期に) 発生します。クエリ側がこの計算を行っていますが、ドメインから提供された数値に基づいて計算しているため、問題はありません。

要点: すべての計算は、ドメインを介して、またはクエリの前にクエリ側のイベント ハンドラーによって実行する必要があります。

編集 - コメントに基づく

その特定の 'what-if' 分析シナリオでは、必要なデータが既にクエリ側にあると仮定します。つまり、従業員の勤務時間を含む 'EmployeeTimesheet' テーブルがある場合、2 つのオプションがあります。

  1. 従業員データを定期的にポーリングし、データを「潜在賃金」ビューモデルテーブルに集計/合計するクエリ側のコンポーネントを用意して、経営陣が現在の賃金支出を確認できるようにします。このポーリングの頻度は、情報が必要になる頻度によって異なります。おそらく、このデータが 1 時間以内に有効になる必要があるか、または毎日で十分である必要があります。

  2. ここでも「潜在賃金」テーブルがありますが、これは従業員が自分のタイムシートを更新したり、従業員の賃金が変更されたりするたびに更新されます。このオプションを使用すると、データはほぼリアルタイムに保たれます。

いずれにせよ、計算された集計データはドメインによって生成された数値を使用しており、クエリの前に行われるため、クエリは非常にシンプルで、最も重要なことに、超高速です。

EDIT 2 - 要約すると

私の考えでは、ドメインは計算を行う責任があり、そのような計算の結果は意思決定に必要ですこれがクエリ自体の一部でない限り、クエリ/読み取り側が合計を合計し、データを集計して画面/レポートに必要なデータを提供するために計算を行うことはまったく問題ありません。

于 2012-07-13T08:35:25.830 に答える