ドメイン駆動設計:戦術設計。 パート2





こんにちは、Habrausers様! 前回の記事では、DDDアプローチを使用した戦略的モデリングについて検討しました。 それは主題領域の特定のタスクが解決さ



中で概念的な境界を区別する方法を示しました。



特定の



を実装するために、本質的に技術的な低レベルの戦術パターンがいくつか使用されます。つまり、これらのパターンは技術的な問題を解決するために使用されます。 そのようなパターンは次のとおりです:



-























および



。 この記事で説明するのは、それらについてです。



適切な設計では、これらのパターンを通じて明示的に







が表現されることを理解することが重要です。 ソフトウェアモデルは







豊かさを完全に示さなければなりません。 概念が



を使用して表現されていない場合、モデルで表現すべきではありません。 設計が



注意を払わずに戦術的なテンプレートを使用して実行される場合、これは軽量のDDDアプローチが使用されることを意味します。



伝統的に、E。Evansのから始まって、DDDテンプレートは最初の



と見なされ







エンティティ



サブジェクト領域の概念が一意であり、システム内の他のすべてのオブジェクトと異なる場合、



使用してモデル化します。 このような-



は、存在サイクル全体で形状が大きく異なる場合がありますが、要求に応じて常に一意に識別および検出できます。 このために、一意の識別子が使用され







設計時には、まずその作成を考慮する必要があります。



識別子を作成するには、いくつかの戦略があります。



一意の値のユーザー入力



このアプローチは、アプリケーションの識別子を読み取り可能にする必要がある場合に使用する必要があります。 それでも、アプリケーション自体で識別子の正確性と一意性を確認する必要があります。 多くの場合、識別子の変更のコストは高く、ユーザーは変更しないでください。 したがって、この識別子の品質を保証する方法を使用する必要があります。



識別子はアプリケーションによって生成されます。



一意の識別子を自動的に作成するためにアプリケーションで使用できる高速で信頼性の高いジェネレータがあります。 たとえば、Java環境には、クラスjava.util.UUID



があります。これにより、4つの異なる方法(時間ベース、DCEセキュリティ、名前ベース、ランダムに生成されたUUID)でいわゆる汎用一意識別子を生成できます。



そのようなUUIDの例: 046b6c7f-0b8a-43b9-b35d-6489e6daee91



これは36バイトの文字列です。



このような大きな識別子は、メモリの過負荷のために保存するには不利です。 UUIDの16進テキストの個々のセグメントの一意性の信頼レベルに応じて、識別子全体の1つ以上のセグメントを使用できます。



境界内の



のローカル識別子として使用される場合、短縮された識別子はより適切に保護されます。



たとえば、 APM-P-08-14-2016-046B6C7F



識別子を考えてAPM-P-08-14-2016-046B6C7F







ここ: PM



個別の設計管理コンテキスト。 P



プロジェクト; 08-14-2016



作成日; 046B6C7F-



はUUIDの最初のセグメントです。 そのような識別子がさまざまな



で出くわすと、開発者はすぐにその由来を確認します。



識別子は永続ストレージエンジンによって生成されます。



データベースにアクセスして、識別子を作成できます。 したがって、一意の値が確実に返されることを確認できます。 ただし、これは十分に短く、複合識別子に使用することもできます。



このアプローチの欠点はパフォーマンスです。 各値のデータベースへのアクセスには、アプリケーションで識別子を生成するよりも時間がかかります。



他の制限されたコンテキストによって割り当てられた識別子



識別子を取得するには、さまざまな



を統合する必要がある可能性があります(たとえば、前の記事で示した



を使用)。



たとえば、別の



で特定の識別子を見つけるために、ローカル識別子として使用できる外部



一意の識別子を決定できるようにする属性(電子メール、アカウント番号)を指定できます。 外部



からローカル



追加の状態(プロパティ)をコピーすることもできます。



エンティティの場合、通常、メインドメイン識別子に加えて、代理識別子が使用されます。 1つ目はサブジェクト領域の要件に従い、2つ目はORMツール自体(Hibernateなど)を対象としています。 代理キーを作成するには、通常、 long



型またはint



型の



属性が作成され、データベースに一意の識別子が作成され、それが主キーとして使用されます。 次に、ORMツールを使用して、このキーの属性へのマッピングが有効になります。 このような代理識別子は、ドメインモデルの一部ではないため、通常は外部から隠されています。



