DDDのリッチドメインモデルを使用するときに子コレクションで動作するHTTPのPUTを実装するにはどうすればよいですか?
うまくカプセル化されたアイテムのコレクションを持つ集約ルートがあるとします。
(簡潔にするため、Idなどの永続性固有のプロパティは省略しました)
public class Foo : IAggregateRoot
{
private readonly List<Bar> _items = new List<Bar>();
public IReadOnlyCollection<Bar> Items => _items;
public void AddItem(Bar bar)
{
_items.Add(bar);
}
public void RemoveItem(Bar bar)
{
_items.Remove(bar);
}
}
ここで、PUT /foos/{id}
を実装して、次のようにDTOを渡します。
public class FooDto
{
public IEnumerable<BarDto> Items { get; set }
}
ここでの問題は、FooDto
をFoo
に単純にマップできないことです。特にBar
から一部のItems
を削除する場合は、解決が複雑な問題になります。
2つのオプションがあります。
POST /foos/{id}/items
やDELETE /foos/{fooId}/items/{itemId}
のように、アイテムを追加および削除するための2つの個別のルートを作成しますAddItem
またはRemoveItem
を呼び出しますここに欠けているものや間違っていることはありますか?貧血ドメインモデルを使用してWeb APIの実装を簡略化した方がよいでしょうか?それは、Web APIが私のドメインモデルの外観を決定することを意味し、それは少し間違っていると思います。
解決策を探しているときに私が見つけた同様の質問: RESTful APIは貧血ドメインモデルを奨励する傾向がありますか?
オプション#2を「ツイストロジック」とは見なしません。
等価関数(2つのBar
sが同じであるかどうかを判別するための関数)が必要で、次のようにします。
toAdd = elements in newItems that don't exist in currentItems
toDelete = elements currentItems that don't exist in newItems
C#はわかりませんが、疑似コードでは次のようになります。
for e in newItems:
if not e in currentItems:
currentItems.add(e)
for e in currentItems:
if not e in newItems:
currentItems.remove(e)
DDDのリッチドメインモデルを使用するときに子コレクションで動作するHTTPのPUTを実装する方法
大変な困難を伴います。
[〜#〜] put [〜#〜] 、たとえば [〜#〜] delete [〜#〜] および [〜#〜] patch [ 〜#〜] 、リモートオーサリングセマンティクスがあります。 「リソースをこのようにする」。
基本的な考え方は、メディアタイプを理解するHTTP対応のエディターを使用し、それを使用して新しい表現をロードし、それを変更してから、変更を保存できるということです。Originサーバーがそれを保存する方法について何も知る必要はありません。情報。
したがって、新しい表現をコマンドに変換して独自のバッキングストレージに送信する方法を理解するのは、サーバーの負担です。
考えられる答えの1つは、メディアの種類により多くの負担をかけることです。リソースは複数の表現を持つことができ、すべてのリソースでPUT
をサポートする必要はありません。 415 Unsupported Media Type
は、ドメインモデルが理解できるメッセージに変換できないPUT
を拒否する必要がある場合のために用意されています。
そのフローは次のようになります
GET /foo
Content-Type: application/json
そして、この表現を見る消費者は、リソースを編集したいと思っています。したがって、編集可能な表現を求めます
GET /foo
Content-Type: application/vnd.hacky-workarounds.edit+json
PUT /foo
Content-Type: application/vnd.hacky-workarounds.edit+json
application/json-patch + json に類似した表現を想像するかもしれません。ドメインモデルが理解するコマンドメッセージと一致する操作と、初期状態を説明するクエリ操作(たとえば、ハッシュ現在のツリーの)。
スキーマによっては、GETを実行しなくても、クライアントが独自に元のDTOを編集可能な表現に変換できる場合があります。 HTTPはステートレスなので、後続のPUT
は関係なく同じように動作するはずです。
考慮すべきもう1つのアプローチは、ドメインモデルに2つの責任があることです。状態が内部的に一貫していることを確認することと、遷移が有効であることを確認することです。つまり、オリジンサーバーは必ずしもビジネスルール自体を適用する必要はなく、ルールが正しく適用されていることを検証するだけです。
したがって、DTOが内部的に一貫していることを確認し、古い状態から新しい状態への移行が正当であることを確認します。これらの両方が保持されている場合は、自分で作業を繰り返すことを心配せずに結果を保存します。
しかし、本当の答えはあなたが嫌いなものです-2つの表現を比較し、一方を他方に変換するために必要なコマンドをリバースエンジニアリングしてから、ドメインモデルを介してそれらのコマンドを適用します。
リソースの作成に加えて、リソースの作成にPUTを使用できます(クライアントがURIをある程度制御できるという概念に慣れている場合)。したがって、コマンドメッセージの表現を新しいリソースにPUTできます。おそらく If-None-Match ヘッダーを使用して、特定のURIがすでに使用されていないことを確認します。したがって、各コマンドは、クライアントによって選択された一意のURIを取得します。その場合に得られないのは、DTOのキャッシュの無効化です(これには、独自のURIが既にあります)。
クライアントがコマンドメッセージリソースのURIを選択できるようにすることは、クライアントが独自の 相関識別子 を選択できるようにすることと類似しています。
ほとんどの設計決定と同様に、答えは「依存する」です。
Foo
を更新するには、通常、Bar
のコレクションも更新する必要がありますか?同じ作業単位で更新する必要がありますか? Bar
は値オブジェクトですか?これらの質問の1つ以上の答えが「はい」である場合、オプション2がおそらく唯一の行動方針です。これらの条件下では、FooDTO
のItems
コレクションを、作業単位の完了時のFoo
のItems
コレクションの最終的な状態の表現と見なすことができます。また、Bar
は値オブジェクトであるため、それらを比較することは簡単です。
一方、Bar
がエンティティであり、Foo
以外の独自のID(および場合によっては有効期間)を持っている場合、またはBar
を更新できることが予想される場合Foo
への対応する変更を必要とせずに、オプション1の方が適している場合があります。
もう1つの可能性は、リッチドメインモデルがこの特定のアプリケーションに最適なアーキテクチャではないことです。 Evan'sは、DDDが考え方であり、リッチドメインモデル、リポジトリ、サービスなどの完全な実装には適切ではないアプリケーションがたくさんあることをすぐに指摘します。ドメインモデリングまたはユビキタス言語の確立。