web-dev-qa-db-ja.com

ビジネスロジックにアクセスするプレゼンテーション層

私は最近、DDD(ビジネスエンティティオブジェクト)およびn層(階層化)アーキテクチャにおけるその他の一般的なパターンについて多くの資料を読んでいます。私が問題を抱えていることの1つは、ほとんどの記事、ブログ、例などがシステムの1つの側面にしか触れていないように見えることです。ビジネスロジックを含むためのBLLの作成について話をする人もいます。 DAL経由のsavingデータのみについて話す人もいます(読み取りは無視)。 DTOだけについて話す人もいます。基本的なレベルでこれをすべてまとめることについて話しているものはまだ見つかりません。私がその資料の多くを自分でまとめようとすると、情報が相互に排他的である場合があります。

ここで(私にとって)物事は相互に排他的である傾向があります:

  • ビジネスロジックは、DDDビジネスエンティティオブジェクトに含まれています。これは、UIが直接作成するものではありません(DTOを取得します)。
  • DataはDTOを介してUIに渡されます。これにはnotがビジネスロジックを含む必要があります。 DTOにはゲッターとセッターのみを含める必要があります。

それは私の質問:につながります

UIを決定するためにこれらのDTOのデータを分析するためにビジネスロジックが必要な場合、UIはどのように構成されますか?

たとえば、UIを開発していて、この要件があるとします。削除ボタンを有効にするかどうかをコードで決定する必要があります。

システム要件:注文に商品がなく、注文のステータスが「オープン」の場合のみ、注文を削除できます。

もちろん、UIで次のようにチェックできます。

_Dim _order As OrderDTO = SomeServiceCall.ReadOrder(123)

If _order.Products.Count = 0 And _order.Status = "Open" Then 
    DeleteButton.Enabled = True
End If
_

しかし、これでビジネスロジックがUIコードに組み込まれているのではないでしょうか。注文をいつ削除できるかを決定する新しいまたは変更されたビジネスルールがある場合はどうなりますか?あなたがcould関数.CanDelete()をOrderDTOに追加している間、ビジネスロジックはOrderエンティティオブジェクトに属しているため、これは私が読んでいるすべてに基づいて間違った場所です。それで、UIに、そのとんでもない[削除]ボタンを有効または無効にするように伝えるにはどうすればよいですか?

1つのオプションは、[削除]ボタンを常に有効にし、[削除]アクションがOrderエンティティオブジェクトの検証に失敗したときに例外をスローすることです(補足:FluentValidationを見つけただけで、すばらしいようです)。しかし、それはずさんなUIです。必要なUIを取得するための「優れたアーキテクチャ」の選択肢は何ですか?

同じことが他の多くのUI状況にも当てはまり、DTOに含まれるデータ(ビジネスロジックなど)のさまざまな組み合わせに基づいて、一部の入力フィールドをロックアウトしたり、非表示にしたりできます。

多くの場合、DDD、DAL、BLL、DTOに関する説明では、実用的なUI要件が無視されているようです。

9
HardCode

システム要件:UIは、注文に商品がなく、注文のステータスが「オープン」である場合にのみ、注文の[削除]ボタンを有効にする必要があります。

これは、実装の詳細を要件に漏らすというclassic要件の誤りです。要点として、この要件には、ユーザーが削除を要求する方法がボタンを使用することであることを知る権利はまったくありません。要件は、UI要件ではありません。

ソフトウェア設計における最も基本的な質問は、「何について何がわかっているか」です。

ボタンがあることを知るのはUIの仕事です。プレゼンテーションレイヤーの仕事ではありません。プレゼンテーション層の仕事は、無効にされているコマンド機能があることを知ることです。ボタンなのか分からない。 delete、effacer、löschenのいずれのラベルが付いているかわかりません。

知識を分離することがこのすべてのポイントです。あ、はい。そのコードはUIに含めないでください。

ただし、.CanDelete()は、ユーザーがコマンドを発行する前に(有効にしたか無効にしたかを問わず)ユーザーに有効/無効ステータスを提示できないため、壊れた戦略です。これがUIの要件です。

UIでの有効化と無効化には、UIのビジネスロジックは必要ありません。ステータスの更新が送信されるたびに、UIがステータスの更新を受け入れる必要があるだけです。理由を理解する必要はありません。

