web-dev-qa-db-ja.com

ドメインオブジェクトにはどのようなロジックを現実的に含めることができますか?

この概念について最初に読んだときから、Webアプリケーションのコンテキストでこの概念に取り組んできました。この理論では、ドメインオブジェクトはその動作とビジネスロジックをカプセル化する必要があると述べています。データのみを含み、そのロジックが外部のどこかにあるモデルは「貧血ドメインモデル」と呼ばれ、これは悪いことです。また、ドメインはデータアクセスを実行しないでください。

たとえば、タイプUserのオブジェクトの束を持つソーシャルアプリがあり、ユーザーが他のユーザーを友達として追加できる場合、Userクラスにはメソッドが含まれている必要がありますBefriend(User user)という名前を付けて、serA.Befriend(userB)のようにすることができます。

class User {

    Friends[] friends;

    void Befriend(User user) { ... }
}

ただし、友だちの行動にはいくつかの制限が含まれる場合があるため、Befriendメソッドで検証を行う必要があります。次に、純粋に理論的な制限をいくつか示します。

  1. ユーザーはすでにあなたの友達であってはなりません
  2. あなたと他のユーザーは共通の友達を持っていてはいけません
  3. ブカレストでは雨が降っているはずです

ここで、友達リストが巨大で、userAに50.000人の友達がいて、userBに100.000人の友達がいるとします。したがって、1と2を検証する場合、ユーザーオブジェクトを作成するときにデータベースからフレンドリスト全体を熱心にプルしてから、Befriendメソッドの反復処理でこれらのチェックを行うのは効率的ではありません。 friendsリスト。データベースには、インデックスとチェックがあり、これらは簡単(かつ高速)です。したがって、当然のことながら、これらのクエリをデータアクセス層のどこかに配置し、必要なときにいつでも使用することを好みます。

class FriendsRepository: IFriendsRepository {

    bool HasFriend(User user, User friend);
    bool HasCommonFriends(User userA, User userB);

}

Befriendメソッド内でUserオブジェクトからこのオブジェクトをどのように使用するのですか?ここでは多少の不一致があるようですが、ドメインオブジェクトはリポジトリを(インターフェースなどの抽象化によっても)使用してはならない、と人々は言います。このルールに違反したとしましょう。ドメインオブジェクトには依存性注入のメリットがないため、Befriendメソッドを次のように変更する必要があります。

void Befriend(User user, IFriendsRepository friendsRepository) { ... }

よし。天気はどうですか?それは私たちのエンティティとはまったく無関係であり、その情報はIWeatherServiceから取得されます。ここでも、Befriendメソッドで必要です。

void Befriend(User user, IFriendsRepository friendsRepository, IWeatherService weatherService) { ... }

このため、このメソッドはUserクラス内に属していないように感じます。私は多くの外部依存関係があり、依存性注入がうまくいかない。しかし、これをUserからアプリケーション層内のサービス(または何でも)に引き出すと、ドメインモデルが貧弱になります。検証なしで実行できる、または非常に単純な検証ルールのみを含むメソッドに遭遇することはめったにありません。そのエンティティですぐに利用できるプロパティ(たとえば、ユーザー名文字列、ActiveUntil日付などのプリミティブフィールドなど)にのみ依存します。

だから私は尋ねられたままです:ドメインオブジェクトに自然に合うことができるメソッドの種類は何ですか?正直に言うと、実際のアプリは多くの場合、大量のデータ、多くのオブジェクト関係、および非常に複雑な検証ロジックを扱います。 「このユーザーは12歳以上ですか?」のような簡単なチェックを行う必要はほとんどありません。

PS:私はその例を純粋にデモ目的で使用しました。固執しないでください。

4
Ted Chirvasiu

間違いなく、カプセル化の最小の方法は関数です。

float harmonic(int n) 
{ 
    float h = 1.0; 

    for (int i = 2; i <= n; i++) { 
        h += 1.0 / i; 
    } 

    return h; 
}

この関数には、コードとデータの両方が含まれています。関数が完了すると、含まれているデータが返されます。

クラスは、コードとデータを同様の方法でカプセル化します。唯一の真の違いは、同じデータ、およびそのデータの複数のインスタンスを操作する複数の関数(クラスでは「メソッド」と呼ばれます)を使用できることです。

here から取得した、複素数クラスのこの部分的なコードリストを考えてみます。

public class Complex {
    private final double re;   // the real part
    private final double im;   // the imaginary part

    // create a new object with the given real and imaginary parts
    public Complex(double real, double imag) {
        re = real;
        im = imag;
    }

    // return a new Complex object whose value is (this + b)
    public Complex plus(Complex b) {
        Complex a = this;             // invoking object
        double real = a.re + b.re;
        double imag = a.im + b.im;
        return new Complex(real, imag);
    }

    // return a new Complex object whose value is (this * b)
    public Complex times(Complex b) {
        Complex a = this;
        double real = a.re * b.re - a.im * b.im;
        double imag = a.re * b.im + a.im * b.re;
        return new Complex(real, imag);
    }
}

