以下のようなコマンドがあるとします。
public sealed class UpdateExampleCommand
{
public int Field1 { get; set; }
public string Field2 { get; set; }
public bool Field3 { get; set; }
public double Field4 { get; set; }
public DateTime Field5 { get; set; }
}
ユーザーがUpdateExampleCommand
を実行できるかどうかを決定する権限があります。この形式の承認は非常に単純であり、コマンドを拒否または受け入れる前にデコレータでユーザーの権限を確認することで、分野横断的な問題として扱うことができます。
ただし、UpdateExampleCommand
によって変更された特定のフィールドをユーザーが編集できるかどうかを決定する権限もあります。
例えばユーザーはCan_Edit_Field_3
に変更を加える権限Field3
の値、Can_Edit_Field_4
に変更を加える権限Field4
、Can_Edit_Field_5
に変更を加える権限Field5
。
ソリューション1
これはビジネスロジックとみなされ、以下のように集約ルートに属しますか?
public void UpdateExample(int field1, string field2, bool field3, double field4, DateTime field5, Editor editor)
{
// Protect invariants
// ...
// Perform updates
Field1 = field1;
Field2 = field2;
// Editor is a value object encapsulating the user permissions.
// It is created in the command handler based on the user context
// and passed into the aggregate root's method.
//
// There are several other permissions like this that aren't strictly
// bound to roles, so I can't use an approach like the "CollaboratorService"
// from the IDDD book which performs an implicit permission check
// by creating a Moderator, Author, etc. role object if the current
// user has that role.
if (editor.CanEditField3) Field3 = field3;
if (editor.CanEditField4) Field4 = field4;
if (editor.CanEditField5) Field5 = field5;
}
ソリューション2
または、適切なメソッドを呼び出す前に、集約ルートに個別のメソッドを作成し、コマンドハンドラーでアクセス許可を確認することをお勧めしますか?
public async Task<Unit> Handle(UpdateExampleCommand request, CancellationToken cancellationToken)
{
// Load aggregate
// ...
entity.UpdateExample(request.Field1, request.Field2);
if (user.HasPermission("Can_Edit_Field_3"))
{
entity.UpdateField3(request.Field3);
}
if (user.HasPermission("Can_Edit_Field_4"))
{
entity.UpdateField4(request.Field4);
}
if (user.HasPermission("Can_Edit_Field_5"))
{
entity.UpdateField5(request.Field5);
}
// Save aggregate
// ...
}
ソリューション
または、それぞれ独自の承認デコレータを使用して複数の異なるコマンドを作成する必要がありますか?
例えばUpdateExampleCommand
、UpdateField3Command
、UpdateField4Command
、UpdateField5Command
これにより、ハンドラーとモデルからすべての承認の懸念が抽出されるため、コードはより簡潔で推論しやすくなりますが、単一のプロセスに対して集約が複数回読み込まれるため、パフォーマンスが犠牲になります(読み込まれる可能性があります8この例では、承認デコレータが集約の状態もチェックする必要がある場合)、適切なコマンドを発行する責任はアプリケーションクライアントに移されます。
結論
最終的には、ドメインによってモデル化されたビジネスプロセスに何が影響を与えるべきかわかりません。
コマンドが最初に単一のコマンドにされたのは、それがUIのレイアウト方法であり、ビジネスの一般的な動作方法だからです。ただし、UIがドメインモデルに影響を与えることを許可すると、承認ロジックがドメインモデルにリークします。
承認の懸念がドメインモデルに影響を与えることを許可すると(単一のコマンドが複数に分割されるため)、コードはより簡潔になりますが、アプリケーションクライアントはコマンドを正しい順序で呼び出す必要があるため、ビジネスプロセスの表現力が低下する可能性があります。 。
これらの状況の一般的な経験則はありますか、これを行う別の方法がありませんか?
ドメイン駆動設計の主な焦点は、ビジネスを最優先することです。アプリケーションの残りの部分(データベース、残り、入出力)はビジネスに依存する必要があります。
あなたのビジネスに基づいてこれを設計するとよいでしょう。したがって、ビジネスパーソンに、どちらのステートメント(1または2)がより自然に聞こえるかを尋ねることができます。 UXエキスパートにも質問をします。ただし、UI開発者にはありません。
ステートメント1:ユーザーは、5つのアイテムの変更を1つのショートでまとめて要求します。そして、変更のどの部分が成功したかを彼に知らせます。
ステートメント2:ユーザーは5つの異なる変更要求を行うことができます。そして私たちは彼に結果を知らせます
ステートメント1が自然に聞こえる場合は、ソリューション1または2を選択します(1と2のどちらを選択するかは、コードの編成方法によって異なります)ステートメント2が自然に聞こえる場合は、ソリューション3を選択します
私の頭に浮かぶ小さな質問はほとんどありません。彼らです
質問1:ユーザーが何かを実行するように要求した場合、その要求のステータスをユーザーに知らせるのは公平です。ユーザーが何らかの操作(更新5)を要求し、操作の一部が成功した場合、どのようにしてステータスをユーザーに通知するのでしょうか。 (RESTでのリクエストの部分的な成功について通知することは可能ですが、複雑です)
質問2:ビジネスに基づいて、「これらは個別の操作(コマンド)として整理する方がよい」と決定した場合。次に、5つの異なるコマンドがあり、UIには5つのコマンドメカニズムがあります。この段階で、各コマンドを実行する権限があるかどうかをユーザーに通知できます(おそらくコマンドを無効/有効にすることにより)。読み取りモデルによってこれを行う可能性があります
アプリケーションクライアントが正しい順序でコマンドを呼び出す責任を負うことは、滑りやすい経路をたどります。新しいフィールドの追加などの単純な変更は、システム全体に波及効果をもたらす可能性があります。したがって、承認プロセスをフィールド(ソリューション3)ごとに分割することはできません。
ソリューション2には副作用があり、時間の経過とともに非常に複雑になる可能性があります。これは、パーミッションは一般にペルソナにリンクされており、ペルソナごとに異なるためです。また、各テナントがユーザーのカスタムACLルールを定義できるという複雑さが加わることもあります。したがって、私はソリューション2が特定のポイントを超えてスケーリングしないようにします。
解決策1は適切な方法ですが、それでもIMHOは複雑すぎます。アクセス許可を動的に導出できるメタレイヤー(テナントまたはデプロイメントインスタンスごとに構成可能なJSON構造のようなもの)を導入すると便利です。これにより、必要に応じてカスタム定義のルールを作成し、それらをデータベース内のテーブルまたはJSON列/ドキュメントに格納することもできます。ただし、メタレイヤーを初めて構築する場合は、少し前もって努力する必要があります。
ソリューション1から始めて、時間の経過とともにコードに形成されるパターンを観察し、リファクタリングして複雑さを取り除き、必要に応じてメタレイヤーを導入します。