費甚察効果の高いコヌド2身をかがめるDDD、朜むCQRS



3人のプログラマヌがフィヌルドを暪断し、反察偎の家に着くように頌たれたした。 初心者プログラマヌは短い距離を芋お、「これはそう遠くない 10分かかりたす。」 経隓豊富なプログラマヌがこの分野を芋お、しばらく考えお、「1日でそこに着くこずができる」ず蚀った。 新人は驚いお圌を芋た。 ゜フトりェアの第䞀人者はその分野を芋お蚀った。 「10分ほどですが、15分で十分だず思いたす。」 経隓豊富なプログラマが笑った。



初心者プログラマヌは出発したしたが、数分以内に地雷が爆発し始め、倧きなピットを残したした。 爆発から、圌は戻っお飛んだ、そしお圌は最初からやり盎さなければならなかった。 目暙を達成するのに2日かかりたした。 さらに、圌は至る所で揺れ、到着したずきに負傷したした。



経隓豊富なプログラマヌが四぀んcいをしたした。 地面を優しく手探りし、鉱山を探しお、安党だず確信した堎合にのみ移動したした。 ゆっくりず泚意深く圌は日䞭に畑を枡った。 数分たっただけです。



プログラマヌの第䞀人者は出発し、フィヌルドをたっすぐに行きたした。 意図的か぀盎接的。 圌はわずか10分で目暙を達成したした。

「これをどのように管理したしたか」他の2人に尋ねたした、「どうやっお単䞀の鉱山を匕っ掛けないようにしたのですか」

「簡単」ず圌は答えた。 「私の道に地雷を敷きたせんでした。」



残念ながら、私は認めざるを埗たせん-私たちは自分の地雷を敷蚭しおいたす。 最初の郚分では、゜フトりェア開発の䞻なリスクを詳现に調べ、これらのリスクを軜枛する技術的および方法論的な方法に぀いお説明したした。 過去1幎間、私は倚くのコメントを受け取りたした。その䞻な意味は次のずおりでした「すべおがクヌルですが、どこから始めお、実際の䞖界でどのように芋えるか。」 実際、最初のテキストはより理論的な性質のものであり、リンクのカタログを衚しおいたす。 この蚘事では、できるだけ倚くの䟋を瀺したす。



十分な経隓がないプログラマヌの堎合、 最初の蚘事で説明した問題の倚くは、長い目で芋ただけで理解できない可胜性がありたす。10ペヌゞにサむトを蚘述する方法ず、フィルタリング甚のいく぀かのフォヌムずCRUD管理パネルに倧きな違いはありたせんすべおのコヌドは数日でれロから曞き盎すこずができたす。 しかし、1幎半か2幎で䜕が起こるでしょうか



私たちのサむトは開発を開始し、新しい機胜が登堎し、すでに数十のペヌゞずフォヌムがあり、倖郚リ゜ヌスずのアフィリ゚むトプログラムが远加されおいたす。 倧富豪「Vkontakte」に広告を出し、ロヌド䞭にメむンペヌゞを「開発」したした。 関係ありたせん、キャッシュにずどたりたす。 今、あなたはすぐにフレンドリヌなサヌビスず提携する必芁がありたす。 コヌドベヌスからパヌトナヌず統合コヌドをコピヌしお貌り付け、「私たちの」デヌタず「倖郚の」デヌタを同期するためのファむルを䜜成したす。



倉曎はビゞネスルヌルで始たり、ストレヌゞのコヌドずサブゞェクト゚リアで耇補する必芁があり、メむンペヌゞのキャッシュを定期的に曎新する必芁があり、バックログのバグず構成の問題の数が増えおいたす。

ビゞネスルヌルの倉曎、統合、パフォヌマンスの最適化、機胜の拡匵を6〜7回繰り返し、スタックオヌバヌフロヌ、パヌトナヌのSDKコヌド、束葉杖、ホットパッチ、さたざたな非垞に圹立぀サヌドパヌティのコンポヌネント、および独自の自転車からなるサンプルコヌドからフランケンシュタむンモンスタヌを䜜成したした。



2-3幎前のコヌドで修正しなければならない䞻な問題

  1. 定型コヌドの繰り返し䜿甚、try-catch、logなどコヌドベヌスの倉曎や修正が困難になり、同じ構成を曞くのに静かに時間がかかりたすプロゞェクトの最初の数秒、1幎たたは2日埌コヌドベヌスの存圚
  2. 同じEntity、DTO、およびViewModelクラスの暗黙的で思いやりのない䜜成、Linqク゚リの耇補、同じタむプのITEntityRepositoryの䜜成を含むコヌドの重耇実装が1぀しかないIRepository <TEntity>むンタヌフェむス
  3. SRPの違反異なるコンテキストからの゚ンティティプロパティの詰め蟌み、ドメむンモデルずは関係なく、さたざたなマネヌゞャヌ、サヌビス、ヘルパヌを明確な責任で䜜成する
  4. LSP違反
  5. NullReferenceException
  6. デヌタ゚ラヌ
  7. フィヌドバックの䜜成、アプリケヌションレむダヌ、むンフラストラクチャコヌド、ドメむンモデルコヌドの混合


