集約ルートに別のARを含める必要がある場合(および含めない場合)


15

最初に投稿の長さについて謝罪しますが、コメントで前後に時間をかけないように、できるだけ多くの詳細を前もって伝えたいと思いました。

DDDアプローチに従ってアプリケーションを設計していますが、集約ルートに別のARを含めるべきか、それとも独立した「独立した」ARとして残すべきかを判断するためにどのようなガイダンスに従うべきか疑問に思っています。

従業員がその日のために自分で出入りできる単純なタイムクロックアプリケーションの場合を考えてみましょう。UIを使用すると、従業員IDとPINを入力できます。これらは検証され、従業員の現在の状態が取得されます。従業員が現在出勤している場合、UIには「Clock Out」ボタンが表示されます。そして、逆に、彼らが出勤していない場合、ボタンには「Clock In」と表示されます。ボタンによって実行されるアクションは、従業員の状態にも対応しています。

アプリケーションは、RESTfulサービスインターフェイスを介して公開されるバックエンドサーバーを呼び出すWebクライアントです。直感的で読み取り可能なURLを作成する最初のパスの結果、次の2つのエンドポイントが作成されました。

http://myhost/employees/{id}/clockin
http://myhost/employees/{id}/clockout

注:これらは、従業員IDとPINが検証され、「ユーザー」を表す「トークン」がヘッダーで渡された後に使用されます。これは、マネージャーまたはスーパーバイザーが別の従業員に出勤または出勤できるようにする「マネージャーモード」があるためです。しかし、この議論のために、私はそれをシンプルにしようとしています。

サーバーには、APIを提供するApplicationServiceがあります。ClockInメソッドの最初のアイデアは次のようなものです。

public void ClockIn(String id)
{
    var employee = EmployeeRepository.FindById(id);

    if (employee == null) throw SomeException();

    employee.ClockIn();

    EmployeeRepository.Save();
}

これは、従業員のタイムカード情報がトランザクションのリストとして実際に維持されていることに気付くまで、非常に簡単です。つまり、ClockInまたはClockOutを呼び出すたびに、従業員の状態を直接変更するのではなく、従業員のタイムシートに新しいエントリを追加しています。従業員の現在の状態(出勤しているかどうか)は、タイムシートの最新のエントリから取得されます。

したがって、上記のコードを使用する場合、リポジトリは従業員の永続プロパティが変更されていないことを認識し、従業員のタイムシートに新しいエントリが追加されたことを認識し、データストアに挿入する必要があります。

一方(そして、ここに投稿の最終的な質問があります)、TimeSheetはAggregate Rootであり、アイデンティティ(従業員IDと期間)を持っているように見え、TimeSheet.ClockInと同じロジックを簡単に実装できます(従業員ID)。

私は2つのアプローチのメリットについて議論していることに気づき、冒頭の段落で述べたように、どのアプローチが問題により適しているかを判断するためにどの基準を評価すべきか疑問に思います。


質問をより明確にし、より良いシナリオを使用するように、投稿を編集/更新しました(うまくいけば)。
-SonOfPirate

回答:


4

私は時間追跡のためのサービスを実装する傾向があります:

public interface ITimeSheetTrackingService
{
   void TrackClockIn(Employee employee, Timesheet timesheet);

   void TrackClockOut(Employee employee, Timesheet timesheet);

}

出勤や退勤はタイムシートの責任ではなく、従業員の責任でもないと思います。


3

集約ルートはお互いを含むべきではありません(ただし、他のIDを含む場合があります)。

まず、TimeSheetは本当に集約ルートですか?アイデンティティを持っているという事実は、必ずしもARではなくエンティティになります。次のルールのいずれかを確認してください。

Root Entities have global identity.  Entities inside the boundary have local 
identity, unique only within the Aggregate.

TimeSheetのIDを従業員IDと期間として定義します。これは、TimeSheetが従業員の一部であり、期間がローカルIDであることを示唆しています。これはタイムクロックアプリケーションであるため、従業員の主な目的はタイムシートのコンテナですか?

