ドメイン/永続性モデルの分離は、通常これが厄介ですか?


12

私はドメイン駆動設計(DDD)の概念に飛び込んでいますが、特にドメインと永続性モデルの分離に関して、いくつかの原則が奇妙であることに気付きました。これが私の基本的な理解です:

  1. (機能セットを提供する)アプリケーション層のサービスは、その機能を実行するために必要なリポジトリからドメインオブジェクトを要求します。
  2. このリポジトリの具体的な実装は、それが実装されたストレージからデータをフェッチします
  3. サービスは、ビジネスロジックをカプセル化するドメインオブジェクトに、その状態を変更する特定のタスクを実行するように指示します。
  4. サービスは、変更されたドメインオブジェクトを永続化するようリポジトリに指示します。
  5. リポジトリは、ドメインオブジェクトをストレージ内の対応する表現にマップする必要があります。

フロー図

さて、上記の仮定を考えると、以下は厄介なようです:

広告2 ::

ドメインモデルは、ドメインオブジェクト全体(すべてのフィールドと参照を含む)をロードしているように見えます。それを要求した関数には必要ない場合でもです。他のドメインオブジェクトが参照されている場合は、それらのドメインオブジェクトとそれらが参照しているすべてのオブジェクトを順にロードしない限り、完全にロードすることさえ不可能かもしれません。遅延読み込みが思い浮かびますが、これは、最初にリポジトリの責任であるドメインオブジェクトのクエリを開始することを意味します。

この問題を考えると、ドメインオブジェクトをロードする「正しい」方法には、各ユースケースに専用のロード関数があるようです。これらの専用関数は、それらが設計されたユースケースで必要なデータのみをロードします。ここで、ぎこちないことが出てきます。最初に、リポジトリの実装ごとにかなりの量のロード関数を維持する必要があり、ドメインオブジェクトはnull、フィールドを運ぶ不完全な状態になってしまいます。後者は、値がロードされなかった場合でも、それを要求した機能によって要求されるべきではないため、技術的には問題になりません。それでも厄介で潜在的な危険です。

広告3 ::

ドメインオブジェクトは、リポジトリの概念がない場合、構築時に一意性の制約をどのように検証しますか?たとえばUser、一意の社会保障番号(指定されている)を使用して新しいを作成する場合、データベースに一意性制約が定義されている場合にのみ、リポジトリにオブジェクトの保存を要求すると、最も早い競合が発生します。それ以外の場合はUser、指定された社会保障でを探し、それが存在する場合は、新しいものを作成する前にエラーを報告できます。ただし、制約チェックはサービスに存在し、それらが属するドメインオブジェクトには存在しません。私はドメインオブジェクトが検証のために(注入された)リポジトリを使用することが非常によく許可されていることに気づきました。

広告5 .:

私は、ドメインオブジェクトのストレージバックエンドへのマッピングを、ドメインオブジェクトが基になるデータを直接変更するのと比較して、作業集約的なプロセスとして認識しています。もちろん、具体的なストレージ実装をドメインコードから切り離すことは、必須の前提条件です。しかし、それは確かにそのような高コストで来るのでしょうか?

ORMツールを使用してマッピングを行うオプションがあるようです。ただし、ORMの制限に従ってドメインモデルを設計する必要がある場合や、ドメインからインフラストラクチャレイヤーへの依存関係を導入する必要がある場合もあります(たとえば、ドメインオブジェクトでORMアノテーションを使用するなど)。また、私はORMがかなりの計算オーバーヘッドをもたらすことを読みました。

ORSQLのような概念がほとんど存在しないNoSQLデータベースの場合、ドメインモデルで変更されたプロパティをどのように追跡しますsave()か?

編集:また、リポジトリがドメインオブジェクトの状態(つまり、各フィールドの値)にアクセスするためには、ドメインオブジェクトがカプセル化を解除する内部状態を明らかにする必要があります。

一般に:

  • トランザクションロジックはどこに行きますか?これは確かに永続化に固有のものです。一部のストレージインフラストラクチャは、トランザクションをまったくサポートしない場合もあります(インメモリモックリポジトリなど)。
  • 複数のオブジェクトを変更する一括操作の場合、オブジェクトのカプセル化された検証ロジックを通過するために、各オブジェクトを個別にロード、変更、および保存する必要がありますか?これは、データベースに対して単一のクエリを直接実行することとは対照的です。

このトピックについて、いくつかの説明をお願いします。私の仮定は正しいですか?そうでない場合、これらの問題に取り組む正しい方法は何ですか?