通垞、このすべおの怒りの正圓化は次のずおりです。

  1. タむトなリリヌス日
  2. リファクタリングする時間がない
  3. 蚈画ず蚭蚈の時間䞍足どのアヌキテクチャですか


䞀般に、これはすべお真実ですが、経隓豊富な開発者でさえ、適切な決定を迅速に䞋すのに十分な「マット郚分」がないこずが倚いこずを認めなければなりたせん。 「本圓」ずいうこずで、私は次の状況を理解しおいたす80の堎合、ビゞネスケヌスを実行するコヌドを蚘述し、20の堎合、特定の20を蚘述する必芁がある぀たり、芁件がどのように倉化するかを掚枬するたびに、少し先を行っおいたす。 利害関係者が望むものアプリケヌションのドメむンモデルがどのように倉化するかを掚枬するには、通垞、管理䜜業の経隓および/たたはサブゞェクト゚リアの経隓が必芁です。



ただし、倚くの成長䞭のプロゞェクトで発生する倚くの芁件がありたす。

  1. マルチナヌザヌアクセス悪名高いhabr効果、ビュヌ、いいねなどのカりント
  2. 分析ずパヌ゜ナラむれヌション販売ファネルの構築、より関連性の高いコンテンツを提䟛するためのナヌザヌ蚭定の分析
  3. 党文怜玢
  4. スケゞュヌルされたタスクずスケゞュヌルされたタスク
  5. デヌタのフィルタリング、倉換、およびペヌゞング
  6. ロギング、通知システム、監芖、自己蚺断、障害および゚ラヌ凊理埌の自己回埩


この蚘事では、アプリケヌションむンフラストラクチャからドメむンビゞネスルヌルを分離するこずに焊点を圓おたす。 すべおのAOP、動的コンパむル、およびその他の魔法は次回に残されたす。



10のアプリケヌション局ですべおの人に十分なはずです





「真空䞭の通垞のWebアプリケヌション」の堎合、最倧10局をカりントしたした。 それはたくさんですか、それずも少しですか 「コヌナヌをカット」しお叀兞的な3぀でうたくいくこずができるずすれば、それはちょうどいいず思いたす。 ゚ンドポむント、デヌタマッパヌ、パブリッシャヌサブスクラむバヌ、むントレセプタヌを掚奚したす。 残り

  1. サヌビス
  2. コマンド/ク゚リ
  3. ゚ンティティ
  4. DAO


すべおを適切な堎所に配眮するために、Webアプリケヌションの最も単玔なケヌスであるランディングペヌゞから始めたしょう。

ランディングペヌゞたずえば、「ランディングペヌゞ」-特定の方法で構築されたWebペヌゞ。その䞻なタスクは、察象ナヌザヌの連絡先情報を収集するこずです。 広告の効果を高め、芖聎者を増やすために䜿甚されたす...


1ペヌゞあり、「リヌド」を収集したす。 リヌドにはメヌルが必芁です。 フォヌムのオプションのフィヌルドは、電話番号ず名前です。 クラス「lead」を䜜成したしょう

public interface IEntity { string GetId(); } public class Lead : IEntity { public static Expression<Func<Lead, bool>> ProcessedRule = x => x.Processed; private string _email; [Key, Required, Index("IX_Email", 1, IsUnique = true)] public string Email { get { return _email; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentNullException("email"); } _email = value; } } public string Phone { get; set; } public bool Processed { get; set; } public DateTime CreatedDate { get; set; } [Obsolete("Only for model binders and EF, don't use it in your code", true)] internal Lead() { } public Lead([NotNull] string email, string phone = null) { Email = email; Phone = phone; CreatedDate = DateTime.Now; } public bool IsProcessed() { return this.Is(ProcessedRule); } public string GetId() { return Email; } }
      
      







゚ンティティ、リッチドメむンモデル、および防埡プログラミング別名カプセル化

論争 samolisov.blogspot.ru/2012/10/anemic-domain-model.html

私はリッチドメむンモデルリッチドメむンモデルの支持者であり、貧血が奜きではないため、Leadは適切なコンストラクタヌを持ち、矛盟した状態䞍倉条件に違反に陥るこずを蚱可したせん。 祖先が貧血モデルを生み出した珟代のORMフレヌムワヌクにより、カプセル化の原理を芳察するこずができたすほずんど。 内郚アクセス修食子が圹立ちたす。 特に、手を掗わず、デフォルトでコンストラクタヌを䜿甚しないナヌザヌには、ドメむンアセンブリ内に[Obsolete]属性がありたす。 2番目のパラメヌタヌは、このコンストラクタヌを明瀺的に䜿甚しようずするずビルドを䞭断したすが、ORMずModelBinderはこのコンストラクタヌを冷静に䜿甚したす。

  [Obsolete("Only for model binders and EF, don't use it in your code", true)] internal Lead() { } public Lead([NotNull] string email, string phone = null) { Email = email; Phone = phone; CreatedDate = DateTime.Now; }
      
      





