実際のドメむン駆動蚭蚈

゚ノァンスは良いアむデアで良い本を曞きたした。 しかし、これらのアむデアには方法論的根拠がありたせん。 経隓豊富な開発者ずアヌキテクトは、顧客の察象領域にできるだけ近づける必芁があるこず、顧客ず話す必芁があるこずを盎感的に理解しおいたす。 しかし、ナビキタス蚀語ず顧客の実際の蚀語のコンプラむアンスに぀いおプロゞェクトを評䟡する方法が明確ではありたせんか ドメむンが境界付きコンテキストに正しく分割されおいるこずをどのように理解するのですか プロゞェクトでDDDが䜿甚されおいるかどうかを確認する方法

最埌の点は特に重芁です。 圌のスピヌチの1぀で、グレッグダングはDDDを実践しおいる人々の手を䞊げるように求めたした。 そしお、圌は、䞀連のパブリックゲッタヌずセッタヌでクラスを䜜成し、「サヌビス」ず「ヘルパヌ」のロゞックを持ち、それをDDDず呌ぶ人を陀倖するように芁求したした。 ホヌルにくすくす笑いがありたした:)

DDDスタむルでビゞネスロゞックを構成する方法 「動䜜」を保存する堎所サヌビス、゚ンティティ、拡匵メ゜ッド、たたはどこでも少しですか この蚘事では、サブゞェクト領域の蚭蚈方法ず䜿甚するルヌルに぀いお説明したす。

すべおの人がうそを぀く


もちろん、具䜓的にはありたせん:)実際には、ビゞネスアプリケヌションは幅広いタスク甚に䜜成され、さたざたなナヌザヌグルヌプの関心を満たしたす。 せいぜい、最初から最埌たでビゞネスプロセスを理解できるのはトップマネゞメントだけです。 ちなみに、たれに誀解しないでください。 区画内では、䞀郚のみが衚瀺されたす。 したがっお、すべおの利害関係者ぞのむンタビュヌの結果は通垞、矛盟のも぀れになりたす。 この芏則から次のこずがわかりたす。

最初に、分析、次に蚭蚈、そしおそれから-開発


デヌタベヌス構造たたはクラスのセットではなく、ビゞネスプロセスから開始する必芁がありたす。 BPMNずUMLアクティビティをテストケヌスず組み合わせお䜿甚​​したす。 チャヌトは、芏栌に粟通しおいない人でもよく読たれたす。 衚圢匏のテストケヌスは、境界ケヌスをより適切に識別し、矛盟を排陀するのに圹立ちたす。

アブストラクトトヌクは時間の無駄です。 人々は詳现は重芁ではなく、「すべおがすでに明確であるため、それらを議論する必芁はたったくない」ず確信しおいたす。 テストケヌスの衚に蚘入するリク゚ストは、オプションが実際には3ではなく26であるこずを明確に瀺しおいたすこれは誇匵ではなく、プロゞェクトの1぀での分析の結果です。

衚ずグラフは、ビゞネス、分析、開発の間の䞻芁なコミュニケヌションツヌルです。 BPMNダむアグラムずテストケヌスのテヌブルのコンパむルず䞊行しお、 プロゞェクトのシ゜ヌラスに甚語を曞き始めたす。 蟞曞は、埌で゚ンティティの蚭蚈に圹立ちたす。

コンテキストを遞択


組織党䜓で単䞀の䞀貫した蚀語を䜿甚するポリシヌがトップ管理レベルで採甚および実装されおいる堎合にのみ、アプリケヌション党䜓の単䞀のサブゞェクトモデルを䜜成できたす。 すなわち 営業郚門が制䜜に「アカりント」ず蚀うず、䞡者は同じ蚀葉を理解したす。 これは同䞀のアカりントであり、「CRMアカりント」や「クラむアントの法人」ではありたせん。

実生掻では、私はこれを芋たこずがない。 したがっお、察象モデルをすぐにいく぀かの郚分に倧たかに「カット」するこずが望たしい。 接続が少ないほど良い。 しかし、通垞、特定の䞀般甚語のセットを芋぀けるこずが刀明しおいたす。 私はそれを䞻題領域の䞭栞ず呌んでいたす。 コンテキストはカヌネルに䟝存する堎合がありたす。 コンテキスト間の䟝存関係を回避するこずを匷くお勧めしたす。 朜圚的に、このアプロヌチは栞の「膚匵」に぀ながりたすが、コンテキストの盞互䟝存性は匷力な接続性を生み出し、これは「厚い」栞よりも悪いです。

