CQRS は非常に理にかなっています。ただし、変更追跡を提供する ORM を使用するアプローチとは相容れないようです。オブジェクトを RO として取得するためのクエリ用の「チャネル」が 1 つある場合、追跡する変更はありません。CUD+ コマンド用の別のチャネルがある場合、軽い DTO を使用した RPC アプローチのほうが多く、自己追跡エンティティを送信して下位層でマージします。
これら 2 つのアプローチ (CQRS / ORM + STE) をどのように調和させることができますか?
CQRS は非常に理にかなっています。ただし、変更追跡を提供する ORM を使用するアプローチとは相容れないようです。オブジェクトを RO として取得するためのクエリ用の「チャネル」が 1 つある場合、追跡する変更はありません。CUD+ コマンド用の別のチャネルがある場合、軽い DTO を使用した RPC アプローチのほうが多く、自己追跡エンティティを送信して下位層でマージします。
これら 2 つのアプローチ (CQRS / ORM + STE) をどのように調和させることができますか?
CQRS アプリケーションを構築する一般的な方法は、コマンド ハンドラー内のドメイン モデルに基づいて動作することです。
コマンド DTO を送信し、ハンドラーでドメイン オブジェクトのメソッドを呼び出します (ドメイン エンティティのプロパティを設定しないでください。これは主要なアンチパターンです)。
ドメイン オブジェクトのメソッドは、ドメインの内部状態の変更を担当します。
その時点で、ORM はドメイン オブジェクトの内部状態への変更を永続化する責任があります。
CQRS アプリケーションを構造化するこの方法では、イベント ソーシングは使用されませんが、ORM とセルフ トラッキング エンティティが使用されます。
これは非常に単純化された例です。
public class AccountController
{
[HttpPost]
public ActionResult ChangePreferredStatus(Guid accountId, bool isPreferred)
{
if (isPreferred) {
// in response to user action, our controller commands our application to carry out an operation/state transition
Bus.SendCommand(new MakeAccountPreferredCommand(accountId));
}
}
}
public class MakeAccountPreferredCommandHander : IHandle<MakeAccountPreferredCommand>
{
public void Handle(MakeAccountPreferredCommand command)
{
using (var uow = new UnitOfWork()) {
var account = accountRepository.Get(command.AccountId);
if (account != null) {
// we tell the domain what we want it to do, we DO NOT fiddle with its state
account.MakePreferred();
}
uow.Accept(); // accepting the uow saves any changes to self-tracked entities
}
}
}
public class Account : SelfTrackedEntity
{
private Guid accountId;
private bool isPreferred; // ORM tracked *private* state
public void MakePreferred()
{
// entities are responsible for their own state and changing it
ValidateForMakePreferred();
isPreferred = true;
RaiseEvent(new AccountMadePreferredEvent(accountId));
}
}