web-dev-qa-db-ja.com

クリーンアーキテクチャ:複数のUI要素にわたるユースケース

私は現在、Clean Architectureを使用してプロジェクトを作成しようとしています。そのUnity Engineプロジェクトは、タスクを容易にしません。

しかし、私が遭遇している問題ははるかに基本的であり、私が見たClean Architectureのユースケースのすべての例はかなり単純で、ほとんどCRUDであり、複数のステップがないように見えるという事実に関係しています。

ここで、実装したい(高レベルのCockburn定義)ユースケースには、いくつかの手順が含まれます。

  1. ユーザーが要素を作成します
  2. ユーザーが要素に関する情報を入力します
  3. ユーザーは追加の要素を作成し、それらを最初の要素にリンクします

現在、入力ポートと出力ポートを使用するClean Architectureで説明されている従来のユースケースでは、このワークフローを独自のプレゼンターとワークフロー自体で複数のUI要素にまたがるため、単一のユースケースとしてこのワークフローをモデル化する方法がわかりません。フォームを開始することを除いて、このワークフローの実行とは何の関係もないプレゼンター階層内の深いところからトリガーされています。

アプリケーションを可能な限り疎結合にしたいので、必要なすべてのプレゼンターへの参照を保持し、出力ポートを実装する神オブジェクトを介して密結合を導入したくありません。

私は、ほとんどのUIでリポジトリの状態変化をリッスンするだけでかなり手助けになるソリューションを持っていますが、これは今日同僚と議論されているように、データの流れがどのように機能するかについて少し混乱を招き、次のように、ユースケースでUIの状態を変更します。

outputPort.ChangeToInputDetailsState()

これは、ユースケースがどのように呼ばれるかを気にする必要がないという原則を完全に破っていると思います。

誰かがClean Architectureのユースケースの例をさらに情報を要求している場合、または一般的に、UI駆動環境でより複雑なユースケースでClean Architectureを使用するための情報源があれば、私は非常に満足します。

編集:必要に応じて詳細

コメントで尋ねられたので、私が何を意味するのかを少し詳しく説明すると、複数のUI要素にまたがります。特定のプロジェクトの詳細を述べることはできませんが、類推を試みることはできます。

ユースケース(Cockburn定義)は次のようなものです。

名前:ビデオの録画とアップロード

主演俳優:ユーザー

手順:

  1. 録画映像
  2. ビデオに注釈を付けてカットする
  3. ビデオをサーバーにアップロード

これらは、ステップ1、2、および3が完了するために独自のサブロジックを備えたUIの異なる部分を必要とするという意味で、複数のUI要素にまたがっています。これらは、複数のポイントから使用されます。このユースケースの定義をクリーンアーキテクチャのユースケースに1対1でマッピングできるかどうかはわかりません。それが可能である場合、入力/出力ポートは、さまざまなユースケースで使用される可能性のあるこれらすべてのさまざまなUIパーツを調整できる必要があります。

そのように詳しく説明すると、私の問題には、単一のコントローラー/プレゼンターに関連付けられた複数のユースケースが存在する可能性があるClean ArchitectureケースでのUIコンポーネントの再利用の問題のバリアントも含まれていると思います。

編集2:必要な情報を取得した後に戻る出力ポートの関数を使用する方法について詳しく説明します。

ここで私が見る問題は、クリーンアーキテクチャでは、出力ポート/プレゼンターは常に(ほとんど論理的な意味では)ばかげて見え、すべてのビジネスロジックの調整がユースケースの領域に含まれるということです。

ただし、追加情報を取得するために別のユースケースを呼び出してプレゼンテーションレイヤーに渡すのではなく、別のユーザーインターフェースを開くことはできません。

4
Blackclaws

さて、あなたはあなたがアーキテクチャ自体についていくつかの誤解を持っていると思います、そしてこれはあなたをつまずかせているので、それを整理してみましょう。

enter image description here

したがって、ドメインの動作(アプリケーションのビジネスロジック)は、ユースケースとエンティティを組み合わせたレイヤー内に存在します。内部でどのように分割するかはあなた次第ですが、ガイドラインは、「アプリケーションに依存しない」(コア)ドメインロジック/ルールをエンティティ内(動作を持つドメインモデル)、またはエンティティとそのレイヤーを共有するオブジェクト内に配置することです(貧血データ構造+関数)。ただし、実際にアプリケーションに依存しないロジックを共有するアプリケーションがいくつかない場合、またはそれが何であるかが完全に明確でない場合、優れたもののほとんどはおそらくユースケースレイヤーに含まれますが、それで問題ありません。