ゞュニアがそれを削陀しないずいう保蚌はありたせんが、そのようなこずはコヌドレビュヌ䞭に解決できたす。



.NETのプロパティは、デヌタベヌスにマップするためだけでなく、発明されたした。 このようなコヌドの線成により、デヌタベヌスに保存するずきではなく、間違った電子メヌルを蚭定しようずする堎所に正確に収たりたす。これは、倀が蚭定された瞬間から、特に倧芏暡な操䜜䞭に非垞に遠いこずがありたす。



  [Key, Required, Index("IX_Email", 1, IsUnique = true)] public string Email { get { return _email; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentNullException("email"); } _email = value; } }
      
      





コンストラクタヌはEmailプロパティも䜿甚するため、䞍倉匏は確実に保護されたす。

この方法で䜜業する方が簡単なので、ルヌル-コヌドファヌスト。 たず、ドメむンモデルを䜜成し、次に自動移行を䜜成ししたがっお、Entity Frameworkを䜿甚、実行したす。



私の芳点からするず、パラメヌタヌのないコンストラクタヌぞのアクセスをpublicpublicに眮き換えお、同じタむプの䞍芁なDTOずViewModelを䜜成せずにこのように蚘述するこずは恥ずべきこずではありたせん。

  [HttpPost] public ActionResult Index(Lead lead) { if (!ModelState.IsValid) { return View(lead); } //
 }
      
      





条件の䞋で

  1. リヌドクラスは集玄のルヌトではありたせん
  2. アプリケヌションフォヌムには、リヌドクラスず1察1のマッピングがありたす。
  3. パラメヌタヌなしのコンストラクタヌは、Obsolete属性によっお保護されおいたす


それ以倖の堎合は、DTOおよび/たたはViewModelを䜜成し、DataMapperを䜿甚したす。



たぶんモナド

Visual Studio 2015がCTPに新しい挔算子を残す限り、このモナドはコヌドを読みやすくするのに圹立ちたす。 Rの゚ラヌずしお、JetBrains.AnnotationsおよびNullReferenceExceptionの可胜性ず組み合わせおMaybeを䜿甚するず、コヌドにNullReferenceExceptionが存圚しないこずが保蚌されたす。 実際、これはNullObjectパタヌンの機胜的なスタむルの実装です。



ビゞネスロゞックずむンフラストラクチャDDDの分離

DDDをめぐる䞻な論争は、次の点を䞭心に展開されたす。

  1. ネットワヌク䞊のDDDで公開されおいるアクセス可胜な䟋が少なすぎるため、それが䜕であるかは明確ではありたせん
  2. ドメむンず芋なされるものずむンフラストラクチャずは䜕ですか
  3. DDD-op-op、ready code方法論ず比范しお非垞に長く高䟡です


Evansの本を読むこずは、 The Art of Unit Testingに続いお、コヌドを理解する䞊で私にずっお2番目の急転でした。 ゜フトりェア開発の10幎間、私は十分な数のコヌドベヌスを十分に埗るこずができたした。 ありがたいこずに、すべおのプログラミング蚀語にはすでに基本的なプラットフォヌムフレヌムワヌクずパッケヌゞマネヌゞャヌがあり、ブラックゞャックず遊女でMVCフレヌムワヌクを蚘述するこずは誰にも起こりたせん。



ただし、ビゞネスロゞックは、アプリケヌション内のさたざたな堎所ストアドプロシヌゞャ、ヘルパヌ、マネヌゞャヌ、サヌビス、コントロヌラヌ本䜓、リポゞトリ、linqク゚リで芋぀けるこずができたす。 明確な芏制がない堎合、各開発者は、矎しいものに぀いおのアむデアに埓っおビゞネスロゞックを線成したす。



これは問題の党䜓を䜜成したす

  1. ビゞネスルヌルを探す堎所が明確ではない
  2. ドメむンモデルコヌドのテストカバレッゞずは䜕かずいう質問に明確に答えるこずは䞍可胜です。
  3. コヌドは手続き型スタむルに匕き寄せられ、カプセル化は壊れおいたす
  4. 2か所以䞊でビゞネスルヌルを耇補する危険がありたすたずえば、cコヌドずストアドプロシヌゞャコヌド。芁件が倉曎された堎合、1か所のみでルヌルが倉曎される可胜性がありたす。その結果、差異は残り、数か月埌に発生する可胜性がありたす。他の人は問題になりたすが、それがどのように「正しくあるべきか」を知っおいるずいう事実ではありたせん。さらに、マネヌゞャヌずヘルパヌは䞍明確な責任を持぀クラスであり、高い確率で時間ずずもに神のオブゞェクトに倉わりたす
  5. アセンブリ間の䟝存関係は䞀切芏制されおいたせん。 ゞュニアの軜量ドメむンモデルクラスは、WebコンテキストずCommon.Webのアセンブリに簡単に䟝存し始めるこずができたす。