建築



ポヌトずアダプタ、 タマネギアヌキテクチャ 、 クリヌンアヌキテクチャ -これらのアプロヌチはすべお、 ドメむンをアプリケヌションのコアずしお䜿甚するずいう考え方に基づいおいたす 。 ゚ノァンスは、「ドメむン」および「むンフラストラクチャ」に぀いお話すずきに、この問題に䜕気なく察凊したす。 ビゞネスロゞックは、「トランザクション」、「デヌタベヌス」、「コントロヌラヌ」、「遅延負荷」などの抂念では動䜜したせん。 nå±€-アヌキテクチャでは、これらの抂念を広めるこずはできたせん。 芁求はコントロヌラヌに送られ、「ビゞネスロゞック」に転送され、「ビゞネスロゞック」はDALず察話したす。 たた、 DALは連続した「トランザクション」、「テヌブル」、「ロック」などです。 Clean Architectureを䜿甚するず、䟝存関係を反転し、パをカツレツから分離できたす。 もちろん、実装の詳现を完党に無芖するこずは䞍可胜です。 ずにかく、RDBMS、ORM、ネットワヌキングはそれらの制限を課したす。 ただし、 Clean Architectureを䜿甚する堎合はClean Architectureこれを制埡できたす。 n-layerでは、最䞋局のストレヌゞ構造により、「単䞀蚀語」 n-layer固執するこずははるかに困難です。

Clean Architectureは、 Bounded Context.うたく機胜しBounded Context. 異なるコンテキストは異なるサブシステムを衚す堎合がありたす。 単玔なコンテキストは、単玔なCRUD実装するのが最適です。 非察称ロヌドコンテキストの堎合、 CQRSが最適です 。 監査ログを必芁ずするサブシステムでは、むベント゜ヌシングを䜿甚するのが理にかなっおいたす。 垯域幅ず埅機時間の制限がある読み取りず曞き蟌みが読み蟌たれたサブシステムの堎合、むベント駆動型のアプロヌチを怜蚎するのは理にかなっおいたす。 䞀芋、これは䞍快に思えるかもしれたせん。 たずえば、 CRUDサブシステムで䜜業し、 CQRSサブシステムからタスクを受け取りたした。 しばらくの間、これらすべおのCommandずQuery新しいゲヌトずしお芋る必芁がありたす。 別の方法-システムを単䞀のスタむルで蚭蚈する-は近芖県的です。 アヌキテクチャはツヌルのセットであり、各ツヌルは特定のタスクに適しおいたす。

プロゞェクト構造


.NETプロゞェクトを次のように構成したす。

 /App /ProjectName.Web.Public /ProjectName.Web.Admin /ProjectName.Web.SomeOtherStuff /Domain /ProjectName.Domain.Core /ProjectName.Domain.BoundedContext1 /ProjectName.Domain.BoundedContext1.Services /ProjectName.Domain.BoundedContext2 /ProjectName.Domain.BoundedContext2.Command /ProjectName.Domain.BoundedContext2.Query /ProjectName.Domain.BoundedContext3 /Data /ProjectName.Data /Libs /Problem1Resolver /Problem2Resolver 

Libsフォルダヌのプロゞェクトはドメむンに䟝存したせん。 レポヌト、csv解析、キャッシングメカニズムなど、ロヌカルの問題のみを解決したす。 ドメむン構造はBoundedContext'察応しBoundedContext' 。 DomainフォルダヌのプロゞェクトはDataから独立しおいたす。 DataはDbContext 、移行、DALに関連する構成がありたす。 Dataは、移行を構築するためのDomain゚ンティティに䟝存したす。 Appフォルダヌのプロゞェクトは、IOCコンテナヌを䜿甚しお䟝存関係を泚入したす。 したがっお、ドメむンコヌドをむンフラストラクチャから最倧限に分離するこずが可胜です。

゚ンティティのモデリング


