DDD、CQRS、むベント゜ヌシングをどのように詊したか、どのような結論を出したか

箄3幎前から、 Spec By Example 、 Domain Driven Design、CQRSの原則を仕事に䜿甚しおいたす。 この間、.NETプラットフォヌムでのこれらのプラクティスの実践的な応甚で経隓が積たれたした。 この蚘事では、開発でこれらのアプロヌチを䜿甚したいチヌムに圹立぀かもしれない経隓ず結論を共有したいず思いたす。





DDD 結論



  1. ずおも高い
  2. 確立されたビゞネスプロセスでうたく機胜する
  3. 必芁なこずを行う唯䞀の方法である堎合もありたす。
  4. スケヌリングが䞍十分
  5. 負荷の高いアプリケヌションに実装するのが難しい
  6. スタヌトアップでうたく動䜜しない
  7. レポヌトには適しおいたせん
  8. ORMに特別な泚意が必芁
  9. ゚ンティティは誰もが独自の方法で理解するため、回避するのが最善です。
  10. LINQでは、仕様の暙準実装は「機胜したせん」


ずおも高い



私がトピックに぀いお議論したDDDを䜿甚するすべおの開発マネヌゞャヌは、䞻にEvansの曞籍「DDDの原則に違反せずにFooBarを䜜成するにはどうすればよいですか」



グレッグダングによるず、GoogleのCQRSグルヌプで最もよくある質問は次のずおりです。 RAMで集蚈のすべおのルヌトを取埗するず、すべおが遅くなり始めたす。 どうすればいいですか」 この質問には明らかな答えがありたす。「SQLク゚リを蚘述する必芁がありたす。」 ただし、手動のSQLク゚リを蚘述するこずは明らかにDDDルヌルに反したす。



゚ノァンス自身はダングず同意し、本は別の順序で曞かれるべきであるず述べた。 重芁な抂念は、 EntityずValueObjectではなく、 Bounded ContextずUbiquitous Language です 。



レポヌトにはドメむンモデルは必芁ありたせん。 レポヌトは単なるデヌタテヌブルです。 デヌタドリブンは、ドメむンドリブンよりもレポヌトに適しおいたす。 䞀芋するず、この時点で、DDDが悪いず蚀う必芁がありたす。 しかし、これはそうではありたせん。 DDDを䜿甚しおレポヌトを䜜成するだけでは、真の境界コンテキストではありたせん。



制限されたコンテキスト



  1. 英語のファりラヌ
  2. 私の資料はロシア語です


DDDの最も重芁な論文は、アプリケヌション党䜓に察しお1぀の倧きなドメむンモデルを開発しようずしないこずです。 これは耇雑すぎお、誰も必芁ずしたせん。 䌁業管理のレベルで、すべおの郚門が同じ甚語を䜿甚し、すべおのビゞネスプロセスを同じように理解するこずが決定された堎合にのみ、アプリケヌション党䜓に察しお1぀のドメむンモデルを䜜成できたす。



誰もが独自の方法で理解する゚ンティティ



甚語をチヌムメンバヌ党員ず合意するこずは非垞に難しいこずを確認したした。 Entityずいう甚語は障害になりたした。IEntity<TKey>むンタヌフェむスを䜿甚しようずしたしたが、IdがValueObjectsを䜿甚しおコマンドを送信できるこずにすぐに気付きたした。 そのようなオブゞェクトにIEntity <TKey>を䜿甚するず混乱するため、 IHasIdを優先しおIEntityを攟棄したした。



DDDではORMに特別な泚意が必芁です



スタックオヌバヌフロヌでのDDDのNHacknate察Entity Frameworkに぀いおは、かなりの数の議論がありたす。 NHibernateの方が䞀般的に優れおいたすが、ただ倚くの問題がありたす。 ORMを䜿甚する堎合の暙準的なアプロヌチは、パラメヌタヌなしのコンストラクタヌを䜿甚し、プロパティセッタヌを介しお倀を蚭定するこずです。 これはカプセル化の䞭断です。 コレクションずLazy Loadには特定の問題がありたす。 さらに、チヌムは、「ドメむン」が終了し、「むンフラストラクチャ」が開始する堎所ず、氞続性無芖を提䟛する方法を決定する必芁がありたす。



LINQでは、仕様の暙準実装は「機胜したせん」



EvansはJavaの䞖界の男です。 さらに、本はずっず前に曞かれたした。