これらのカプセル化の例はどちらも、「自己完結型」です。それらは機能するために外部の依存関係に依存しません。

コードとデータのカプセル化の問題は、ビジネスアプリケーションの設計を開始するときに少し厄介になります。これが本当である理由は、ビジネスアプリケーションが主にエンティティのコレクションとそれらのエンティティ間の関係に関係しているためです。個々のエンティティに対してアトミックに実行できる操作はあり得ますが、これはまれです。エンティティ間の関係やコレクション内のエンティティの状態や数に影響を与える操作を実行する方が一般的です。その結果、ほとんどのビジネスロジックはオブジェクトの集合体で見つかる可能性が高くなります。

説明のために、Amazonのような普通のビジネスを考えてみましょう。アマゾンを選ぶ特別な理由はありませんが、Amazon、それは多くの点で他の企業と著しく似ていますが、顧客、在庫、注文、請求書、支払い、クレジット、通常の容疑者があります。

他のエンティティと離婚してアトミックに実行できる顧客エンティティ内に何をカプセル化できますか?まあ、多分あなたは彼らの姓を変更することができます。これは、貧弱なデータモデルを使用して、リポジトリのどこかで自動的に発生する可能性があるデータベース内のデータ変更です。おそらく、パスワードのハッシュを変更できます。これにはいくつかのロジックが必要ですが、Customerエンティティに存在することはほとんどありません。一部のセキュリティモジュールに存在する可能性が高くなります。

興味深いビジネスロジックはすべて、基本エンティティの外にあります。個別のエンティティではなく、複数のエンティティの集合体である請求書について考えてみます。システムの他の部分から離脱した請求書クラスの中で何ができますか?まあ、あなたは配送先住所を変更することができます。これは、Invoiceエンティティの外部キーに対する変更です。合計(ラインアイテムの数量とコストの合計)を計算できます。最後に、エンティティ自体にカプセル化できる重要なロジックがいくつかあります。たぶん、ラインアイテムにはラインアイテムの合計プロパティがあるので、そこには少し論理があります。

しかし、バランスを計算したい場合はどうでしょうか?ここで、その計算を行うには、請求書以外の場所に移動する必要があります。これは、(設計上)請求書が他のすべての請求書について何も知らないためです。それはCustomerエンティティで発生する可能性がありますが、他の会計モジュールでも発生する可能性が高くなります。

そして、リンクエンティティがあります。そのエンティティは、データレベルでエンティティ間の接続を提供することのみを目的としています。一般に、それらにはロジックがありません。

したがって、データ階層の最下部には単純なデータ転送オブジェクトがあります。集約オブジェクトに組み合わせると、ロジックの観点から有用になり、それらの一部またはすべては、単にデータとして扱われる、任意の数のソフトウェアモジュールによる処理の対象になります。考えてみると、多くのビジネスロジックをCustomerオブジェクトのようなものにベイクすることはあまり意味がありません。これは、そのオブジェクトをビジネスの特定の方法に緊密にバインドしているためです。

クラスはデータとロジックをカプセル化する必要がありますか?もちろん、そうすることが適切かつ有用な場合。 ソフトウェア設計の核となる考え方は適合性です。絶対的な原則はありません。ソフトウェア設計技術は、特定のシステムのコンテキストで常に評価して、特定の機能要件および非機能要件に適しているかどうかを判断する必要があります。

6
Robert Harvey

ドメインオブジェクトにはどのようなロジックを現実的に含めることができますか?

「有限状態機械」を考えてください:

Given message a
    When I start in state X
        Then I should end up in state Y
    When I start in state Y
        Then I should end up in state Z

実際、ドメインモデルは入力データのコピーと独自の内部ロジックを使用して、新しい状態を計算します。


さて、あなたは難しい部分に近いと思いますが、それを完全に特定していません。具体的には、どのデータがロックされるかドメインオブジェクトを更新するときですか? 「フレンドリストの2つの古いコピーをドメインオブジェクトに渡し、相互排除を計算させる」と「相互排除の結果の古いコピーをドメインオブジェクトに渡す」の間に大きな違いはありません。

ですから、データベースクエリを呼び出して、すべての共通の友人のリストを要求し、そのデータが数ナノ秒経過したら、それをドメインモデルに渡して、何をすべきかを判断させることができます。

もちろん、これらの数ナノ秒の間、およびドメインモデルが独自の作業を行っている間に、データベース内のデータは、クエリへの回答を変更する方法で変更される可能性があります。

それでも不十分な場合は、ドメインモデルが処理を行っている間、これらのリストが変更に対してロックされるようにする方法を検討する必要があります。

ロックする必要があるものがdifferentデータベースにある場合、さまざまな一貫性の問題に対して脆弱になります。

そして、場合によっては、そのようなロックは単に不可能であることに注意してください。ブカレストの天気が変化するのを防ぐためにドメインモデルで実行できることは何もありません。それは私のコントロールの外にあるので、私は過去の情報のスナップショットで満足している必要があります...