1
良い点や質問、それらにも興味があります。一方の注意点-集計を適切にモデル化している場合、つまり、ある時点で、集計インスタンスは有効な状態でなければなりません-これが集計の主要ポイントです(そして、集計を構成コンテナーとして使用していません)。つまり、DBデータから集計を復元するために、リポジトリ自体は通常、特定のコンストラクターと一連のミューテーション操作を使用する必要があり、どのORMがそれらの操作の実行方法を自動的に知ることができるかわかりません。
Dusan

2
さらに残念なのは、あなたのような質問がかなり頻繁に尋ねられることですが、私の知る限り、本による集計とリポジトリの実装の例はありません
Dusan

回答:


5

あなたの基本的な理解は正しく、スケッチしたアーキテクチャは良好であり、うまく機能します。

行間を読むと、よりデータベース中心のアクティブレコードスタイルのプログラミングから来ているように見えますか?実用的な実装を実現するには、次のことを行う必要がある

1:ドメインオブジェクトにオブジェクトグラフ全体を含める必要はありません。たとえば、私は持つことができます:

public class Customer
{
    public string AddressId {get;set;}
    public string Name {get;set;}
}

public class Address
{
    public string Id {get;set;}
    public string HouseNumber {get;set;
}

「顧客名はハウスネームと同じ文字でのみ開始できる」などのロジックがある場合、住所と顧客は同じ集計の一部である必要があります。オブジェクトの遅延読み込みと「ライト」バージョンを避けるのは正しいことです。

2:一意性の制約は通常、ドメインオブジェクトではなく、リポジトリの範囲です。リポジトリをドメインオブジェクトに挿入しないでください。これは、アクティブなレコードへの移動であるため、サービスが保存しようとしたときにエラーが発生するだけです。

ビジネスルールは、「同じSocialSecurityNumberを持つユーザーの2つのインスタンスが同時に存在することはできません」ではありません。

同じリポジトリに存在することはできません。

3:個別のプロパティ更新メソッドではなく、リポジトリを記述することは難しくありません。実際、どちらの方法でもほとんど同じコードを使用していることがわかります。あなたがそれを入れたのはまさにそのクラスです。

最近のORMは簡単で、コードに追加の制約はありません。とは言っても、個人的にはSQLを手動で調整することを好みます。それはそれほど難しくありません。ORM機能で問題が発生することはなく、必要に応じて最適化できます。

保存時に変更されたプロパティを追跡する必要はありません。ドメインオブジェクトを小さく保ち、単に古いバージョンを上書きします。

一般的な質問

  1. トランザクションロジックはリポジトリに格納されます。しかし、それがあったとしても多くはありません。集計の子オブジェクトを配置する子テーブルがある場合は、いくつか必要ですが、それはSaveMyObjectリポジトリメソッド内に完全にカプセル化されます。

  2. 一括更新。はい、各オブジェクトを個別に変更してから、SaveMyObjects(List objects)メソッドをリポジトリに追加して、一括更新を行う必要があります。