また、オブジェクトの存在を通じて一意性を維持するために、その識別子を変更から保護する必要があると言うことも重要です。 これは主に、識別子インストーラーメソッドを非表示にするか、インストーラーメソッドで状態変更チェックを作成して、そのような変更を禁止することによって行われます。 たとえば、 前の記事と同じPFMシステムを検討できます。



まず、サブジェクト領域で



を強調表示する必要があります。 BankingAccount



があり、アカウント番号accountNumberの使用がそれを識別するように頼むことは明らかです。 ただし、この番号は別の銀行でのみ一意であり、他の銀行でも繰り返される場合があります。 (IBAN番号を使用できますが、この番号は主に欧州連合の銀行でのみ使用されます。)つまり、口座番号に加えて、UUIDセグメントも使用できます。 次に、識別子はPFM-A-424214343245-046b6c7f



で構成されます。ここで、











-



として識別子を指定でき-



。 この非常に重要なDDDテンプレートを詳細に見る必要があります。



値オブジェクト



オブジェクトにとって個性が重要でない場合、属性によって完全に決定される場合は-



と見なす必要があり-



。 概念が



持っているかどうかを調べるには、次の特性のほとんどを持っているかどうかを調べる必要があります。





このようなオブジェクトは、一見すると思われるよりもはるかに頻繁に見つかるはずです。 作成、テスト、および保守が簡単なので、可能な場合は



代わりに-



を使用するようにしてください。



ごくまれに-



が変更可能になります。 フィールドへのアクセスを制限するには、通常、セッターをプライベートに設定し、オブジェクトのコンストラクターをパブリックに渡します。パブリックには、



属性であるすべてのオブジェクトが渡されます。 -



作成はアトミック操作である必要があります。



-



等価性チェック操作を定義することが非常に重要です。 2つの-



等しくするには、すべての属性タイプと値が等しくなければなりません。



-



すべてのメソッドは



べきだと言うことも非常に重要です。 不変のプロパティに違反してはならないため、オブジェクトを返すことはできますが、オブジェクトの状態を変更することはできません。



金額の価値のオブジェクトの古典的な例を考えてみましょう(インターネット上の多くの例では、このクラスが発生します)。



 public class Money implements Serializable { private BigDecimal amount; private String currency; public Money (BigDecimal anAmount, String aCurrency) { this.setAmount(anAmount); this.setCurrency(aCurrency); } … }
      
      





ここでのインストーラーメソッドは非表示になります;値のオブジェクトの作成はアトミック操作です。 特定の値の例は{50 000 }



です。 個々に、これらの属性は何か他のものを記述するか、具体的なものを意味するものではありません。 これは、特に数50,000およびある程度ドルに当てはまります。 つまり、これらの属性は、金額を表す概念的に



を形成します。 サブジェクト領域の概念のこの整合性は、非常に大きな役割を果たします。



タイプと



がそれらの







に従って参照されると理解するのは重要です。



次の重要な戦術モデリングテンプレートに移りましょう。



ドメインサービス







を使用すると、この言語の名詞はオブジェクトに反映され、動詞はこれらのオブジェクトの動作に反映されます。



または-



起因しない動詞またはアクションが非常に頻繁にあり-



。 サブジェクト領域にそのような操作が存在する場合、サブジェクト領域の



として宣言され



(クライアントである



とは異なります)。



は3つの特徴があり













の使用を乱用する必要はありません。 これ



作成されます。 ビジネスロジックは、







分散する必要があり







に従ってこれを実行できない場合にのみ



を使用する必要があり



。 主なものは、そのインターフェースが



正確に反映すること







たとえば、1つの支払人アカウントから受取人アカウントに送金



を利用できます。 どのオブジェクトに変換メソッドを保存するかは完全に不明であるため、



使用されます。









イベント(ドメインイベント)



主題分野を研究するとき、主題の専門家にとって特に重要な事実があります。 たとえば、専門家から次の重要なフレーズを聞くことができます。





つまり、別の別のアクションの結果として何かが発生する場合



特定の



をモデル化する必要が



ます。

モデリングの際には、



が過去形で起こったことを考慮する必要があります。 したがって、



の名前は過去の時間を反映しますが、名前自体は







に従って割り当てられる必要があり











-



ように不変に設計されること



非常に多くあり-



。それらの関数は



です。



、インターフェイスが目的を表すオブジェクトとしてモデル化され、プロパティはその原因を反映します。



たとえば、FundsDepositedイベントを考えます。