ここで私が目にする問題は、クリーンアーキテクチャでは、出力ポート/プレゼンターが常に(ほとんど論理的な意味で)馬鹿げているように見えることです。

入力ポートと出力ポートについては少し誤解していると思います。あなたは特定のドメインの振る舞いを持っています:それはいくつかの入力を必要とし、それから何かを行い、いくつかの出力またはいくつかの副作用を生成します。入力は入力ポートを介して受信されます。これは単なるこのドメインコードへのインターフェースです。これは、実際のインターフェースタイプ、基本クラス、またはパブリックインターフェース(パブリックメソッドとプロパティ)のいずれかです。いくつかのオブジェクト(「インタラクター」)。したがって、入力ポートは、ユースケースによって提供されるビジネスロジックの抽象的なビューを表すため(そして、うまくいけば、ユースケースに値のロジックが含まれるため)、ダムコンポーネント(ほとんどすべてのロジック)ではありません。はい、その実装はユースケースにありますが、メソッド(機能、何ができるかを説明するもの、つまりスマートコンポーネントか貧弱なデータ構造か)は、入力ポート)。ただし、それを通過するデータ構造(またはパラメーター)は、単純なデータ構造であるため、ばかげています。

同様に、出力ポートは抽象ある外部コンポーネントへのインターフェース(ビュー、サービス、データストア)であり、それがどのようにダム/スマートであるかは、それが表すコンポーネントの性質に依存します。 (「外部」とは、ユースケースレイヤーの外部を意味します)。

これは一般化されたビューであることに注意してください。場合によっては、2つのポートを1つのポートにマージすることもできます(たとえば、出力ポートから継承するものが何も必要ない場合、またはInteractorインターフェースによって返されるデータ構造を介してすべての出力を行う場合)。

ところで、Observerパターンまたはイベントを使用している場合、抽象Observerインターフェイス、またはイベントの場合はイベントハンドラーのシグネチャが、出力ポートの具体例です。

UIパターンではない

このユースケースの定義をクリーンアーキテクチャのユースケースに1対1でマッピングできるかどうかはわかりません。それが可能である場合、入力/出力ポートは、さまざまなユースケースで使用される可能性のあるこれらすべてのさまざまなUIパーツを調整できる必要があります。

クリーンなアーキテクチャはIパターンではありません; not各UI要素に、コントローラー、プレゼンター、および入出力ポートを持つインタラクターがあることを要求します。それはそれについてではありません-すべてで。厳密に言うと、コントローラーとプレゼンターは実際にはアーキテクチャーの一部ではありません(実際にはアーキテクチャーによって規定されていません)。境界を越えた関係はそうです。コントローラーとプレゼンターが存在するのは、この例でUI関連のロジックが構造化されているためです(MVC/MVPが一般的なパターンです)。また、InteractorはUIと概念的には関係ありません。 「外の世界」がドメインロジックと相互にやり取りする方法を処理するだけです。ユースケースは、一緒に機能するユースケースレイヤーの1つ以上のオブジェクトによって実装され、Interactorはエントリポイント(可能であればFacade)です。

入力ポートと出力ポートはorchestrate UIを認識しません。UIについては認識していません。また、UIがpartsで構成されていることを認識していません。それがプレゼンテーションロジックです。ユースケースには、さまざまな目的のためにseveral入力ポートと出力ポートがある場合があります。たとえば、ユーザー入力用、自動システムによる入力用、画面に出力するもの、データベースに出力するものなどです。 、またはWebサーバー。そして、これらのポートは、実際のコンポーネント、テストケース(TDDなど)、またはテストダブルに接続できます。

したがって、(ある程度)汎用的なUI要素があり、さまざまなユースケースのコンテキストで複数の画面に表示できるようになっている場合、つまり絶対に問題ないです。右側のインセットしないこれは、すべてのUI要素の構造であると言います。これは、UI側から開始されるフローの単なる例です。

プレゼンテーションロジックに関しては、UIに必要なオーケストレーションはプレゼンテーションレイヤーで行われます。ただし、ユーザーがビジネス上関心のあるオブジェクトに対する実際の作業は、UIによってInteractorに委任されます。多くの場合、画面は単一のインタラクターを使用しますが、異なるが関連するユースケースをサポートするためにそれらの2つまたは3つを参照できない理由や、同じユースケースのUX関連の内訳を表す複数の画面ができる理由はありません同じインタラクターを共有しないでください。