したがって、.CanDelete()を無限にループしてポーリングする必要がない限り、実行する必要があるのは、このビジネスロジックを_order.Products.Countまたは_order.Statusは状態を変更した可能性があります。

これをすべてまとめたいですか?うまくいくものから始めましょう。次に、あまりにも多くのアイデアが混在している場所を見つけ、それらをばらばらにします。それを十分に行うと、最初に何を分離するかがわかり、時間を節約できます。

この方法で始めることが非常に重要です。懸念が完全に分離されたシステムを構築するだけの場合、それらが混在するシステムを修正する方法を学ぶことは決してありません。

5
candied_orange

注文をいつ削除できるかを決定する新しいまたは変更されたビジネスルールがある場合はどうなりますか? OrderDTOに関数.CanDelete()を追加することもできますが、ビジネスロジックはOrderエンティティオブジェクトに属しているため、これは私が読んでいるすべてに基づいた間違った場所です。

私にとって有効なこと:ドメインモデルは有限状態機械と考えています。状態マシンはmessagesに反応します。メッセージは、ローカルクロックのタイムスタンプからの読み取りを伴う「Time Has Passed」のような単純なものである場合があります。貨物の特定の旅程を選択する予約エージェントのような複雑なもの。状態マシンは、メッセージから必要な情報をコピーし、次の状態を計算します(遷移がない場合、または遷移が現在の状態にループバックする場合は、現在の状態である可能性もあります)。

モデルが現在の状態(別名、変更のユースケースのビューを提供する)を記述する場合、モデルは、計算された状態の表現だけでなく、受け取る準備ができている候補コマンドの表現も提供することによってそれを行います。

特定の例では、空の注文の候補コマンドのリストには、「アイテムの追加」コマンドと「注文の削除」コマンドの両方が含まれますが、ドメインロジックが削除を禁止している場合、注文の削除コマンドはありません。候補リストに。

@candied_orangeが指摘したように、候補となる各コマンドを適切なユーザーアフォーダンスに変換する方法を理解するのは、他の誰かの仕事です。

たとえば、Webインターフェイスでは、候補となるコマンドのリストを受け取り、それぞれのHTMLフォームを作成するモジュールがあるとします。これは、ドメインモデルの決定を行わないという意味で「ドメインロジック」ではありませんが、ドメインモデルメッセージからより一般的な目的の表現に変換しています。

しかし、Webインターフェイスをコマンドラインインターフェイスに置き換えるのと同じくらい簡単です。候補となるコマンドのリストは変更されていませんが、コマンドラインインターフェイスでのそれらの表現は確かに異なります。

ある意味では、DTOに ".CanDelete"を追加することは、正しいスペルのアイデアですが、そのスペルは使用しない場合があります。 DTOは不変のデータ構造であり、候補となるコマンドのリストがメモリ内に表現されており、クエリに使用する設計を自由に選択できます。

Dim _order As OrderDTO = SomeServiceCall.ReadOrder(123)

If _order.commands.contains("http://example.org/commands/deleteOrder") Then 
    DeleteButton.Enabled = True
End If

Data Transfer Object の基本的な考え方は、メソッド呼び出しの数を減らすように設計されているということです。つまり、多くの興味深い答えが組み込まれています。したがって、いくつかの冗長な情報をDTOにエンコードし、その結果、変更が容易な中央化ドメインロジックを取得します。

多くの場合、DDD、DAL、BLL、DTOに関する説明では、実用的なUI要件が無視されているようです。

はい。特にDDDは、「配管」と、DDDがもたらす可能性のある実際の合併症について話し合うのが苦手です。

3
VoiceOfUnreason

私はあなたの苦痛を感じ、数年前に同じ観察をしました。それは、オブジェクト指向がどうあるべきか、そして維持可能な設計を構成するものを再評価するといううさぎの穴に私を導きました。 短い答えうまくいかないであるため、この件名には多くを見つけることができません。 N層アーキテクチャ、DDD(ほとんどの人が実践している)、特にDTOは、すべて最適ではありません。永続性にとらわれないビジネス層は機能しません。これらのデザインのビジネスにとらわれないUIは実際には存在しません。

最初に、いくつかのルールに基づいていくつかのボタンをグレー表示する必要があることは完全に合理的です。 UIはユーザーに表示されるものであり、ユーザーがUIの用語を採用することは完全に自然です。