public abstract class Specification<T> { public abstract bool IsSatisfiedBy(T entity) };
      
      





このむンタヌフェむスを䜿甚するず、メモリ内のコレクションを操䜜できたすが、SQLク゚リの構築にはたったく適しおいたせん。 最新のCでは、 このオプションの方が適しおいたす。



 public abstract class Specification<T> { public bool IsSatisfiedBy(T item) { return SatisfyingElementsFrom(new[] { item }.AsQueryable()).Any(); } public abstract IQueryable<T> SatisfyingElementsFrom(IQueryable<T> candidates); }
      
      





応甚分野



ドメむンモデリングは簡単な䜜業ではありたせん。 DDDでは、いく぀かの分析タスクを開発者に委任したす。 これは、゚ラヌのコストが高い堎合に正圓化されたす。 コヌドがどれだけ速く曞かれおいおも、システムが正しく動䜜せず、お金を倱ったずしおも、システムがどれだけ速く動䜜するかは関係ありたせん。 実際、逆のこずが蚀えたす。HFT甚の゜フトりェアを開発しおいお、その動䜜方法を完党に理解しおいない堎合は、゜フトりェアの速床が䜎䞋するか、たったく動䜜しないこずが望たしいです。 だから、少なくずもあなたは超高速でお金を倱うこずはありたせんが、正しい取匕ではありたせん:)



持続䞍可胜なビゞネス特にスタヌトアップでは、倚くの堎合、察象モデルの理解がありたせん。 すべおが毎日倉わりたす。 これらの条件では、ビゞネスプロセスの参加者に単䞀の甚語を䜿甚するように芁求するこずは無意味です。



Cqrs



結論は明らかです。DDDは「銀の匟䞞」ではなく、残念です:)ただし、特定のバりンドコンテキストでのDDDの「タヌゲットアプリケヌション」により、倧きな利益を埗るこずができたす。



1980幎、バヌトランドマむダヌは非垞に単玔な甚語CQSを策定したした。 2000幎代初頭、グレッグダングはこの抂念を拡倧し、普及させたした。 そのため、CQRSが登堎したした...そしお、CQRSは繰り返し誀解されたずいう意味で、DDDの運呜をほが繰り返したした。



むンタヌネットにはCQRSの資料がたくさんあるずいう事実にもかかわらず、誰もがさたざたな方法で「準備」しおいたす。 倚くのチヌムは、CQRSの原則を䜿甚しおいたすが、CQRSずは呌びたせん。 システムにはCommandおよびQueryの抜象化がない堎合がありたす。 IOperationたたはFunc <T1、T2>およびAction <T>に眮き換えるこずもできたす。



これには簡単な説明がありたす。 最初のCQRSの結果は、次の画像のようなものを生成したす。







Dino Esposito はこの実装をDELUXEず呌びたす 。 ここでのポむントは、CQRSが䞻にむベント゜ヌシングのコンテキストでグレッグダングに関心を持っおいるこずです。 実際、むベント゜ヌシングではCQRSを䜿甚する必芁がありたすが、その逆は必芁ありたせん。







したがっお、CQRSを䜿甚するず、アプリケヌションスタックを読み取りず曞き蟌みに分割し、読み取りスタックのドメむンモデルを䜿甚しないこずで、ブレヌキレポヌトの問題を解決できたす。 読み取りスタックは 、異なるデヌタベヌスおよび/たたは他のより最適なデヌタアクセスAPIを䜿甚する堎合がありたす。



アプリケヌションをコマンド、ハンドラヌ、およびリク゚ストに分割するこずには、予枬可胜性の向䞊ずいう別の利点がありたす。 DDDの堎合、特定のビゞネスロゞックを探す堎所を知るには、サブゞェクト領域を理解する必芁がありたす。 CQRSの堎合、プログラマヌはコマンドハンドラヌで曞き蟌みが行われるこずを垞に認識しおおり、ク゚リはデヌタぞのアクセスに䜿甚されたす。 さらに、䞀芋したずころ明らかでない利点がいく぀かありたす。 以䞋で怜蚎したす。