occuredOnは



のタイムスタンプです。 次に、何が起こったかについての情報を伝える重要なプロパティを指定する必要があります。 重要なプロパティは、



生成される



または



識別子(accountId)です。 加入者にとって、ある状態から別の状態への



の移行のいくつかのパラメータが重要になる可能性もあります。



この場合、資金が特定の口座に入金されるときに発生するイベントがシミュレートされます。 その結果、登録に関するSMSの送信、メールの送信、または別の操作を実行できます。







公開し、



処理できるようにするには、



テンプレートまたは-



使用できます。



イベントが1つの



内で処理される場合、さまざまなインフラストラクチャコンポーネントを使用できません(ドメインモデルのフレームワーク内に存在するべきではありません)が、



テンプレートの実装のみをモデルに追加できます。



したがって、すべてのサブスクライバを格納、登録し、



を発行するDomainEventPublisher



オブジェクトを作成するだけで十分です。 この場合、サブスクライバーのパブリケーションは、別のサイクルと1つのトランザクションで同期的に実行されます。 また、各サブスクライバーは、



個別に解決できます。











の範囲の概念であり、別々の



ないことを強調するのは重要です。 したがって、メッセージングインフラストラクチャを使用して、



外部







を非同期的に転送できます。



ミドルウェアクラスに属する多くのメッセージングコンポーネント(RabbitMQ、NServiceBusなど)があります。 また、RESTリソースを使用してメッセージングを実行することもできます。この場合、自律システムは公開システムにアクセスし、まだ処理されていないイベントの通知を要求します。







通知を公開するRESTfulのアプローチは、一般的なメッセージングインフラストラクチャを使用した公開の反対です。 「発行者」は、関係者に何も送信されないため、登録された「購読者」の数をサポートしません。 代わりに、このアプローチでは、RESTクライアント自体がURIを使用して通知を要求する必要があります。



メッセージングインフラストラクチャが公開するものとドメインモデルの現在の状態との間で一貫性を実現する必要があることを理解することが重要です。



の配信を保証する必要があり、この



が発行されたモデルの実際の状況を反映する必要があります。



このような一貫性を実現するには、さまざまな方法があります。 ほとんどの場合、



内で



使用されるメソッドを使用し



。 このストアハウスは、ドメインモデルによって使用されると同時に、メッセージパッシングメカニズムを使用して未公開の



を公​​開する外部コンポーネントによって使用されます。 ただし、このアプローチでは、クライアントは受信メッセージを重複排除する必要があるため、同じイベントを再送信したときにクライアントはそれを正しく処理します。



両方の場合-サブスクライバーがメッセージングにミドルウェアを使用する場合、または通知クライアントがRESTを使用する場合-処理されたメッセージのIDの追跡は、ドメインモデルのローカル状態の変更とともに記録されることが重要です。



次のDDDパターンを見てみましょう。



モジュール



モデル内の



は、互いに密接に関連するドメインオブジェクトの特定のグループの名前付きコンテナーです。 彼らの目標は、異なる



クラス間の結合を弱めることです。 DDDアプローチの



は非公式または一般化されたセクションであるため、適切な名前を付ける必要があります。 彼らの名前を選ぶことは



機能







疎結合



設計



必要です。これにより、モデリングの概念のサポートとリファクタリングが容易になります。 一貫性が必要な場合は、ピアツーピアモジュール間の非循環依存関係について戦う必要があります(ピアツーピア



、プロジェクト内で同じレベルにあるか、同じ重みを持つ



)。



は、モデルの静的な概念にしない方



よいでしょう。なぜなら、



は、構成するオブジェクトに応じて変化するはずだからです。



モジュールの命名には特定のルールがあります。 モジュールの名前(多くのプログラミング言語)は、組織の階層形式を反映しています。 通常、名前の階層は、モジュールを開発している組織のドメイン名で始まります(競合を避けるため)。 例:



 com.bankingsystems
      
      





モジュール名の次のセグメントは、



識別し



。 このセグメントの名前は、



名前に基づいていることが望ましいです:



 com.bankingsystes.pfm
      
      





以下は、特定のサブジェクトエリアのモジュールを識別する修飾子です。



 com.bankingsystems.pfm.domain
      
      





すべてのモデル



は、このドメインセクションに配置できます。 このように:



 com.bankingsystems.pfm.domain.account <<Entity>>BankingAccount <<ValueObject>>AccountId
      
      