    ドメインオブジェクトまたはドメインサービスにロジックを含める必要があります。データベースありません。つまり、「update customer set name = x where y」を実行するだけでは、Customerオブジェクトを知っているか、CustomerUpdateServiceが名前を変更したときに他の20の奇妙なことを行うためです。


すばらしい答えです。あなたは絶対に正しいです。私はコーディングのアクティブレコードスタイルに慣れています。そのため、リポジトリパターンは一見奇妙に見えます。ただし、「リーン」ドメインオブジェクト(のAddressId代わりにAddress)はOOの原則と矛盾しませんか?
ダブルM

いいえ、あなたはまだAddressオブジェクトを持っていますが、それはCustomerの子ではありません
Ewan

変更追跡なしの広告オブジェクトのマッピングsoftwareengineering.stackexchange.com/questions/380274/...
ダブルM

2

短い答え:あなたの理解は正しく、あなたが持っている質問は、解決策が簡単ではなく、広く受け入れられていない有効な問題を指摘しています。

ポイント2 : :(完全なオブジェクトグラフの読み込み)

ORMが常に良い解決策とは限らないことを指摘したのは、私が最初ではありません。主な問題は、ORMが実際のユースケースについて何も知らないため、何をロードするか、またはどのように最適化するかがわからないことです。これは問題です。

あなたが言ったように、明白な解決策は、それぞれのユースケースのための永続メソッドを持つことです。しかし、それでもORMを使用する場合、ORMはすべてをデータオブジェクトにパックすることを強制します。これは、実際にはオブジェクト指向ではないことを除けば、いくつかのユースケースに最適な設計ではありません。

一部のレコードを一括更新するだけの場合はどうなりますか?すべてのレコードにオブジェクト表現が必要なのはなぜですか?等。

したがって、それに対する解決策は、ORMを適切でないユースケースに使用しないことです。ユースケースをそのまま「自然に」実装します。これは、データ自体(データオブジェクト)の追加の「抽象化」や「テーブル」(リポジトリ)の抽象化を必要としない場合があります。

ご指摘のとおり、データオブジェクトを半分埋めるか、オブジェクト参照を「id」で置き換えることは、せいぜい回避策であり、良いデザインではありません。

ポイント3 . :(制約の確認)

永続性が抽象化されていない場合、各ユースケースは明らかに、必要な制約を簡単にチェックできます。オブジェクトが「リポジトリ」を知らないという要件は完全に人工的なものであり、テクノロジーの問題ではありません。

ポイント5 . :(ORM)

もちろん、具体的なストレージ実装をドメインコードから切り離すことは、必須の前提条件です。しかし、それは確かにそのような高コストで来るのでしょうか?

いいえ、ありません。永続化する方法は他にもたくさんあります。問題は、ORMが常に(少なくともリレーショナルデータベースでは)使用する "the"ソリューションと見なされることです。プロジェクトの一部のユースケースで使用しないことを提案するのは無駄であり、ORM自体によってはキャッシュや遅延実行がこれらのツールで使用されることがあるため、場合によっては不可能です。

一般的な質問1 .:(トランザクション)

単一の解決策があるとは思いません。デザインがオブジェクト指向の場合、各ユースケースに「トップ」メソッドがあります。トランザクションはそこにあるはずです。

その他の制限は完全に人為的なものです。

一般的な質問2 .:(一括操作)

ORMを使用すると、(私が知っているほとんどのORMの場合)個々のオブジェクトを通過する必要があります。これは完全に不要であり、手がORMで縛られない場合はおそらくデザインではありません。

SQLから「ロジック」を分離する要件は、ORMから来ています。彼らそれをサポートすることができないので、彼らはそれを言わなければなりません。それは本質的に「悪い」ものではありません。

概要

私のポイントは、ORMが常にプロジェクトに最適なツールであるとは限らないことだと思います。たとえそうであっても、プロジェクトのすべてのユースケースに最適である可能性はほとんどありません。

同様に、DDDのdataobject-repository抽象化も常に最良であるとは限りません。私は今のところ、これらが最適なデザインであることはめったにありません

そのため、万能のソリューションはありません。そのため、各ユースケースのソリューションを個別に検討する必要があります。これは朗報ではなく、明らかに作業が難しくなります:)


非常に興味深い点があります。私の仮定を確認していただきありがとうございます。永続化する方法は他にもたくさんあるとおっしゃいました。PIを提供するグラフデータベース(ORMなし)で使用するパフォーマンスデザインパターンを推奨できますか?
ダブルM

1
そもそも、分離が必要かどうか(そして、どのような種類か)を実際に質問します。テクノロジー(データベース、UIなど)ごとに分離すると、回避しようとしている「ぎこちなさ」がほぼ自動的に発生し、データベーステクノロジーの置き換えが多少簡単になるという利点があります。ただし、ビジネスロジックの変更はレイヤー全体に広がるため、コストはより困難になります。または、ビジネス機能に沿って分割することもできます。これにより、データベースの変更は困難になりますが、ロジックの変更は容易になります。どれにしますか?
RobertBräutigam18年

1
ドメイン(ビジネス関数など)をモデル化し、データベースを抽象化しない場合(リレーショナルでもグラフでもかまいません)、最高のパフォーマンスを得ることができます。データベースはユースケースから抽象化されていないため、ユースケースは必要な最適なクエリ/更新を実装でき、必要なことを達成するために厄介なオブジェクトモデルを通過する必要がありません。
ロバートブロイティガム2018年

まあ、主な目標は、永続化の懸念をビジネスロジックから遠ざけることです。これにより、理解、拡張、テストが容易なクリーンなコードを作成できます。DBテクノロジーを交換できることは単なるボーナスです。効率と無知の間には明らかに摩擦があることがわかります。これは、使用できる(しかし許可されていない)強力なクエリにより、グラフDBの方が強いようです。
ダブルM

1
Java Enterprise開発者として、過去20年間、永続性をロジックから分離しようとしてきたことを私は言うことができます。動作しません。第一に、分離は実際には達成されなかった。今日でも、おそらく「ビジネス」オブジェクトにはあらゆる種類のデータベース関連のものがあり、主なものはデータベースID(および多くのデータベース注釈)です。第二に、あなたが言ったように、時々ビジネスロジックはどちらかの方法でデータベース上で実行されます。3つ目は、特定のデータベースがある理由です。これは、データが存在する場所で最適なロジックをオフロードできるようにするためです。
ロバートブロイティガム
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.