web-dev-qa-db-ja.com

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

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

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

Flow Illustration

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

広告2.:

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

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

広告3.:

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

広告5.:

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

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

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

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

一般的に:

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

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

12
Double M

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

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

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を持つUserの2つのインスタンスが同時に存在することはできません」ではありません。

それらは同じリポジトリに存在できないということです。

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

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

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

一般的な質問

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

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

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

5
Ewan

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

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

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

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

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

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

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

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

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

ポイント5.:(ORM)

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

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

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

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

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

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

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

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

概要

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

同様に、DDDのdataobject-repository抽象化も常に最適であるとは限りません。私はこれまで言ってみれば、めったに最適な設計ではありません。

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

2