「ドメインモデルを集計ルートに分割できない」という問題に遭遇しました。
私はジュニア開発者であり、DDDの初心者です。私は本当にそれを理解したいのですが、時にはそれは本当に混乱します。
この時点から、私のドメインについて簡単に説明します。
私の目標は、ユーザーが自分であらゆる種類のドキュメントを作成する機会を提供することです。ユーザーは新しいタイプのドキュメントを作成できます。新しいタイプはそれぞれその属性で構成されます。次に、このアプリケーションのユーザーは、そのタイプに基づいて具体的なドキュメントを作成できます。ユーザーは承認のためにドキュメントを送信することもできます。承認フローはタイプごとに異なります。
したがって、次のモデルがあります。
他にもモデルはありますが、意味がないと思います。
ご理解のとおり、ここではデータモデルのエンティティ属性値(EAV)パターンを適用します。データベース内の関係を示す diagram を確認できます。
そして私の問題は:
説明した以外にも、モデルには多くのエンティティがあります。
ドキュメントは間違いなく私のドメインの集約ルートだと思います。集合体であるApprovalProcessなどは、その外では生きられないからです。
これが最初の質問です:
ApprovalProcessは、そのステップで構成されています。変更可能であるため、各ステップはエンティティです。ステップには、変更可能な状態があります。 ApprvalProcessの状態は、そのステップによって異なります。ここに、ビジネスの不変条件があります:「ApprovalProcessは、そのすべてのステップが承認された場合にのみ承認できます」。
それはビジネスの不変条件を持ち、そこから生きることができないエンティティーを含んでいるので、それは集合根だと思います。また、ApprovalProcessの一貫性を保つために、そのステップに直接アクセスできるようにしたくありません。
ApprovalProcessが集約ルートであると誤解していますか?それは単なる集合体でしょうか?ある集計ルートを別のルートの一部として存在させることはできますか? Documentがその部分へのアクセスを担当しているため、ApprovalProcessは単に集約されているということですか?しかし、ApprovalProcessのステップが承認されると、Documentは操作をApprovalProcessに委任します。
例えば:
Document doc = new Document(...);
doc.SendForAooroval(); //ApprovalProcess is created.
doc.ApproveStep(int stepId); // Inside the method Document delegates responsibility for approvement to ApprovalProcess.
または、DocumentとApprovalProcessを別々に残す必要があります。したがって、ドキュメントはIDによってApprovalProcessを参照します。また、次のシナリオがあります。
Document doc = documentRepository.Get(docId);
doc.SendForAooroval();// A domain event "DocumentCreatedEvent" is raised.
DocumentCreatedEventHandler:
ApprovalProcess approvalProcess = new ApprovalProcess(event.DocId); // ApprovalProcessCreatedEvent is raised
approvalProcessRepository.Add(approvalProcess);
approvalProcessRepositroy.UnitOfWork.Save(); //commit
ただし、ApprovalProcessの状態が変化すると、Documentの状態も変化します。 ApprovalProcessが承認され、次にドキュメントも承認されます。別のWord ApprovalProcessは、ドキュメントの状態の一部です。そのおかげで初めて、ドキュメントが承認されたことがわかります。
そして私が経験している最大の問題:
DocumentTypeも集約ルートです。これは、その属性とApprovalSchemeで構成されています。説明をできるだけ簡単にするために、ApprovalSchemeについてはまだ言及していません。 ApporvalSchemeもいくつかのエンティティで構成されています。これは、DocumentTypeの承認フローにすぎません。 ApprovalProcessは、Documentを持つDocumentTypeのApprovalSchemeに従って作成されます。 DocumentTypeがないとApprovalSchemeは存在できません。一対一の関係。
Documentは、IDによってそのDocumentTypeを参照します。正しいですか?
このタスクの最初に、DocumentTypeはDocumentの一部である必要があると思いました。
DocumentTypeには多くのドキュメントがありますが、私のドメインでは意味がありません。 DocumentTypeの状態を表すものではありません。 DocumentTypeは削除済みとしてマークできますが、削除することはできません。
DocumentとDocumentTypeは、2つの異なる集約ルートです。私は正しいですか?
よろしくお願いします。ご清聴ありがとうございました!私のひどい英語でごめんなさい。
================================================== ================================================== ================================================== ====
私のドメインに関する詳細を提供したいと思います。
ユーザー、ドキュメント、ドキュメントのタイプ、ドキュメントの属性があります。
アプリケーションのドキュメントには動的フィールドがあります。そこで、EAVパターンを使用します。ドキュメントのタイプは、一連の属性と、ステップで構成される承認フローを定義します。ドキュメントが作成されると、そのドキュメントにそのタイプに含まれるすべての属性値が作成されます。
ドキュメントが承認のために送信される場合、ドキュメントタイプによって保持される承認フローの各ステップに対して特定の状態が作成されます。この一連の状態は、ドキュメントの承認プロセスに関連しています。
ユーザーには、従業員とアミンの2種類があります。従業員は、マネージャー、会計士などの異なる役割を持つことができます。
各従業員は次のことができます。
各管理者は次のことができます。
承認プロセスが拒否された場合、ドキュメントの作成者であるユーザーは、ドキュメントを修正して、承認のために再度送信する必要があります。
それを実装する方法を選択する必要があります。最初のオプションは、次のエンティティとアグリゲートがあると思います(インターフェースを表示したい)。
interface User {
Document CreateNewDocument(int docType);
Document SendDocumentForApproval(int docId);
Document EditDocument(document, List<AttributesValues> attributesValues); // list of only attributes that must be changed.
Document Approve(document, stepId);
Document Reject(document, stepId);
Document Print(document);
}
そして、次のように使用します。
CreateDocumentCommandHandler:
User user = userRepository.GetUser(userId);
Document doc = user.CreateNewDocument(docTypeId);
documentRepository.Add(doc);
documentRepository.UnitOfWork.Save();
SendDocumentForApprovalCommandHandler:
User user = userRepository.GetUser(userId);
Document doc = documentRepository.GetDocument(docId)
doc = user.SendDocumentForApproval(doc );
documentRepository.Update(doc);
documentRepository.UnitOfWork.Save();
しかし、私にとって本当にトリッキーなのは、ユーザーのSendDocumentForApprovalメソッド内で起こっていることです。
Document SendDocumentForApproval(document)
{
if(document == null) throw new ArgumentNullException(nameof(document));
// When document is sent for approval, it needs to initialize a new
// approval process and all states for the approval scheme's steps.
// So we need to get a type of the document and ask it to provide us
// ApprovalProcess that is based on ApprovalScheme.
document.SubmitApprove(); // DocumentSendForApprovalDomainEvent is raised
return document;
}
DocumentSendForApprovalDomainEventHandler:
Handle(dto)
{
// It seems very stupid because either I need get one more time document
// with the repository or I need to keep a reference to the instance of
// the document as a field in the dto argument of this handler.
Document doc = dto.Document;// looks terrible
// or
Document doc = documentRepository.GetDocument(dto.DocId);//looks also terrible because we have alredy used repository on this purpose in SendDocumentForApprovalCommandHandler
DocType docType = docTypeRepositoty.GetDocType(doc.TypeId);
ApprovalProcess approvalProcess = docType.InitNewApprovalProcessForDocument(doc.Id);
doc.AssignApprovalProcess(approvalProcess)
}
上記でモデル化したApprovalProcessを誰かが修正してくれませんか?または、私が正しい方向に進んでいるかどうか教えてください。
ここでの混乱の多くは、行動ではなくデータに焦点を合わせた結果だと思います。 DDDの目的は、システムの機能要件をモデル化して、ビジネスドメインの有用な要約を提供することです。つまり、behaviorに従ってモデル化し、動作をサポートするデータを実装の詳細にします。さあ、始めましょう!
あなたが記述しているシステムは、動作に関して、実際には非常に単純です。あなたのユースケースをリストすることから始めましょう(あなたの質問から離れて):
Document
Document
属性値を変更するDocument
上記を念頭に置いて、いくつかのコマンドハンドラーを作成します。
CreateDocumentHandler
DocumentType dt = documentTypes.Find( cmd.DocumentTypeId );
Document doc = dt.CreateEmptyDocument( cmd.UserId ); // factory method
documents.Add( doc );
ChangeDocumentHandler
Document doc = documents.Find( cmd.DocumentId );
doc.ChangeAttribute( cmd.AttributeName, cmd.AttributeValue );
documents.Save( doc );
ApproveDocumentHandler
Document doc = documents.Find( cmd.DocumentId );
doc.Approve(); // approval process is part of document
documents.Save( doc );
ここからは空欄に記入できると思います。