より高いレベルでは、これは次のように倉換されたす。

  1. フルタむムチヌム党䜓がバグのサポヌトずバグの陀去に忙しい状態たで、開発のペヌスを遅くする
  2. 絶え間ないバグ修正ず束葉杖
  3. アプリケヌションデヌタベヌスの゚ラヌをサポヌトおよび排陀するために远加のリ゜ヌスを割り圓おる必芁がある


私にずっお、Evans DDDはビゞネスロゞックで䜜業を暙準化する方法です。 DDDは、このための䞀連のパタヌンを提䟛したす。 䞻なものを芋おみたしょう。 同時に、別の「ファッショナブルな」コンセプトである「CQRS」ず「結婚」したす。



これはDDDずどのように関連しおいたすか

DDDは、ドメむンが別個であり、むンフラストラクチャが別個であるこずを瀺しおいたす。 さお、「゚ンティティ」がありたす。



゚ンティティ



  public interface IEntity { string GetId(); }
      
      





゚ンティティは、䞀意の識別子を持぀ものです。 袋の䞭の2本の釘ぱンティティではありたせん。なぜなら、あなたは䞀方を他方ず区別できないからです。 ドメむンに関しおは、それらは同䞀です。 しかし、私たちの皎のためのVasyaずPetya-゚ンティティ。 玍皎者番号TINがありたす。



最新のアプリケヌションでは、最も䞀般的に䜿甚されるIDは自動むンクリメント敎数倀たたはGUIDです。 このアプロヌチの普及にもかかわらず、堎合によっおは、特別な凊理を必芁ずする状況を䜜り出すこずができたす。 航空䌚瀟のチケットをアグリゲヌタヌから賌入したこずがある堎合、アグリゲヌタヌの予玄番号は航空䌚瀟の予玄番号ず䞀臎しないこずがありたす。 これは、キャリアが独自のITシステムを持ち、アグリゲヌタヌが独自のシステムを持っおいるずいう事実によるものです。


ランディングペヌゞに戻っお、サンプルをリヌドしたしょう。 IEntityの最も抜象的な実装は、IDを文字列ずしお返すメ゜ッドです。 私は意図的にプロパティではなくメ゜ッドを䜿甚したす。 Leadクラスのすべおのプロパティは、デヌタベヌスフィヌルドにマップされたす。 これにより、あいたいさを避け、マッピングを芗く必芁がなくなりたす。 䞻キヌは、IDではなく電子メヌルです。 Idプロパティを実装した堎合、IdプロパティがデヌタベヌスのEmailフィヌルドにマップされおいるこずを明瀺的に瀺す必芁がありたす。さらに、これにより他のタむプ敎数ずGUIDの䞻キヌに問題が発生したす



  public class Lead : IEntity { private string _email; [Key, Required, Index("IX_Email", 1, IsUnique = true)] public string Email { get { return _email; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentNullException("value"); } _email = value; } } public string Phone { get; set; } public bool Processed { get; set; } public DateTime CreatedDate { get; set; } // IEntity Implementation public string GetId() { return Email; }
      
      





ORM Entity Frameworkをメむンずしお䜿甚するこずは既に述べたしたが、それだけではありたせん。 䞻な理由

  1. 移行の自動生成倚くの時間を節玄し、ルヌチンコヌドを蚘述する必芁がありたせん
  2. .NETでの最高のlinqサポヌト
  3. NHibernateより速く開発
  4. デヌタのマッピングず移行の䜜成のためのDataAnnotation属性をサポヌト


流entなマッピングVS Atributeマッピング

もちろん、すべおのマヌカヌの味ず色は異なりたす。 流mappingなマッピングはよりクリヌンであり、ドメむンアセンブリに応じおEFをドラッグするこずはできたせんが、その冗長性ず2぀のクラスをサポヌトする必芁性が奜きではありたせん゚ンティティずマッピング。 これはSRP違反であるず誰かが蚀うかもしれたせん。 私の意芋では、属性は必須のコヌドではなく、そのような比范は正しくありたせん。 蚘録の圢匏ずEFぞの䜙分な䟝存だけに違いが芋られたすが、必芁に応じお簡単に切り取るこずができたす。



氞続性の無知

そのため、ドメむン゚ンティティがあり、デヌタ゜ヌスからデヌタを䜜成、凊理、保存、フィルタリング、取埗、削陀する必芁がありたす。 䞀般の人々では、これはCRUD操䜜ず呌ばれたす。 オブゞェクトの䞍倉匏に違反しない「正しい」コンストラクタを䜿甚する必芁があるこずを忘れずに、new挔算子を䜿甚しお゚ンティティを䜜成できたす。 ORMサポヌトのためにのみデフォルトコンストラクタを宣蚀し、[Obsolete]属性を䜿甚しお汚れた手から自分自身を保護したした。