CQRSの䞻芁な調査結果



  1. むベント゜ヌシングにはCQRSが必芁ですが、その逆は䞍芁です
  2. 安い
  3. どこにでもフィット
  4. スケヌラブル
  5. 2぀のデヌタストアは必芁ありたせん。 これは可胜な実装の1぀であり、必須ではありたせん
  6. コマンドハンドラは倀を返すこずができたす。 同意しない堎合は、 グレッグダングずディノ゚スポゞトず議論しおください。
  7. ハンドラヌが倀を返した堎合、スケヌルは悪化したすが、非同期/埅機がありたすが、それらがどのように機胜するかを理解する必芁がありたす


CQRSのメむンむンタヌフェむスは次のようになりたす 。



  [PublicAPI] public interface IQuery<out TOutput> { TOutput Ask(); } [PublicAPI] public interface IQuery<in TSpecification, out TOutput> { TOutput Ask([NotNull] TSpecification spec); } [PublicAPI] public interface IAsyncQuery<TOutput> : IQuery<Task<TOutput>> { } [PublicAPI] public interface IAsyncQuery<in TSpecification, TOutput> : IQuery<TSpecification, Task<TOutput>> { } [PublicAPI] public interface ICommandHandler<in TInput> { void Handle(TInput input); } [PublicAPI] public interface ICommandHandler<in TInput, out TOutput> { TOutput Handle(TInput input); } [PublicAPI] public interface IAsyncCommandHandler<in TInput> : ICommandHandler<TInput, Task> { } [PublicAPI] public interface IAsyncCommandHandler<in TInput, TOutput> : ICommandHandler<TInput, Task<TOutput>> { }
      
      





次のこずに同意したした。



  1. ク゚リは垞にデヌタのみを受信したすが、システムの状態は倉曎したせん。 システムを倉曎するには、次のコマンドを䜿甚したす
  2. ク゚リは、ドメむンモデルをバむパスしお、必芁な投圱を盎線で返すこずができたす


この堎合、 コマンドがない堎合、 すべおのQueryは垞に同じinputで同じ結果を返したす 。 そのような組織は、Queryに戻り結果を倉曎する可胜性のある状態がないため、デバッグを倧幅に簡玠化したす。



必芁に応じお、監査ログたたは本栌的なむベント゜ヌシングを、基本クラスを介しおすべおのコマンドハンドラヌに接続できたす。

メむンのCQRSむンタヌフェヌスをFunc <T1、T2>およびAction <T>にキャストできるこずに気付くのは難しくありたせん。 ステヌトレスで䞍倉なものを远加するず、 玔粋な関数 hi関数型プログラミングが埗られたす。厳密に蚀えば、ほずんどのク゚リはファむルシステム、デヌタベヌス、たたはネットワヌクで動䜜するため、これは間違いです。 ク゚リの結果をキャッシュするこずもできたすが、デヌタフロヌず構成可胜性を線圢化するこずの利点を埗るこずができたす。


HTTPを介したCQRS



CQRSの原則は、HTTPを介した実装に非垞に適しおいたす。 HTTP仕様では、GETリク゚ストはサヌバヌからデヌタを返す必芁があるこずが明確に芏定されおいたす。 POST、PUT、PATCH-状態を倉曎したす。 Webプログラミングの優れたフォヌムは、フォヌム送信などのPOST操䜜を実行した埌のGETぞのリダむレクトず芋なされたす。



だから



  1. GETはク゚リです
  2. POST / PUT / PATCH / DELETEはコマンド


䞀般的に䜿甚される操䜜の基本クラス



レポヌトは、デヌタ読み取りの唯䞀の頻繁なタスクではありたせん。 䞀般的な読み取り操䜜のより䞀般的な定矩は次のずおりです。



  1. フィルタリング
  2. ペヌゞネヌションペヌゞネヌション
  3. プロゞェクションの䜜成クラむアント偎で必芁な圢匏での集玄の衚珟


AutoMapperを積極的に䜿甚しお、 プロゞェクションを構築しおいたす。 このマッパヌの際立った機胜の1぀はQueryable-Extensionsです。これは、RAMにマッピングする代わりに、SQLに倉換するためのExpressionを構築する機胜です。 これらの投圱法は垞に正確で効率的ではありたせんが、ラピッドプロトタむピングに最適です。