よく知られている



命名は次のようになります。



om.bankingsystems.resources





om.bankingsystems.resources.view







(ビューのストレージ)



om.bankingsystems.application





om.bankingsystems.application.account











サブモジュール)







は、サブジェクト領域の関連オブジェクトを集約するために使用され、接続されていないオブジェクトや疎結合されているオブジェクトから分離されます。



明確な境界がない限り、通常は最初にすべての概念を1つのモデルに結合するため、



は複数のモジュールにまたがることがよくあります。



集計







は、すべてのDDD戦術ツールの中で最も複雑です。



は、



オブジェクトまたは



クラスタ



。 つまり、これらのオブジェクトは、データ変更の観点から全体として考慮されます。



それぞれ



にルート(集合ルート)と、不変条件が常に満たされなければならない境界があります。



すべての呼び出しは、グローバルに一意の識別子である



itを介して行う必要があります。すべての内部オブジェクトにはローカルIDのみがあり、必要に応じて相互に参照できます。外部オブジェクトは、への参照のみを保存でき、内部オブジェクトは保存できません。不変式は、一貫性を常に維持するビジネスルールです。この現象はトランザクションの一貫性と呼ばれ、アトミックです。最終的な一貫性もあります。不変条件の場合、トランザクションの一貫性について話します。























また、トランザクションの一貫性の境界を呼び出すこともできます。この境界内では、実行される操作に関係なく、不変のルールが実行されます。



特定しようとする







で、マージに分析する必要があるオブジェクトを決定するために、モデルの真の不変量を







また、設計時



には、大規模クラスタのもの



がパフォーマンスとスケーラビリティの点で小さな単位に失われることを考慮する必要があります。 largeをダウンロード



するには、より多くのメモリが必要です。小さいもの



はより速く動作するだけでなく、トランザクションの成功にも貢献します。また、内部は



内部-



よりも使用する方良いと言うことも重要です



。前述のように、メンテナンス、テストなどが簡単です。



誰でも



リンクを他のユーザーのルートとして保存できます



。同時に、これは



前者の一貫性の範囲内にこれを配置しません



。リンクは全体的なものを生成しません







1つのトランザクション内で、1つ



だけを変更する必要があります







リンクは



、オブジェクト(またはポインター)として直接リンクを保存するのではなく、グローバル識別子を使用して行うのが最適です。したがって、オブジェクトのメモリが削減されます。読み込みが速くなります。スケーリングが簡単です。



クライアントのリクエストが複数



影響する場合、完全な一貫性の原則を使用する必要があります。最終的な一貫性は、出版物を通じて達成できます。



。つまり、1つを変更した後



、彼は公開し



、1つ以上の他の人



に対して、システムの一貫性をもたらすアクションが実行されます。



集計の例は、信用報告書です。









各信用報告書には、借り手の身元に関する情報が含まれていなければなりません。これを行うために、identifierで外部接続を保存しますustomerId



。 ustomer-



借り手に関する情報(名前、住所、連絡先)を含む完全に異なる。たとえば、不変式は、信用履歴のデータに応じて、信用格付けを再計算するためのルールです。



正確にどのようにカウントされるかは重要ではありません。主なことは、トランザクションの一貫性が内部で実行されること



です。たとえば、クレジットレコードを追加または置換した後、クレジットスコアをすぐに再計算する必要があります。アトミック操作である必要があります。データベースを使用する場合は、個別のトランザクションが必要です。内部のオブジェクトにデータを入力した直後



、すべての不変条件を満たさなければなりません。



Inquiry



-他の組織からの信用報告書の特定の要求。



です



彼はグローバルなアイデンティティを持っています。これを参照する必要がある



なら、識別子だけを使用できます



レポートを



削除する場合



、すべてを削除する必要があります



-履歴とクエリに関するレコード。



工場



このパターン



は他のパターンよりも有名です。



一部



または



非常に複雑な場合があります。複雑なオブジェクトは、コンストラクターを介して自分自身を作成することはできません。 (エリックエバンスの本で例が挙げられました:メカニックまたはロボットのいずれかによって組み立てられる車のエンジンですが、それ自体で組み立てる必要はありません。)さらに悪いことに、複雑なオブジェクトの作成がクライアントに転送される場合。そのため、クライアントは施設内の内部構造と関係を認識する必要があります。これにより、カプセル化が解除され、クライアントが特定の実装にバインドされます(したがって、オブジェクトが変更された場合、クライアント実装も変更する必要があります)。