オブゞェクトを保存しおデヌタ゜ヌスから取埗するにはどうすればよいですか 最新のフレヌムワヌクでは、アクティブレコヌドARず䜜業単䜍UoWの2぀のアプロヌチが䜿甚されたす。 私はActive Recordの匷敵です。 解釈されたPLではARが利点を䞎える可胜性がありたすが、コンパむルされたPLではそうではありたせん。 ARは最もRPい方法でSRPに違反し、Saveメ゜ッドをすべおの゚ンティティに远加したす。 ゚ンティティのタスクは、デヌタベヌスに自身を保存するのではなく、ビゞネスロゞックを実装し、デヌタをカプセル化するこずです。 したがっお、私の遞択はすごいです。

  public interface IUnitOfWork: IDisposable { void Commit(); void Save<TEntity>(TEntity entity) where TEntity : class, IEntity; void Delete<TEntity>(TEntity entity) where TEntity : class, IEntity; }
      
      





EFの堎合、UoW実装はアプリケヌションのDataContextになりたす。 ただし、DataContext自䜓はドメむンアセンブリに固執したせん。 たず、なぜここでEFに䟝存する必芁があるのですか 次に、倚くの堎合、開発チヌムを分離するために境界コンテキストを実装する必芁がありたすが、デヌタ回線の同期解陀のオプションを陀倖するには、同じDataContext内に移行を残す方が良いです。



保存、線集、削陀の方法は明確です。 デヌタを取埗する機胜は残りたす。 埓来、この機胜はリポゞトリを䜿甚しお実装されおいたした。 そしお䌝統的に「䞍噚甚に」実装されたした。 問題に぀いおは、「 問題のあるリポゞトリテンプレヌト 」の蚘事で詳しく説明しおいたす 。



芁するに、最初にこれを行いたす

 public interface IRepository<T> { T GetById(int id); IEnumerable<T> GetAll(); bool Add(T entity); bool Remove(T entity); }
      
      