ARを使用する必要があると仮定すると、ClockInとClockOutは、Employeeの操作よりもTimeSheetの操作のように見えます。サービスレイヤーメソッドは次のようになります。

public void ClockIn(String employeeId)
{
    var timeSheet = TimeSheetRepository.FindByEmployeeAndTime(employeeId, DateTime.Now);    
    timeSheet.ClockIn();    
    TimeSheetRepository.Save();
}

EmployeeとTimeSheetの両方で出勤状態を本当に追跡する必要がある場合は、Domain Eventsを見てください(Evansの本には載っていないと思いますが、オンラインには多数の記事があります)。Employee.ClockIn()はEmployeeClockedInイベントを発生させます。これはイベントハンドラーが取得し、TimeSheet.LogEmployeeClockIn()を呼び出します。


あなたのポイントがわかります。ただし、従業員が出勤できるかどうか、いつ入室できるかを制限する特定のルールがあります。これらのルールは、主に従業員の現在の状態によって決まります。タイムシフトはこの知識を所有する必要がありますか?
-SonOfPirate

3

DDDアプローチに従ってアプリケーションを設計していますが、集約ルートに別のARを含めるべきか、それとも独立した「独立した」ARとして残すべきかを判断するためにどのようなガイダンスに従うべきか疑問に思っています。

集約ルートに別の集約ルートを含めることはできません。

説明には、Employee エンティティと同様にTimesheetエンティティがあります。これら2つのエンティティは異なりますが、相互参照を含めることができます(例:これはボブのタイムシートです)。

これが基本的なモデリングです。

集約ルートの質問はわずかに異なります。これらの2つのエンティティが互いにトランザクション的に異なる場合、それらは2つの異なる集約として正しくモデル化できます。トランザクション的に異なるということは、Employeeの現在の状態とTimeSheetの現在の状態を同時に知る必要があるビジネス不変条件がないことを意味します。

説明に基づいて、Timesheet.clockInとTimesheet.clockOutは、コマンドが許可されているかどうかを判断するためにEmployeeのデータをチェックする必要はありません。したがって、これだけの問題については、2つの異なるARが妥当と思われます。

ARの境界を考慮するもう1つの方法は、どの種類の編集が同時に許可されるかを尋ねることです。HRは従業員のプロファイルをいじりながら、マネージャーは従業員を退勤させることができますか?

一方(そして、ここに投稿の最終的な質問があります)、TimeSheetは、Aggregate Rootであり、アイデンティティ(従業員IDと期間)を持っているようです。

アイデンティティとは、それがエンティティであることを意味するだけです。独立した集約ルートと見なすべきかどうかを決定するのはビジネスの不変条件です。

ただし、従業員が出勤できるかどうか、いつ入室できるかを制限する特定のルールがあります。これらのルールは、主に従業員の現在の状態によって決まります。タイムシフトはこの知識を所有する必要がありますか?

たぶん-集約が必要です。それは必ずしもタイムシートがそうであることを意味しません。

つまり、タイムシートを変更するためのルールがEmployeeエンティティの現在の状態に依存する場合、EmployeeとTimesheetは間違いなく同じ集計の一部である必要があり、集計は全体として、ルールが続きました。

集約には1つのルートエンティティがあります。それを識別することはパズルの一部です。従業員に複数のタイムシートがあり、両方が同じ集計の一部である場合、タイムシートは間違いなくルートではありません。これは、アプリケーションがコマンドを直接変更したり、タイムシートにディスパッチしたりできないことを意味します。ルートオブジェクト(おそらくEmployee)にディスパッチする必要があります。

別のチェックは、タイムシートの作成方法を検討することです。従業員が出勤するときに暗黙的に作成された場合、それは集計内の下位エンティティであることの別のヒントです。

余談ですが、集計が独自の時間感覚を持つ必要はほとんどありません。代わりに、時間が彼らに渡されるべきです

employee.clockIn(when);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.