゚ンティティずは、䞀意の識別子を持぀ドメむンのオブゞェクトを意味したす。 たずえば、特定の郚門で認定を取埗するずいう文脈でロシアの䌚瀟を説明するクラスを取り䞊げたす。

 [DisplayName("  ()")] public class Company : LongIdBase , IHasState<CompanyState> { public static class Specs { public static Spec<Supplier> ByInnAndKpp(string inn, string kpp) => new Spec<Supplier>(x => x.Inn == inn && x.Kpp == kpp); public static Spec<Supplier> ByInn(string inn) => new Spec<Supplier>(x => x.Inn == inn); } //  EF protected Company () { } public Company (string inn, string kpp) { DangerouslyChangeInnAndKpp(inn, kpp); } public void DangerouslyChangeInnAndKpp(string inn, string kpp) { Inn = inn.NullIfEmpty() ?? throw new ArgumentNullException(nameof(inn)); Kpp = kpp.NullIfEmpty() ?? throw new ArgumentNullException(nameof(kpp)); this.ValidateProperties(); } [Display(Name = "")] [Required] [DisplayFormat(ConvertEmptyStringToNull = true)] [Inn] public string Inn { get; protected set; } [Display(Name = "")] [DisplayFormat(ConvertEmptyStringToNull = true)] [Kpp] public string Kpp { get; protected set; } [Display(Name = " ")] public CompanyState State { get; protected set; } [DisplayFormat(ConvertEmptyStringToNull = true)] public string Comment { get; protected set; } [Display(Name = "  ")] public DateTime? StateChangeDate { get; protected set; } public void Accept() { StateChangeDate = DateTime.UtcNow; State = AccreditationState.Accredited; } public void Decline(string comment) { StateChangeDate = DateTime.UtcNow; State = AccreditationState.Declined; Comment = comment.NullIfEmpty() ?? throw new ArgumentNullException(nameof(comment)); } 

適切な集玄ず関係を遞択するには、倚くの堎合、1回の反埩だけでは䞍十分です。 最初に、クラスの基本構造をマップし、1察1、1察倚、および倚察倚の関係を定矩し、デヌタ構造を説明したす。 次に、ビゞネスプロセスごずに構造をトレヌスし、BMPNずテストケヌスで確認したす。 いく぀かのケヌスが構造に適合しない堎合、蚭蚈䞭に間違いが発生し、構造を倉曎する必芁がありたす。 結果ずしお生じる構造は、ダむアグラムの圢匏で配眮でき、さらに䞻題分野の専門家ず合意できたす。

専門家は、蚭蚈゚ラヌや䞍正確さを指摘する堎合がありたす。 その過皋で、䞀郚の゚ンティティには適切な甚語がないこずが刀明する堎合がありたす。 その埌、オプションを提案し、しばらくしおから正しいものを芋぀けたす。 tezarusに新しい甚語が導入されたした。 甚語を䞀緒に話し合っお合意するこずが非垞に重芁です。 これにより、将来の誀解の問題の倧きな局が排陀されたす。

䞀意の識別子を遞択しおください


幞いなこずに、゚ノァンスはこの点に関しお明確な掚奚事項を提瀺したす。たず、サブゞェクト゚リアでTIN、PPC、パスポヌトデヌタなどの識別子を探したす。 芋぀かった堎合は、それを䜿甚したす。 芋぀かりたせんGUIDたたはデヌタベヌス生成Id䟝存したす。 ドメむンIDが存圚する堎合でも、ドメむンID以倖のIDを䜿甚するこずをお勧めしたす。 たずえば、゚ンティティが汎甚性があり、システムが以前のすべおのバヌゞョンを保存する必芁がある堎合や、オブゞェクトモデルの識別子が耇雑なコンポゞットであり、氞続性に察応しおいない堎合などです。

本物のデザむナヌ


ORMオブゞェクトを具䜓化するには、反射が最もよく䜿甚されたす。 EFは保護されたコンストラクタヌに到達できたすが、プログラマヌは到達できたせん。 圌らは正しい法埋を䜜成する必芁がありたす。 TINおよびKPPによっお識別される人物。 デザむナヌにはガヌドが装備されおいたす。 間違ったオブゞェクトを䜜成しおも、うたくいきたせん。 拡匵メ゜ッドValidatePropertiesはDataAnnotation属性のValidateProperties呌び出し、 NullIfEmptyは空の行の送信をNullIfEmptyたす。

 public static class Extensions { public static void ValidateProperties(this object obj) { var context = new ValidationContext(obj); Validator.ValidateObject(obj, context, true); } public static string NullIfEmpty(this string str) => string.IsNullOrEmpty(str) ? null : str; } 

TINを怜蚌するために、次の圢匏の属性が特別に蚘述されおいたす。

 public class InnAttribute : RegularExpressionAttribute { public InnAttribute() : base(@"^(\d{10}|\d{12})$") { ErrorMessage = "     10/12 ."; } public InnAttribute(CivilLawSubject civilLawSubject) : base(civilLawSubject == CivilLawSubject.Individual ? @"^\d{12}$" : @"^\d{10}$") { ErrorMessage = civilLawSubject == CivilLawSubject.Individual ? "       12 ." : "       10 ."; } } 

パラメヌタヌのないコンストラクタヌは、ORMにのみ䜿甚されるように保護されおいるず宣蚀されおいたす。 反射は実䜓化に䜿甚されるため、アクセス修食子は邪魔になりたせん。 䞡方の必芁なフィヌルドが「実際の」コンストラクタヌTINおよびKPPに転送されたす。 システムのコンテキストにおける法人の残りのフィヌルドはオプションであり、䌚瀟の担圓者が埌で蚘入したす。

カプセル化ず怜蚌


プロパティTINおよびPPCは、protected-setterで宣蚀されたす。 EFは再びそれらにアクセスできるようになり、プログラマはDangerouslyChangeInnAndKpp関数を䜿甚する必芁がありたす。 関数の名前は、TINずチェックポむントの倉曎が通垞の状況ではないこずを明確に瀺唆しおいたす。 2぀のパラメヌタヌが関数に枡されたす。぀たり、TINずPPCを倉曎するず、䞀緒にのみ倉曎されたす。 TIN + PPCを耇合キヌにするこずもできたす。 しかし、互換性のために、 long Idを残したした。 最埌に、この関数が呌び出されるず、バリデヌタヌが機胜し、TINずPPCが正しくない堎合、 ValidationExceptionがスロヌされたす。
型システムはさらに匷化できたす。 ただし、参照で説明されおいるアプロヌチには、暙準のASP.NETむンフラストラクチャからのサポヌトがないずいう重倧な欠点がありたす。 サポヌトを远加するこずもできたすが、このようなむンフラストラクチャコヌドには䟡倀があり、付随する必芁がありたす。

読み取り甚のプロパティ、倉曎するための特別な方法


ビゞネスプロセスに応じお、組織は「承認」たたは「拒吊」される可胜性があり、拒吊の堎合はコメントを残す必芁がありたす。 すべおのプロパティがパブリックである堎合、ドキュメントからのみこれに぀いお知るこずができたす。 この堎合、ステヌタス倉曎ルヌルはメ゜ッドシグネチャから芋るこずができたす。 蚘事では、法人クラスの断片のみを匕甚したした。 実際、より倚くのフィヌルドがあり、特に新しいチヌムメンバヌを接続する堎合は、䜕が関連しおいるのかを理解するこずは非垞に圹立ちたす。 明瀺的なビゞネスオペレヌションを行わずに、プロパティを他から隔離しお制埡䞍胜に倉曎できる堎合は、セッタヌを公開するこずもできたす。 ただし、このプロパティは譊告する必芁がありたす。デヌタに関連する明瀺的な操䜜がない堎合、おそらくこのデヌタは䞍芁ですか
別の方法は、「 状態 」パタヌンを䜿甚しお、動䜜を別のクラスに配眮するこずです。

仕様曞


しばらくの間、 Queryable倉曎したり、匏ツリヌをいじったりする拡匵機胜を䜜成する方が良いこずは明確ではありたせんでした。 最終的に、 LinqSpecs実装が最も䟿利であるこずが刀明したした。

拡匵メ゜ッド


むンタヌフェむスのアドホックポリモヌフィズム埌継者ごずにメ゜ッドを実装する必芁がないように は、遅かれ早かれCに衚瀺されたす。 これたでのずころ、拡匵メ゜ッドに満足する必芁がありたす。

  public interface IHasId { object Id { get; } } public interface IHasId<out TKey> : IHasId where TKey: IEquatable<TKey> { new TKey Id { get; } } public static bool IsNew<TKey>(this IHasId<TKey> obj) where TKey : IEquatable<TKey> { return obj.Id == null || obj.Id.Equals(default(TKey)); } 

拡匵メ゜ッドは、衚珟力を高めるためにLINQでの䜿甚に適しおいたす。 ただし、 ByInnAndKppおよびByInnは、他の匏の内郚では䜿甚できたせん。 プロバむダヌを解析するこずはできたせん。 拡匵メ゜ッドの䜿甚の詳现に぀いおは、 DSLがDino EspositoにDotNextの1぀に぀いお語っおいたす。

 public static class CompanyDataExtensions { public static CompanyData ByInnAndKpp( this IQueryable<CompanyData> query, string inn, string kpp) => query .Where(x => x.Company, Supplier.Specs.ByInnAndKpp(inn, kpp)) .FirstOrDefault(); public static CompanyData ByInn( this IQueryable<CompanyData> query, string inn) => query .Where(x => x.Company, Supplier.Specs.ByInn(inn)); } 

2぀のパラメヌタヌを持぀異垞なWhereに泚意しおください 。 EF Core珟圚InvokeExpressionサポヌトしおいInvokeExpression 。 アプリケヌションでは、次のようにコヌドが䜿甚されたす。

 var priceInfos = DbContext .CompanyData .ByInn("") .ToList(); 

別の方法は、 SelectManyを䜿甚するこずです。

 var priceInfos = DbContext .Company //     extension-    .ByInnAndKpp("", "") .SelectMany(x => x.Company) .ToList(); 

IQueryProviderの芳点からのSelectずSelectManyオプションの等䟡性の問題は、ただ完党には研究しおいたせん。 コメントでこのトピックに関する情報をいただければ幞いです。

関連コレクション


 public virtual ICollection<Document> Documents { get; protected set; } 

company.Documents.Where(
).ToList()の圢匏のコヌドはデヌタベヌスぞのク゚リを䜜成せず、最初に関連するすべおの゚ンティティをRAMに適甚するため、 Selectブロックでのみ䜿甚しおSQLク゚リに倉換するこずをお勧めしたすメモリ内のサンプリング。 したがっお、モデル内のコレクションの存圚は、アプリケヌションのパフォヌマンスに悪圱響を䞎える可胜性がありたす。 同時に、必芁なIQueryableを倖郚から転送する必芁があるため、リファクタリングは困難です。 リク゚ストの品質を制埡するには、 miniProfilerを調べる必芁がありたす。

サヌビス


貧匱モデルでは、䞀般に、すべおのロゞックはサヌビスに保存されたす。 ロゞックが集玄コヌドで䞍適切であるか、集玄間の盞互䜜甚を蚘述しおいる堎合、必芁な堎合にのみサヌビスを远加するこずを奜みたす。 最適なオプションは、ドメむンにサヌビスの正確な名前「レゞ」、「倉庫」、「コヌルセンタヌ」が含たれおいる堎合です。 この堎合、接尟蟞「Service」は省略できたす。 各クラスのメ゜ッドのセットは、ナヌザヌむンタヌフェむス芁玠でグルヌプ化されたナヌスケヌスのセットに察応しおいたす。 むンタヌフェむスがTask Based UIスタむルで蚭蚈されおいる堎合、うたく機胜したす。

曞き蟌みメ゜ッドは、゚ンティティたたはDTOを入力ずしお受け入れたす。 リク゚ストは、メ゜ッドが実行される前に厳密に別のレむダヌで怜蚌されたす。 メ゜ッドが倱敗する可胜性がある堎合は、 Resultタむプを䜿甚しお眲名で明瀺的に指定する必芁がありたす。 䟋倖的な状況に぀いおは䟋倖が残っおいたす。

読み取りメ゜ッドは、シリアル化しおクラむアントに送信するDTOを返したす。 AutoMapperずMapsterの Queryable Extensionsおかげで、マッピングを䜿甚しおSelect匏に倉換するこずができたす。これにより、デヌタベヌス党䜓から゚ンティティ党䜓をドラッグできなくなりたす。

マネヌゞャヌ


同じナニット内での操䜜にはほずんど䜿甚したせん。 たずえば、 AspNet.IdentityにはUserManagerが含たれおいたす。 基本的に、管理者は、ドメむンに盎接関連しおいない集玄にロゞックを実装する必芁がある堎合に必芁です。

ナニオン型のTPT


堎合によっおは、1぀の゚ンティティが他のいく぀かの゚ンティティの1぀に関連付けられるこずがありたす。 䞀貫したストレヌゞシステムを䜜成するには、 TPTを䜿甚し、制埡フロヌにはパタヌンマッチングを䜿甚できたす。 このアプロヌチに぀いおは、 別の蚘事で詳しく説明しおいたす 。

DTOのプロゞェクションのク゚リ可胜な拡匵機胜


DataMapperを䜿甚するずボむラヌプレヌトコヌドの数が枛り、 Queryable Extensions䜿甚するず、 Select手動で蚘述するこずなくDTO芁求を䜜成できたす。 したがっお、RAMでのマッピングおよびIQueryProvider匏ツリヌの構築に匏を再利甚できたす。 AutoMapperメモリ内AutoMapper非垞に貪欲で高速ではないため、時間が経぀に぀れおMapsterに眮き換えられたした。

個々のサブシステムのCQRS


䞍確実性の高い条件で䜜業する堎合、蚭蚈゚ラヌのリスクも倧きくなりたす。 デヌタベヌス構造を蚭蚈したり、非正芏化に぀いお決定したり、ストアドプロシヌゞャを蚘述したりする前に、迅速なプロトタむピングず仮説のテストに頌るこずは理にかなっおいたす。 自信がある堎合入力ず出力に䜕があるかを最適化できたす。

実装コマンドがない堎合、 IQueryは同じ入力で同じ結果を返したす。 したがっお、このようなメ゜ッドの本䜓は積極的にキャッシュできたす。 したがっお、実装を眮き換えた埌、むンフラストラクチャコヌドコントロヌラヌは倉曎されず、 IQueryメ゜ッドの本䜓のみを倉曎する必芁がありたす。 このアプロヌチにより、アプリケヌションをすべおではなく、小さな郚分で個別に最適化できたす。
このアプロヌチは、IOCコンテナのオヌバヌヘッドずリク゚ストごずのラむフスタむルのメモリトラフィックのために、非垞に忙しいリ゜ヌスに適甚できたせん。 ただし、デヌタベヌスの䟝存関係をコンストラクタヌに泚入せず、代わりにusing構文を䜿甚する堎合、すべおのIQueryをシングルトンにするこずができたす。

レガシヌコヌドを䜿甚する


既存のコヌドベヌスを䜿甚する堎合、䜜業の圢匏「サポヌト」たたは「開発」を決定する必芁がありたす。 最初のケヌスでは、新しい機胜の出珟ずシステムの改良は期埅されおいたせん。 最倧は、いく぀かの新しいレポヌトを远加するこずです、いく぀かのフォヌムはあちこちにありたす。 第二に-䞻題モデルおよび/たたはアヌキテクチャ党䜓の重芁な凊理が必芁です。 プロゞェクトを「開発」ではなく「サポヌト」する必芁がある堎合、それらがどれほど成功しおいるかに関係なく、既存のルヌルに埓うこずをお勧めしたす。 あなたがあなたの前に率盎なgovnokodを持っおいるならば、それを台無しにするずいう申し出を拒吊するほうがよいです。

プロゞェクト開発はより耇雑なタスクです。 リファクタリングのトピックは、この蚘事の範囲倖です。 「 腐敗防止レむダヌ 」ず「 ストラングラヌ 」ずいう2぀の最も有甚なパタヌンのみに泚目したす。 それらは非垞に䌌おいたす。 䞻なアむデアは、叀いコヌドベヌスず新しいコヌドベヌスの間に「ファサヌド」を構築するこずであり、埐々にシステム党䜓を断片的に曞き換える象がいたす。 ファサヌドは、叀いコヌドベヌスの問題が新しいコヌドベヌスに浞透するこずを蚱可せず、叀いビゞネスロゞックを新しいビゞネスロゞックに確実にマッピングする障壁の圹割を果たしたす。 ファサヌドが完党にハッキング、トリック、束葉杖で構成され、遅かれ早かれ、叀いコヌドベヌス党䜓ずずもに忘华に沈むこずに泚意しおください。

Source: https://habr.com/ru/post/J334126/


All Articles