建築の境界

さて、ビジネスロジック(ユースケース)があり、何かを行うには入力データが必要です。渡したデータは、一般化された方法で「入力モデル」と呼ばれます(「モデル」は単に「表現」を意味します)。たとえば、問題を調査し、特定の方法で理解しました。コードでその問題を表すために使用するこれらのすべての概念、アイデア、データ構造などが思い付きました。入力モデルは、実際のデータ構造でも、メソッドの単なるパラメーターリストでもかまいません。それはばかげたことです。動作ではなく、アーキテクチャの境界を越えてトスできるデータのみです。これは実際には入力ポートインターフェースの一部です。「所有」されており、同じレイヤーで定義されています(使用例)。

入力ポートと出力ポートはどちらも、インタラクターと他のコンポーネントがどのように「やり取り」するかをキャプチャする単なるインターフェースであることに注意してください。そのため、これらのインタラクションのさまざまな側面に対するいくつかのメソッドが含まれ、各メソッドには独自の入力と/ or出力モデル(基本的に、それらが取得するパラメーターとそれらが返すもの、またはパラメーターとして他のコンポーネントに渡す)。覚えておくべきことは、それらすべてのメソッド、およびそれらを介して渡されるデータ構造と型は、レイヤーの境界にあるということです。レイヤーの内側はカプセル化されますそれらの後ろ。内側の層と相互作用する必要がある外側の層のコードは、境界にあるこれらの要素に関してプログラムされます。基本的には、レイヤーのAPIです。

ユースケースごとに複数の画面

複数の画面を含むマルチステッププロセスがある場合、実行可能なオプションの1つは、入力モデルにスタンドアロンのデータ構造を使用することです。これは、複数のビュー間でデータ構造を渡すことができるためです(ただし、以下をお読みください)。別の方法で行います)。

現在、入力の収集は、実際にはプレゼンテーションロジックの一部です。プレゼンテーション層は、そこで定義されたインターフェースを利用するという意味で、アプリケーションビジネスルール層に依存します。 (ここでも、「インターフェース」というより一般的な概念を使用しています)。具体的には、入力モデルを使用してデータをインタラクターに渡します。入力モデルとインタラクターの両方に、わかりやすい名前のBTWを付けます。プレゼンテーション層のコードを実装する場合、画面間で入力モデルを渡し、途中でユーザー入力を収集できます。または、それらが組み立てられたときに、最初にすべての関連画面にそれを挿入することもできます。最後の画面で、Interactorのメソッドを呼び出し、ユースケースの完全な入力を含むデータ構造を渡します。

場合によっては、データをローカルに保存すれば十分です(クラッシュした場合の作業の損失を防ぐため)。他のシナリオでは、特定のステップのどこかに(またはすべてのステップの後に)アップロードすることができます。いずれにせよ、これについて仮定を作成しないでください。ドメインの専門家であるユーザーと話し合って、実際に必要なもの、許容できるもの、不要なものを見つけます。必要以上に複雑にしないでください。

このプロセスでは、厳密にUI/UX関連のものを追跡する必要があります。例えば。一部のパネルが折りたたまれているかどうか、または一部のデータが非表示になっているかどうかを追跡する必要がある場合があります。ただし、これはたまたまユーザーの便宜のためであり(たとえば、作業時に何かに集中するのに役立ちます)、ビジネスによるものではありません。論理。その場合、特にUIがどうあるべきかというアイデアが十分に安定化されていないプロジェクトの段階にいる場合、そのようなデータを入力モデルに配置しても意味がありません。変更を導入する必要がある場合、入力モデルが邪魔になります。このアイデアは、UI/UX固有のものをプレゼンテーション層内に制限することです。いくつかのオプションがあります。入力モデルと追加データを含む複合構造を作成できます。構造と言語で許可されている場合は、入力モデルから派生できます。または、言語によってその方法が簡単になる場合は、別の特殊なデータ構造を使用してデータを収集し、関連するフィールドを最後のステップで入力モデルにコピーできます(または、入力モデルが単なるパラメーターリストの場合は、パラメータとしての関連プロパティ)。自由に使えるツールがあれば、機能する最も単純なことを行います。