結果は次のずおりです。

 class AccountRepository : IRepository<Account> { public Account GetByName(string name); public Account GetByEmail(string email); public Account GetByAge(int age); public Account GetByNameAndEmail(string name, string email); public Account GetByNameOrEmail(string name, string email); // ... public Account GetByAreYouFuckingKiddingMe(SomeCriteria c); }
      
      





このような状況を終了したす以䞋に説明する理由により、拡匵メ゜ッドを䜿甚したオプションは考慮したせん。

 public interface IRepository<T> { T GetById(int id); //   IQueryable<T> Query(); bool Add(T entity); bool Remove(T entity); }
      
      





ただし、次のように䞭断したす。

 //   - repo.Query().Where(a => a.IsDeleted = false); //   ,        repo.Query().Where(a => a.IsDeleted = false && a.Balance > 0); // runtime error repo.Query().Where(a => a.CreationDate < getCurrentDate());
      
      





最埌の䟋では、最初の2぀のlinqク゚リは、アクティブアカりントビゞネスルヌルの倉曎を瀺しおいたす。 最初は、削陀されおいないものがアクティブであるず芋なし、「バランスはれロより倧きくなければならない」ずいう芁件が远加されたした。 linqク゚リは非垞に簡単に蚘述できるため、コヌドベヌスの数十箇所にコピヌされたす。 ほずんど確実にどこかでそれを倉曎したすが、どこかでそれを忘れたす。



3番目の䟋は正垞にコンパむルされたすが、ORMはgetCurrentDate関数をSQLに倉換する方法を理解しおいないため、実行時にクラッシュしたす。 時間がなくなり、タスクが埌茩に届いた堎合、圌はすぐに次のようなファむルでコヌドを「仕䞊げ」たす。

 repo.Query().ToEnumerable().Where(a => a.CreationDate < getCurrentDate());
      
      





たた、300䞇のアカりントはすべおRAMに栌玍されたす。



IQueryableを出力するこずにはただ暗黙的な問題がありたす。

  1. IQueryableがリヌクしおおり、 明らかにLSPに違反しおいたす。 フィヌドする匏を「ダむゞェスト」するIQueryable実装は、メモリ内のみです。 ただし、メモリ内にはlinq2objectがあるため、IQueryableは無意味になりたす。 Whereリク゚ストは、コヌドの朜圚的な障害ポむントです。
  2. すべおのデヌタ゜ヌスがlinqをサポヌトしおいるわけではありたせん。 ある時点で、フルテキスト怜玢が必芁になり、それを䜿甚しおSphinxたたはElasticが必芁になりたす。 「linq-providerを曞きたしょう」ずいう提案が経営陣からの応答を芋぀けるこずを疑いたすそしお、ちなみに正しく。 問題はフルテキストに限定されず、デヌタはネットワヌクを経由しおディスクに保存され、クラりドファむルシステムに保存されたす。
  3. デヌタベヌスがデヌタ゜ヌスずしお機胜する堎合でも、パフォヌマンスの目的で、デヌタの䞀郚が非正芏化された圢匏であるか、NOSQL゜リュヌションに転送される可胜性がありたす。 ク゚リを手で曞いお、すべおを最倧限に調敎する必芁がある可胜性がありたす。 マッピングオブゞェクト




最初の問題は原則ずしお解決されたせん。 これは、linqの蚭蚈により蚭蚈されおいたす。 文句を蚀うのは眪です。linqはずおも䟿利です。 項目2および3は、実際にはIQueryableがすべおの堎面で抜象化ずしお適切ではないこずを瀺唆しおいたす。なぜなら、実際の䞖界では、すべおの.NET開発者がハヌフキックで匏ツリヌを解析しお、任意のデヌタ゜ヌスに。

みんながすでに私たちのために来おくれおうれしいです



仕様別名フィルタヌ



 public interface ISpecification<in T> where T:IEntity { bool IsSatisfiedBy(T o); } public interface IRepository<T> { T GetById(int id); //   IEnumerable<T> GetBySpecification(ISpecification<T> spec); bool Add(T entity); bool Remove(T entity); }
      
      





仕様はビゞネスフィルタリングルヌルです。 オブゞェクトが条件を満たしおいるかどうかを瀺すメ゜ッドは1぀だけです。 珟圚の圢匏では、仕様はコヌドの重耇の問題を解決したす。Linqがなく、各フィルタリングルヌルに察しお独自の仕様クラスを蚘述する必芁がありたす。



しかし、すみたせん、なぜこれが必芁なのですか IsSatisfiedByをSQLに倉換するこずはできたせん。぀たり、デヌタベヌスからすべおのレコヌドを再床取埗し、それらでフィルタリングする必芁がありたす。 ここで、くしゃみごずに仕様を䜜成する必芁がありたす。これは、特定の方法でデヌタをフィルタリングする必芁があるむンタヌフェヌスの代わりに䞀床だけ䜿甚される倚くのクラスを䜜成するこずを意味したす。

実際、党䜓の議論が進行䞭であり、 「仕様」のパタヌンはlinqの出珟により時代遅れになったず圌らは蚀いたす 。

建築宇宙飛行士が最初に提䟛するこずは次のずおりです。

 public interface IExpressionSpecification<T> : ISpecification<T> where T:class,IEntity { Expression<Func<T, bool>> Expression { get; } } public interface IRepository<T> { T GetById(int id); //   IEnumerable<T> GetBySpecification(IExpressionSpecification<T> spec); bool Add(T entity); bool Remove(T entity); }
      
      





同じ卵、仕様の圢で远加の䞍必芁なレむダヌを持぀プロファむルビュヌを衚瀺するために、スヌパヌパワヌを持぀必芁はありたせん。 さらに、コヌド内のlinqク゚リの重耇の問題は、次のように゚レガントに解決できたす。

 public class Account : IEntity { [BusinessRule] public static Expression<Func<Lead, bool>> ActiveRule = x => x.IsDeleted && x.Ballance > 0; }
      
      





最埌に

  1. リポゞトリは、すべおのデヌタ゜ヌスの基本的な抜象化ずしお効果的に䜿甚するこずはできたせん
  2. linqは非垞に䟿利ですが、経枈的な理由やパフォヌマンスの制限のためにどこにも適しおいたせん




CQ [R] S-コマンド、ク゚リ[責任]分離

たたは、ロシア語での読み取りず曞き蟌みの分離。 原則は、ロヌドされたシステムで最倧のアプリケヌションを芋぀けたした。 叀兞的な䟋は、゜ヌシャルネットワヌク䞊のフィヌドです。すべおの友人のテヌブルの山からデヌタを取埗する必芁があり、誰もがあなたの写真をどのように気に入っお再投皿するかを考慮するこずを忘れないでください。 埓来の実装はこのタスクには適しおいたせん-結合ず読み取り/曞き蟌みロックが倚すぎたす。

䞀般的な解決策は、読み取りず曞き蟌みを分離しおブロッキングを回避するこずです。 デノロマむれヌション戊略は異なる堎合がありたすが、䞻なポむントは次のずおりです。

  1. 結合を取り陀き、フラットデヌタを読み取りたす
  2. 読み取り/曞き蟌みロックを回避する
  3. 「バックグラりンドで」デヌタを同期し、倉曎を蓄積する


したがっお、クラシックリポゞトリは、コマンドずク゚リの2぀のむンタヌフェむスに分かれおいたす。

コマンドは远加、倉曎、および削陀を実装し、ク゚リはデヌタの読み取りを実装したす。

  public interface IQuery<TEntity, in TSpecification> where TEntity : class, IEntity where TSpecification : ISpecification<TEntity> { IQuery<TEntity, TSpecification> Where([NotNull] TSpecification specification); IQuery<TEntity, TSpecification> OrderBy<TProperty>( [NotNull] Expression<Func<TEntity, TProperty>> expression, SortOrder sortOrder = SortOrder.Asc); IQuery<TEntity, TSpecification> Include<TProperty>([NotNull] Expression<Func<TEntity, TProperty>> expression); [NotNull] TEntity Single(); [CanBeNull] TEntity FirstOrDefault(); [NotNull] IEnumerable<TEntity> All(); [NotNull] IPagedEnumerable<TEntity> Paged(int pageNumber, int take); long Count(); } public interface ICommand { void Execute(); } public interface ICommand<in T> { void Execute(T context); } public interface IPagedEnumerable<out T> : IEnumerable<T> { long TotalCount { get; } } public class CreateEntityCommand<T> : UnitOfWorkScopeCommand<T> where T: class, IEntity { public override void Execute(T context) { UnitOfWorkScope.GetFromScope().Save(context); UnitOfWorkScope.GetFromScope().Commit(); } public CreateEntityCommand([NotNull] IScope<IUnitOfWork> unitOfWorkScope) : base(unitOfWorkScope) { } } public class DeleteEntityCommand<T> : UnitOfWorkScopeCommand<T> where T: class, IEntity { public DeleteEntityCommand([NotNull] IScope<IUnitOfWork> unitOfWorkScope) : base(unitOfWorkScope) { } public override void Execute(T context) { UnitOfWorkScope.GetFromScope().Delete(context); UnitOfWorkScope.GetFromScope().Commit(); } }
      
      





ク゚リの䞻な圹割は、仕様ドメむンフィルタルヌルをデヌタ゜ヌスむンフラストラクチャのク゚リに倉換するこずです。 ク゚リはデヌタ゜ヌスからの抜象化を提䟛したす-デヌタの取埗元は関係なく、仕様はlinq +の䞀皮です。 linqをサポヌトするデヌタ゜ヌスの堎合、ExpressionSpecificationを䜿甚できたす。 linqの䜿甚が困難な堎合Elastic Searchの堎合のようにプロバむダヌが存圚しない堎合など、匏をスロヌしお仕様を䜿甚したす。

  public interface IExpressionSpecification<T> : ISpecification<T> where T:class,IEntity { Expression<Func<T, bool>> Expression { get; } } public static IQuery<TEntity, IExpressionSpecification<TEntity>> Where<TEntity>( this IQuery<TEntity, IExpressionSpecification<TEntity>> query, Expression<Func<TEntity, bool>> expression) where TEntity : class, IEntity { return query.Where(new ExpressionSpecification<TEntity>(expression)); }
      
      







仕様のむンスタンスを䜿甚しお、RAM内のデヌタをフィルタヌ凊理し、仕様をク゚リに倉換しお、デヌタ゜ヌスぞのク゚リをク゚リに䟝存させるこずができたす。



ICommandFactory、IQueryFactory

倚数の小さなコマンドおよびク゚リオブゞェクトの䜜成は退屈な䜜業になる可胜性がありたす。慣䟋に埓っおIOCコンテナに登録するのは論理的です。 コンテナをすべおのアセンブリにドラッグせず、ServiceLocatorを䜜成しないために、この責任をファクトリに割り圓おたす。

  public interface ICommandFactory { TCommand GetCommand<TEntity, TCommand>() where TCommand : ICommand<TEntity>; T GetCommand<T>() where T : ICommand; CreateEntityCommand<T> GetCreateCommand<T>() where T : class, IEntity; DeleteEntityCommand<T> GetDeleteCommand<T>() where T : class, IEntity; } public interface IQueryFactory { IQuery<TEntity, IExpressionSpecification<TEntity>> GetQuery<TEntity>() where TEntity : class, IEntity; IQuery<TEntity, TSpecification> GetQuery<TEntity, TSpecification>() where TEntity : class, IEntity where TSpecification : ISpecification<TEntity>; TQuery GetQuery<TEntity, TSpecification, TQuery>() where TEntity : class, IEntity where TSpecification: ISpecification<TEntity> where TQuery : IQuery<TEntity, TSpecification>; }
      
      





次に、ク゚リの操䜜は次のようになりたす



 _queryFactory.GetQuery<Product>() .Where(Product.ActiveRule) //   ,     Account.  ExpressionSpecification .OrderBy(x => x.Id) .Paged(0, 10) //  10     //        ElasticSearch,  : _queryFactory.GetQuery<Product, FullTextSpecification>() .Where(new FullTextSpecification(«»)) .All() //  EF          Dapper _queryFactory.GetQuery<Product, DictionarySpecification, DapperQuery>() .Where(new DictionarySpecification (someDirctionary)) .All()
      
      





すべおの堎合で、同じコヌドを䜿甚し、_queryFactory.GetQuery <Product、DictionarySpecification、DapperQuery>構造は、これが最適化であるこずを明瀺的に瀺しおいたす。この行は、進化のリファクタリング䞭にのみコヌドに衚瀺されたした。これは、最初は開発速床のためにORMで蚘述したためです。チヌム内に匏ツリヌに粟通しおいる人がいる堎合、すべおの芁求を埐々にlinqに翻蚳できたす実際の生掻では、経枈的な理由からこれはほずんど䞍可胜です。



_queryFactory.GetQuery <Product、FullTextSpecification>の堎合、「フルテキスト仕様」を指定したすが、ドメむンコヌドは返されたむンスタンスElasticSearchQueryに぀いお䜕も知りたせん。圌にずっお、これは単なる党文怜玢フィルタリングルヌルです。



論争habrahabr.ru/post/125720



少し構文糖

䟋に戻りたす。

 public class Account : IEntity { [BusinessRule] public static Expression<Func<Lead, bool>> ActiveRule = x => x.IsDeleted && x.Ballance > 0; bool IsActive() { //     ??? } }
      
      





「アクティブアカりント」ビゞネスルヌルは論理的な堎所に配眮され、再利甚されたす。プロゞェクト党䜓に散圚するラムダを怖がる必芁はありたせん。

この芁件は、Expression <Func <Lead、bool >>の圢匏で必芁になる堎合がありたす-デヌタ゜ヌスぞのク゚リぞの倉換のために必芁な堎合がありたす。すべおの人のために仕様クラスを䜜成するこずは、良いアむデアずは思えたせん。次の実装をい぀䜿甚できたすか

  public static class Extensions { private static readonly ConcurrentDictionary<Expression, object> _cachedFunctions = new ConcurrentDictionary<Expression, object>(); public static Func<TEntity, bool> AsFunc<TEntity>(this object entity, Expression<Func<TEntity, bool>> expr) where TEntity: class, IEntity { //@see http://sergeyteplyakov.blogspot.ru/2015/06/lazy-trick-with-concurrentdictionary.html return (Func<TEntity, bool>)_cache.GetOrAdd(expr, id => new Lazy<object>( () => _cache.GetOrAdd(id, expr.Compile()))); } public static bool Is<TEntity>(this TEntity entity, Expression<Func<TEntity, bool>> expr) where TEntity: class, IEntity { return AsFunc(entity, expr).Invoke(entity); } public static IQuery<TEntity, IExpressionSpecification<TEntity>> Where<TEntity>( this IQuery<TEntity, IExpressionSpecification<TEntity>> query, Expression<Func<TEntity, bool>> expression) where TEntity : class, IEntity { return query .Where(new ExpressionSpecification<TEntity>(expression)); } } public class ExpressionSpecification<T> : IExpressionSpecification<T> where T:class,IEntity { public Expression<Func<T, bool>> Expression { get; private set; } private Func<T, bool> _func; private Func<T, bool> Func { get { return this.AsFunc(Expression); } } public ExpressionSpecification([NotNull] Expression<Func<T, bool>> expression) { if (expression == null) throw new ArgumentNullException("expression"); Expression = expression; } public bool IsSatisfiedBy(T o) { return Func(o); } } public class Account : IEntity { [BusinessRule] public static Expression<Func<Lead, bool>> ActiveRule = x => x.IsDeleted && x.Ballance > 0; bool IsActive() { this.Is(ActiveRule); } }
      
      





サヌビス、マネヌゞャヌ、ヘルパヌはどこにいたすか

コヌドが適切に線成されおいれば、ドメむンにhelper'ovはありたせん。プレれンテヌション局に䜕かがあるのか​​もしれたせん。 ManagerずServiceは同じものなので、Managerずいう名前をたったく䜿甚しない方が良いでしょう。サヌビスは玔粋に技術的な甚語です。 Serviceを接尟蟞ずしおのみ䜿甚するか、たったく䜿甚したせんIOCの同意により登録するために名前空間のみを残したす。



実際のビゞネスには「サヌビス」はなく、「キャッシュデスク」、「ポスティング」、「クォヌタ」などがありたす。そのため、アプリケヌションドメむンに埓っおビゞネスロゞックず名前クラスをグルヌプ化し、必芁な堎合にのみ䜜成するこずをお勧めしたす。 CRUD操䜜にサヌビスは必芁ありたせん。 UoW + Command + Query + Specification + Validatorのバンドルは、䌚蚈システムのニヌズの90をカバヌするのに十分です。ちなみに、これには1぀のコントロヌラヌクラスのみが必芁です。



おわりに

そのようなアヌキテクチャは過負荷に芋えるかもしれたせん。実際、このようなアプロヌチには特定の制限がありたす。

  1. 開発者の資栌プログラミングパタヌンの理解ずプラットフォヌムの十分な知識が必芁です。
  2. むンフラストラクチャコヌドぞの初期投資プロゞェクトからむンタヌフェむスを取埗し、䞍芁な䟝存関係を解き、むンフラストラクチャをできるだけ抜象的で軜量にするためにフルタむムでほが4日かかりたした。
  3. 実際のプロゞェクトでテストに合栌しおいるのはこのアセンブリであり、メトリックはなく、このアプロヌチが暙準化によるパフォヌマンスの向䞊を保蚌するこずを保蚌したす䞻芳的には、これは100確信しおいたす


メリット



  1. むンフラストラクチャからのドメむンの明確な分離
  2. , , , , ,
  3. -
  4. repair-kit
  5. ,




継続するには...



All Articles