実装方法:一度に1つのステップについて考えてみましょう。 Orderが削除可能かどうかを判断するための知識はどこにあると考えられますか?これをOrder自体に期待するのはかなり合理的です。 Orderの内部状態とセマンティクスに依存するため、存在する必要があります。

では、Orderからの「UIへの移動」情報はどのようになっていますか?これは、上記のすべての設計が失敗する場所です。それらのすべてが何らかの方法でOrderのこの情報outを取得しようとします。そして、それをどのように行うかに関係なく、それがDTOの新しいフィールドであるか、ステータスの更新であるか、イベントであるか、その他何であれ、常にこれが今あなたがcoupleこの単純な機能にすべてのことを意味することを意味します。

これを1か所で取得して保守できる唯一の方法は、Orderこの情報を取得しないにすることです。代わりに、この情報を必要とするbehaviorを取得しますintoOrder、つまり、UIでpresentOrderを取得します。

したがって、Orderは、無効化/有効化された削除ボタンとすべてで、それ自体を提示できるはずです。実際には、UIは「ビジネス」に依存するべきではなく、「ビジネス」はUIに依存するべきです。 (または、「ビジネス」has必要に応じてUI)

過去20〜30年間にこれらのアーキテクチャと設計パターンによって伝えられてきたほとんどすべてに反する場合でも、それがあなたにとっても完全に合理的に聞こえることを願っています。

疑似コードの例を更新:

class Order {
   ...state: i.e. products, whatever...

   void cancel() { ... }

   ...

   UIComponent display() {
      return Panel(
         new Table(products),
         new Button("Track", ...),
         new Button("Cancel", products.empty?ENABLED:DISABLED, this->cancel()),
         ...);
   }
}

これは私が何を意味するかを示しています。 Orderは、それ自体を表示する方法を知っています。これは、KISS、オブジェクト指向、デメテルの法則に準拠し、保守可能などです。「ビジネス」と「UI」が同じアプリケーション内にある場合、理由は何もありませんnotこのようにします。

注文がキャンセル可能かどうかの決定はOrder内に留まることに注意してください。また、Order自体にリークするプレゼンテーションの詳細はありません。色、レイアウトなどはありません。

1

このようにDTOを使用するという考えで、どのようなアーキテクチャを想定していたのかはわかりませんが、ボブマーティンの非常に人気のある クリーンアーキテクチャ を取り上げ、そこで問題がどのように解決されるかを見てみましょう。

そのようなアーキテクチャでは、UIオブジェクトは、

  • 注文データの取得

  • ボタンの有効化と無効化(ただし、これをいつどのように行うかというロジックはありません)。

Presenterオブジェクト(このようなインターフェースインスタンスを保持する)は、注文エンティティからデータを取得し、そのデータを注文DTOとしてインターフェースを介してUIに渡し、注文の.CanDelete()メソッドを確認します。エンティティとインターフェイスを呼び出し、それに応じて削除ボタンを無効または有効にします。プレゼンターは、注文の状態を変更する可能性のあるシステム内の特定のイベントをリッスンし、.CanDelete()の評価(およびUIの更新)を繰り返すこともできます。

つまり、簡単に言えば、どの記事 "記事、ブログ、例など"を参照しているのかわかりませんが、そこに欠けているように見える要素は単にインターフェイスですとイベント。 「データはDTOを介してUIに渡される」という考えは、単に誤解または単純化しすぎている可能性がありますが、参照を提供しなかったため、これがどのソースから得られたのかを実際に伝えることはできません。

1
Doc Brown

データエンティティでDTOが1対1である必要はないので、BLのビジネスルールを使用して、DTOでCanDelete()を使用します。データのようにするには、おそらく「IsDeletable」属性として再パッケージ化します。

これが通常のパターンである場合は、何らかの構造を埋め込むことができます。 (IsDeletable、IsUpdatable)。

UIの代わりにAPIを実行していた場合(私の意見ではAPIはUIの一種です)、抽象的には https://en.wikipedia.org/wiki/HATEOAS のようなものです。

教義はたくさんありますが、完全に有効な慣行はありません。完璧主義者の手絞りとハッキングのバランスを取りながら、自分にとって効果的で、何かを届けることができるものを選ぶことは、スキルです。

1
LoztInSpace