途中のステップにビジネスロジックがあり、最終的なユースケース(すべてのデータの準備ができている場合)とは異なる場合は、そのための個別のユースケースを作成し、それを呼び出します。一方、たとえば、途中でアップロードする必要があるが、実際にはドメインロジックが含まれておらず、すべてが「ロジスティクス」またはCRUDに似ている場合は、bypassドメインアプリケーションのコアに完全に関連し、(おそらく特殊な目的で) Gateway を直接呼び出します。これは依存関係のルールに違反しないことに注意してください-レイヤー内でのカップリングは許可されますが、いつものように最小化/制御する必要があります。今後の変更でビジネスロジックが導入される場合は、再構築してユースケースレイヤーを通過します。そうでない場合は、そのままにしておきます。

制御の流れ

ただし、追加情報を取得するために別のユースケースを呼び出してプレゼンテーションレイヤーに渡すのではなく、別のユーザーインターフェースを開くことはできません。

設計上の問題があるのではないかと思います。たとえば、プレゼンターのビジネスロジックとプレゼンテーションロジックが混在している場合があります。もしそうなら、それを解きほぐしてみてください。ただし、すべての場所で一度にではなく、徐々に増やしてください。開発中のコードベースの小さな部分から始めて、必要に応じて他の部分を修正してください。

いずれにしても、アプリケーションの流れについて考えてみてください。画面の流れについても考えてください。場合によっては、同じ画面にすぐに表示する必要がある副作用があります。その場合は、(プレゼンテーション)出力ポートにプッシュするか、オブザーバーパターン(イベント)を使用します。また、まったく新しい画面を表示することもあります。そこで同じ戦略を使用できますが、Interactorのメソッドを呼び出してデータをプルするオプションもあります。

複数の画面がユースケースを共有している場合、ユースケースは変更された画面について何も知る必要はありません(その観点から、発生しているのは、メソッドがsomethingによって呼び出されているということだけです。 =、別の時間に)。各画面が異なるインタラクターを呼び出す場合、フローは次のとおりです。 Interactorでメソッドを呼び出すと、Interactorは何かを実行し、おそらく何らかの方法で何かを返します。 UIがそれを処理し、別の画面に切り替えてから、プロセスが繰り返されますが、画面とインタラクターが異なります。これらのフローを検討し、プレゼンテーションロジック、ビジネスロジック、ユースケースの実際を決定し、複雑すぎないバランスのとれたデザインを見つけてください。

1

もちろん、どのようなシナリオでもClean Architectureを機能させることは技術的に可能ですが、実際には アプリケーションが大きくなるとすぐに機能しなくなります

あなたが言及したように、それはCRUDアプリケーションで一種の働きをします、しかしそれは発見される他の例が偶然ではありません。また、 ncle Bob自身のプロジェクト を見ると、小規模でも非常に扱いにくい(つまり、保守できない)ことがわかります。

私が言っているのは、特にUIを多用するシナリオでは、使用するデザインが間違っているということです。クリーンアーキテクチャでは、UIは詳細ではありませんが、この場合は明らかにそうではありません(他のほとんどの場合はどちらも主張しません)。

2

クリーンアーキテクチャでは、ユースケースは、要求、ハンドラー、および応答の3つのコンポーネントで構成されます。

リクエストにはユーザー入力が含まれます。リクエスト自体も入力されるため、空のリクエストを作成することは不可能ではありませんが、通常、リクエストには少なくともリソースのIDが含まれています。

ハンドラーはこの特定の要求に属し、それ以外のものはありません。ハンドラーはデータフローの接着剤のようなものです。

  • リクエストを検証します(これを個別のリクエストバリデーターに委任することができます)
  • データベースまたは外部APIから必要なデータを収集します
  • このデータとリクエストのデータでドメインオブジェクトを作成(初期化)します
  • リクエストに関連付けられたドメイン機能を呼び出します
  • 最後に、すべてがうまくいくと、新しいドメインの状態が保持され、イベントが送信され、APIに通知されます。
  • 必要に応じて、応答を返します

あなたの質問では、3つのステップについて言及しています。これらが3つの個別のリクエストであるか、それとも単一のリクエストであるかを自問する必要がありますか?ドメインロジック/ビジネスルールを呼び出すためのすべてのデータを含む単一のリクエストを作成する前に、クライアント側のUIレイヤー内でこれらの3つの手順を実行できます。

私の経験では、このクリーンなアーキテクチャのデータフローはコマンドに最適です。

クエリの場合、通常、ページングやソートなどはもちろんのこと、さらに多くのクエリがあるため、このフローはすぐに退屈になります。コマンドが有効な状態を保持していると仮定すると、すべてのクエリに有効な状態が含まれていると見なすこともできます。

2
Rik D