複雑な



オブジェクトまたは他のオブジェクトの作成を個別に実行することをお勧めします。このために使用され



ます。



-他のオブジェクトを作成する責任があるプログラムの要素。



ほとんどの場合、の



ように設計され



てい



ます。ファクトリー



1は、その助けを借りて表現できるという点でまだ有利です



(コンストラクターはこれを表現しません)。



作成するとき



にある



すべての不変条件を遵守する必要があり



、全体としてそれを作成し、。このメソッドは、単一で不可分でなければなりません。作成するすべてのデータ(通常のみ-



)は、1回の通信操作で転送する必要があります。建設の詳細は非表示です。



-



そして



、さまざまな方法で作成された:として



不変、すべての属性は、作成後すぐに送信されなければなりません。そして



、特定のものを作成するために重要なものだけを追加できます



およびその不変式。



リポジトリ



ストレージは、そこに置かれたアイテムを安全に保管するために設計されたメモリ領域です。それがサブジェクト指向のストレージです。



に使用され



ます。



適切なものに入れて



そこから抽出すると、完全なオブジェクトが得られます。場合



は、変更し、変更が保存されます。



削除された場合、削除できなくなります。永続的なストレージを想定している



すべての



ユーザーには、独自のストレージがあり



ます。多くの場合、



メソッドは



いくつかの基準によって完全に生成されたサンプリングのために実装されます。



プロジェクトには2つのタイプがあります







  1. コレクションを模倣することを志向。
  2. 永続的なストレージのメカニズムを指向。






コレクションの模倣を非常に正確に模倣し、そのインターフェイスの少なくとも一部をモデル化することを目的としています。



この場合、インターフェイス自体は永続的なストレージメカニズムの存在を明らかにしないため、従来の元のDDDテンプレートを表します。



これ



想像できHashMap<ObjectId,Object>



ます。このコレクションは、同じアイテムの再挿入を除外します。同時に、オブジェクトを受信して​​変更すると、変更がすぐに記録されます。



クライアントは永続的なストレージメカニズムの動作に干渉する必要はありませんが、その適切な動作のために、オブジェクトの変更を暗黙的に追跡する必要があります。これを行うには2つの方法があります。



  1. 暗黙のコピーオンリード(永続ストレージメカニズムは、データベースから読み取られるたびにストレージオブジェクトをコピーし、トランザクションがコミットされたときに閉じられたコピーをクライアントと比較します)。
  2. 記録中の暗黙的なコピー(メカニズムは、プロキシオブジェクトを使用してダウンロードされたオブジェクトを管理します)。


Hibernateなどのメカニズムを使用すると、



コレクション指向のコレクションを作成できます



高性能環境では、メモリと実行システムに大きな負荷がかかるため、非常に多くのオブジェクトをメモリに格納すると不利になる可能性があります。ストレージ指向のメカニズム



の場合



、オブジェクトの変更を追跡する必要はありませんが



、メソッドsave()



などにより変更後の変更を毎回記録する必要があります。例:









実装で



は、メカニズムのメソッドとオブジェクトを使用します。この場合、実装はインフラストラクチャレベルで行われ、インターフェイスはドメインで宣言されます。



ここでは、最初のタイプが使用され



、コレクションを模倣するようになっています。save()



またはメソッドを実装する必要はありませんput()



。収集方法のみ。



したがって、データベースまたは他のメカニズムへのアクセスはにカプセル化され



ます。クライアントは非常に単純であり、特定の実装に依存しません







おわりに



したがって、基本的なDDD技術パターンが検討されました。これらの各技術ツールを使用して、個別のツールを開発できます



。手始めに、最も重要な



強調表示できます-



。次に、これらを組み合わせて、



データの一貫性と境界内のビジネスルールを実現します



。次に、あなたが作成する必要があります







。システム全体でデータの一貫性を確保するには、を使用します



。これは、個別のシステムだけでなく、システム全体で使用できます







また







アプリケーションの内容そのものについて話すこともできますが、記事は非常に大きくなり、読むのが不便になります。



この記事がDDDパターンの理解を深めるのに役立つことを願っています!質問がある場合は、コメントを書いてください。喜んでお答えします。見てくれてありがとう。



作成者:greebn9k(セルゲイグリブニャック)、wa1one(ウラジミールコヴァルチュク)、silmarilion(アンドレイカハレフ)。



All Articles