そして、場合によっては、物事が十分に大きくて不格好になる場合、主題の専門家に戻って「この制約は私たちの管理下にあるのか?」 」また、制約を緩和できる場合もあれば、アーキテクチャを修正するためにより多くの費用をアーキテクチャに費やす必要がある場合もあります。

正直なところ、「ドメインオブジェクト」は、ロジックを管理しやすくするために使用する幻想です。 transientメモリに保持するデータ構造は、計算に便利です。法廷で主張を弁護する必要がある場合、重要なのはその情報の永続的な表現です。このフレーミングでは、ドメインモデルで実際に行っているのは、計算永続ストレージを更新するために送信するコマンドを計算することです。

3
VoiceOfUnreason

あなたの質問の最初のいくつかのパラグラフはスポットです。 すべてオブジェクトは、データではなくビジネスロジックのみを公開する必要があります。これにより、すべてがオブジェクトに対してローカルで発生するため、アプリケーションの保守性が大幅に向上します。

一部のオブジェクトは、有用なビジネスロジックを提供するためにデータベースまたは他の外部システムにアクセスする必要があるかもしれないというあなたの論理的な結論に同意します。これが動作する唯一の方法であり、私はこの方法でソフトウェアをこの10年ほど作成してきました。できます。

これが混乱の原因です。

ドメインオブジェクトはリポジトリを使用してはならない、と人々は言う

これは完全に異なる視点から来ています。ほとんどのプロジェクトは、実際には、オブジェクト指向をユーザーが説明する方法で使用していません。ほとんどのプロジェクトは貧血の「オブジェクト」に完全に慣れています。これらの種類のシェル手続き型の設計は完全に異なる動作をし、この制限はそこで意味をなす場合があります。オブジェクトのデザインには適用されません。

ドメインオブジェクトは依存性注入の恩恵を受けない

そのようなルールはありません。これは一部のフレームワークまたはライブラリの制限ですか?明確にするために、はい、あなたのオブジェクトは他の皆と同じようにコンストラクターで依存関係を取得する必要があります。なぜ彼らはそうしませんか?

公正な警告:オブジェクト指向の権利を実行しようとしています。ほとんどのプロジェクト(過去20年間で私が見たすべてのプロジェクト)これを実行しない。つまり、事実上の標準から逸脱しています。

この道を進むと、「リポジトリ」、つまり完全な設計を再考せざるを得なくなる可能性があります。質問は layered architectures です。

2

これから別の角度から来ます。これまでの答えは、「システムの本質とその限界を理解する」という方法で素晴らしいでした。しかし、私はより具体的な答えを提供できると信じています:

「モデル化して」

ある意味で、あなたはドメインモデルの目的を誤解しています。 ではない相互作用するシステムの「もの」を表すことを意味します(それが物理モデルの目的です)。むしろ、それはシステムの動作要件の有用な抽象化であることを意味します。

ここで実際に話しているのは、「プロセスのどのステップ」がドメインで適切にモデル化されていることだけです。たとえば(あなたの例)、上で線引きした機能要件を持つUserエンティティを指定します。

  1. Userはあなたの友達ではないはずです
  2. あなたと他のUserには共通の友達がいてはいけません
  3. ブカレストでは雨が降っているはずです

私たちはcanが実際にこれらの条件に対して最適化するモデルを導き出しました。このモデルはどのように見えますか?

    class PotentialRelationship {

        int FromUserId;
        int ToUserId;

        bool AreAlreadyFriends;
        bool HaveCommonFriends;

        void Codify(WeatherInBucharest forecast);
    }

私がそこで何をしたか分かりますか? (AreAlreadyFriendsおよびHaveCommonFriendsの値の計算をデータベースに移動するように、内部状態をリフレームしました(これらの値を使用するビジネスロジックを移動するのではなく)。これで、最適化された新しいPotentialRelationshipエンティティを適切な値で単純にハイドレートして(ただし、それらが派生した場合)、ドメイン内の不変条件を維持できます。

上記のアプローチを使用してモデル化できない問題にまだ遭遇していません。ビジネスプロセスのsomeポイントで、いくつかの条件のresultを考慮して、決定を行うか、値を変更する必要があります。 Whereresultが計算されることは、実装の詳細です。データベースで実行する必要がある場合は、そうしてください。ドメインで「決定」/「変更」を維持するだけです。

別の言い方をすると、私たちのドメインには、ビジネスの決定を構成するcomputing中間値の責任を与える必要はなく、決定自体を仲介する責任を与える必要があります。これらすべての決定は、私たちの機能要件の本質ですよね?入力がどのように収集されるかは、実装の詳細です。

そして明確にするために。私は、上記について必ずしも「先行技術」を提供するほど推奨しているわけではありません。もちろん、上記を採用することが必ずしも意味をなさない場合があります。そうすることは、過剰なエンジニアリング(またはさらに複雑なこと)になる可能性がありますが、このような不変条件をモデル化するのは可能です。ビジネス価値はそうすることを賢明にします。

この答えは存在することを意図していますサイドに沿ってすでに提供されているもの。

2
king-side-slide