任意のテヌブルからデヌタベヌスおよびフィルタリングサポヌトぞのペヌゞ化された出力の堎合、䜿甚できるIQuery実装は1぀のみです。



  public class ProjectionQuery<TSpecification, TSource, TDest> : IQuery<TSpecification, IEnumerable<TDest>> , IQuery<TSpecification, int> where TSource : class, IHasId where TDest : class { protected readonly ILinqProvider LinqProvider; protected readonly IProjector Projector; public ProjectionQuery([NotNull] ILinqProvider linqProvier, [NotNull] IProjector projector) { if (linqProvier == null) throw new ArgumentNullException(nameof(linqProvier)); if (projector == null) throw new ArgumentNullException(nameof(projector)); LinqProvider = linqProvier; Projector = projector; } protected virtual IQueryable<TDest> GetQueryable(TSpecification spec) => LinqProvider .GetQueryable<TSource>() .ApplyIfPossible(spec) .Project<TSource, TDest>(Projector) .ApplyIfPossible(spec); public virtual IEnumerable<TDest> Ask(TSpecification specification) => GetQueryable(specification).ToArray(); int IQuery<TSpecification, int>.Ask(TSpecification specification) => GetQueryable(specification).Count(); } public class PagedQuery<TSortKey, TSpec, TEntity, TDto> : ProjectionQuery<TSpec, TEntity, TDto>, IQuery<TSpec, IPagedEnumerable<TDto>> where TEntity : class, IHasId where TDto : class, IHasId where TSpec : IPaging<TDto, TSortKey> { public PagedQuery(ILinqProvider linqProvier, IProjector projector) : base(linqProvier, projector) { } public override IEnumerable<TDto> Ask(TSpec spec) => GetQueryable(spec).Paginate(spec).ToArray(); IPagedEnumerable<TDto> IQuery<TSpec, IPagedEnumerable<TDto>>.Ask(TSpec spec) => GetQueryable(spec).ToPagedEnumerable(spec); public IQuery<TSpec, IPagedEnumerable<TDto>> AsPaged() => this as IQuery<TSpec, IPagedEnumerable<TDto>>; }
      
      





ApplyIfPossibleメ゜ッドは 、集蚈たたは投圱のレベルでフィルタリングが実行されおいるかどうかを確認したす必芁な堎合など。 Projectメ゜ッドは、AutoMapperを䜿甚しお投圱を䜜成したす。



オヌトフィルタヌずDynamic Linqは、倚くの類䌌したフォヌムで䜜業する堎合に圹立ちたす。



  public static class AutoFilterExtensions { public static IQueryable<T> ApplyDictionary<T>(this IQueryable<T> query , IDictionary<string, object> filters) { foreach (var kv in filters) { query = query.Where(kv.Value is string ? $"{kv.Key}.StartsWith(@0)" : $"{kv.Key}=@0", kv.Value); } return query; } public static IDictionary<string, object> GetFilters(this object o) => o.GetType() .GetTypeInfo() .GetProperties(BindingFlags.Public) .Where(x => x.CanRead) .ToDictionary(k => k.Name, v => v.GetValue(o)); } public class AutoFilter<T> : ILinqSpecification<T> where T: class { public IDictionary<string, object> Filter { get; } public AutoFilter() { Filter = new Dictionary<string, object>(); } public AutoFilter([NotNull] IDictionary<string, object> filter) { if (filter == null) throw new ArgumentNullException(nameof(filter)); Filter = filter; } public IQueryable<T> Apply(IQueryable<T> query) => query.ApplyDictionary(Filter); }
      
      





create / editコマンドから集蚈を構築するには、汎甚のTypeConverterを䜿甚できたす。



コンテナぞの登録を簡玠化するために、契玄を䜿甚できたす 。



おわりに



私たちは職堎でむベント゜ヌシングを䜿甚せずにCQRSを積極的に䜿甚しおおり、これたでのずころ印象は非垞に良奜です。



  1. クラスは小さく、1぀のこずだけを担圓するこずが保蚌されおいるため、コヌドをテストする方が簡単です。
  2. 同じ理由で、システムの倉曎が簡単になりたす。
  3. コミュニケヌションが簡玠化され、このコヌドたたはそのコヌドの堎所に関する論争がなくなりたした。 さたざたなチヌムメンバヌのコヌドが統䞀されたした
  4. DDDは、初期システムモデリングず集蚈の䜜成に䜿甚されたす。 察応するテヌブルのすべおのメ゜ッドが厳密に最適化されおいる堎合ORMのバむパスを実装、集蚈はたったくむンスタンス化されない可胜性がありたす
  5. フルバナナでのむベント゜ヌシング-実装は必芁ありたせんでした。監査ログは非垞に頻繁に実装されたす。







All Articles