クリーンアーキテクチャ:プレゼンターを含むユースケースまたはデータを返す?


42

クリーンアーキテクチャユースケースをさせインタラクタ応答/表示を処理するために(DIP以下、注入される)プレゼンタの実際の実装を呼び出すことを示唆しています。ただし、このアーキテクチャを実装し、インタラクターから出力データを返し、コントローラー(アダプター層内)がその処理方法を決定できるようにする人がいます。2番目のソリューションは、インタラクターへの入力ポートと出力ポートを明確に定義していないことに加えて、アプリケーションの責任をアプリケーション層から漏えいさせていますか?

入出力ポート

Clean Architectureの定義、特にコントローラー、ユースケースインタラクター、プレゼンター間の関係を説明する小さなフロー図を考慮すると、「ユースケースの出力ポート」がどうあるべきかを正しく理解しているかどうかはわかりません。

六角形アーキテクチャのようなクリーンアーキテクチャは、プライマリポート(メソッド)とセカンダリポート(アダプタによって実装されるインターフェイス)を区別します。通信フローに従って、「ユースケース入力ポート」がプライマリポート(したがって、単なるメソッド)になり、「ユースケース出力ポート」が実装されるインターフェイスになります。実際のアダプターを取得するコンストラクター引数、インタラクターが使用できるようにします。

コード例

コード例を作成するために、これはコントローラーコードになります。

Presenter presenter = new Presenter();
Repository repository = new Repository();
UseCase useCase = new UseCase(presenter, repository);
useCase->doSomething();

プレゼンターインターフェイス:

// Use Case Output Port
interface Presenter
{
    public void present(Data data);
}

最後に、インタラクター自体:

class UseCase
{
    private Repository repository;
    private Presenter presenter;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.repository = repository;
        this.presenter = presenter;
    }

    // Use Case Input Port
    public void doSomething()
    {
        Data data = this.repository.getData();
        this.presenter.present(data);
    }
}

プレゼンターを呼び出すインタラクター上

前の解釈は、前述の図自体によって確認されているようです。コントローラと入力ポートの間の関係は、「鋭い」頭の実線矢印で表されます(「関連付け」のUMLコントローラーは「ユースケース」を持ちます)、プレゼンターと出力ポートの間の関係は「白」の頭を持つ実線の矢印で表されます(「継承」のUMLは「実装」の1つではありませんが、おそらくとにかくそれが意味です)。

さらに、この別の質問への回答では、Robert Martinが読み取り要求時にインタラクターがプレゼンターを呼び出すユースケースを正確に説明しています。

マップをクリックすると、placePinControllerが呼び出されます。クリックの場所とその他のコンテキストデータを収集し、placePinRequestデータ構造を構築し、PlacePinInteractorに渡します。PlacePinInteractorはピンの場所を確認し、必要に応じて検証し、ピンを記録するPlaceエンティティを作成し、EditPlaceReponseを構築しますオブジェクトを編集し、プレイスエディタ画面を表示するEditPlacePresenterに渡します。

これをMVCでうまく動作させるために、アプリケーションロジックがアプリケーションレイヤーの外部にリークすることを望まないため、従来はコントローラーに入力されていたアプリケーションロジックをここでインタラクターに移動すると考えることができます。アダプターレイヤーのコントローラーはインタラクターを呼び出すだけで、プロセス内でマイナーデータ形式の変換を行う場合があります。

この層のソフトウェアは、ユースケースやエンティティに最も便利な形式から、データベースやWebなどの外部機関に最も便利な形式にデータを変換するアダプターのセットです。

元の記事から、インターフェイスアダプターについて話します。

データを返すインタラクター上

ただし、このアプローチの問題は、ユースケースがプレゼンテーション自体を処理する必要があることです。Presenterインターフェースの目的は、いくつかの異なる種類のプレゼンター(GUI、Web、CLIなど)を表すのに十分抽象的であり、実際には単に「出力」を意味することがわかりました。非常によく持っていますが、それでも私はそれに完全に自信がありません。

さて、きれいなアーキテクチャのアプリケーションをWebで見てみると、出力ポートをDTOを返すメソッドとして解釈している人しかいないようです。これは次のようになります。

Repository repository = new Repository();
UseCase useCase = new UseCase(repository);
Data data = useCase.getData();
Presenter presenter = new Presenter();
presenter.present(data);

// I'm omitting the changes to the classes, which are fairly obvious

これは魅力的です。なぜなら、プレゼンテーションを「呼び出し」する責任をユースケースの外に移動しているからです。また、このケースでは、ユースケースはまだ外側のレイヤーについて何も知らないため、依存関係の規則を破っていません。

ただし、ユースケースは、実際のプレゼンテーションが実行されるタイミングを制御しません(たとえば、その時点でロギングなどの追加処理を行ったり、必要に応じて完全に中止したりする場合に便利です)。また、コントローラーがgetData()メソッド(新しい出力ポート)のみを使用しているため、ユースケースの入力ポートを失ったことに注意してください。さらに、インタラクターに何らかのデータを要求して、実際の処理を行うように指示するのではなく、ここで「伝える、尋ねない」という原則を破っているように見えます。最初の場所。

ポイントへ

では、これら2つの選択肢のいずれかは、クリーンアーキテクチャによるユースケース出力ポートの「正しい」解釈ですか?両方とも実行可能ですか?


3
クロスポストは強く推奨されていません。これが質問を表示する場所である場合は、Stack Overflowから削除する必要があります。
ロバートハーベイ

回答:


48

Clean Architectureでは、ユースケースインタラクターがプレゼンターの実際の実装(DIPに続いて挿入される)を呼び出して、応答/表示を処理することを提案しています。ただし、このアーキテクチャを実装し、インタラクターから出力データを返し、コントローラー(アダプター層内)がその処理方法を決定できるようにする人がいます。

それは確かにCleanOnion、またはHexagonal Architectureではありません。つまり、この

ここに画像の説明を入力してください

MVCをそのように行う必要はありません

ここに画像の説明を入力してください

多くの異なる方法を使用してモジュール間で通信し、MVCと呼ぶことができます。MVCを使用して何かを教えても、コンポーネントがどのように通信するかは実際にはわかりません。それは標準化されていません。それが私に伝えるすべては、それらの3つの責任に焦点を合わせた少なくとも3つのコンポーネントがあるということです。

これらの方法のいくつかには異なる名前が付けられていますここに画像の説明を入力してください

そして、それらのすべてを正当にMVCと呼ぶことができます。

とにかく、流行語アーキテクチャ(Clean、Onion、Hex)がすべてあなたに求めていることを実際にキャプチャするものはありません。

ここに画像の説明を入力してください

飛び回るデータ構造を追加し(そして何らかの理由で上下逆さまに)、次のようになります

ここに画像の説明を入力してください

ここで明確にすべきことの1つは、応答モデルがコントローラーを通過しないことです。

あなたがイーグルアイズをしているなら、流行語のアーキテクチャだけが循環依存を完全に回避していることに気づいたかもしれません。重要なことは、コンポーネントを循環させることでコード変更の影響が広がらないことを意味します。気にしないコードにヒットすると、変更は停止します。

制御の流れが時計回りに流れるように上下を逆にした場合は不思議です。さらに、これらの「白い」矢印は後で説明します。

2番目のソリューションは、インタラクターへの入力ポートと出力ポートを明確に定義していないことに加えて、アプリケーションの責任をアプリケーション層から漏えいさせていますか?

ControllerからPresenterへの通信はアプリケーションの「レイヤー」を通過することを意図しているので、はい、ControllerをPresentersジョブの一部にすることはおそらくリークです。これはVIPERアーキテクチャに対する私の主な批判です。

これらを分離することが非常に重要である理由は、おそらくCommand Query Responsibility Segregationを調べることで最もよく理解できます。

入出力ポート

Clean Architectureの定義、特にコントローラー、ユースケースインタラクター、プレゼンター間の関係を説明する小さなフロー図を考慮すると、「ユースケースの出力ポート」がどうあるべきかを正しく理解しているかどうかはわかりません。

この特定のユースケースでは、出力を送信するAPIです。それ以上ではありません。このユースケースのインタラクターは、出力がGUI、CLI、ログ、またはオーディオスピーカーのいずれに送られるかを知る必要も、知りたくもありません。インタラクターが知る必要があるのは、可能な限り非常に単純なAPIだけで、その結果を報告します。

六角形アーキテクチャのようなクリーンアーキテクチャは、プライマリポート(メソッド)とセカンダリポート(アダプタによって実装されるインターフェイス)を区別します。通信フローに従って、「ユースケース入力ポート」がプライマリポート(したがって、単なるメソッド)になり、「ユースケース出力ポート」が実装されるインターフェイスになります。実際のアダプターを取得するコンストラクター引数、インタラクターが使用できるようにします。

出力ポートが入力ポートと異なる理由は、抽象化する層によって所有されてはならないためです。つまり、抽象化するレイヤーが変更を指示することを許可しないでください。アプリケーションポートとその作成者のみが、出力ポートを変更できると判断する必要があります。

これは、抽象化するレイヤーが所有する入力ポートとは対照的です。入力ポートを変更するかどうかは、アプリケーション層の作成者のみが決定する必要があります。

これらの規則に従うことで、アプリケーション層または内部層は外部層についてまったく何も知らないという考えが保持されます。


プレゼンターを呼び出すインタラクター上

前の解釈は、前述の図自体によって確認されているようです。コントローラと入力ポートの間の関係は、「鋭い」頭の実線矢印で表されます(「関連付け」のUMLコントローラーは「ユースケース」を持ちます)、プレゼンターと出力ポートの間の関係は「白」の頭を持つ実線の矢印で表されます(「継承」のUMLは「実装」の1つではありませんが、おそらくとにかくそれが意味です)。

その「白い」矢印の重要な点は、次のことができることです。

ここに画像の説明を入力してください

制御の流れを依存関係の反対方向に進めることができます!つまり、内側の層は外側の層について知る必要はありませんが、内側の層に飛び込んで戻ることができます!

これを行うことは、「interface」キーワードの使用とは関係ありません。これは、抽象クラスで行うことができます。ええ、拡張できる限り、(ick)具象クラスでそれを行うことができます。Presenterが実装する必要のあるAPIの定義のみに焦点を当てたものでそれを行うのは簡単です。白抜きの矢印は多型のみを求めています。どんな種類があなた次第です。

その依存関係の方向を逆にすることが非常に重要である理由は、依存関係反転の原理を研究することによって知ることができます。ここでこれらの図にその原理をマッピングしました

データを返すインタラクター上

ただし、このアプローチの問題は、ユースケースがプレゼンテーション自体を処理する必要があることです。現在、Presenterインターフェイスの目的は、いくつかの異なるタイプのプレゼンター(GUI、Web、CLIなど)を表すのに十分な抽象的であり、実際には単に「出力」を意味することがわかります。非常に良いかもしれませんが、それでも私は完全に自信がありません。

いいえ、それは本当にそれです。内側の層が外側の層を認識しないようにすることのポイントは、外側の層を削除、置換、またはリファクタリングできることです。そうすることで、内側の層のすべてが壊れることはありません。彼らが知らないことは彼らを傷つけません。それができれば、外側のものを必要なものに変更できます。

さて、きれいなアーキテクチャのアプリケーションをWebで見てみると、出力ポートをDTOを返すメソッドとして解釈している人しかいないようです。これは次のようになります。

Repository repository = new Repository();
UseCase useCase = new UseCase(repository);
Data data = useCase.getData();
Presenter presenter = new Presenter();
presenter.present(data);
// I'm omitting the changes to the classes, which are fairly obvious

これは魅力的です。なぜなら、プレゼンテーションを「呼び出し」する責任をユースケースの外に移動しているからです。また、このケースでは、ユースケースはまだ外側のレイヤーについて何も知らないため、依存関係の規則を破っていません。

ここでの問題は、データを要求する方法を知っているものはすべて、データを受け入れるものでなければならないということです。コントローラーがUsecase Interactorを呼び出す前に、応答モデルがどのように見えるか、どこに行くべきか、そして、それをどのように提示するかについて、至福のことに気付きません。

繰り返しになりますが、コマンドクエリの責任分離を調べて、それが重要な理由を確認してください。

ただし、ユースケースは、実際のプレゼンテーションが実行されるタイミングを制御しません(たとえば、その時点でロギングなどの追加処理を行ったり、必要に応じて完全に中止したりする場合に便利です)。また、コントローラーがgetData()メソッド(新しい出力ポート)のみを使用しているため、ユースケースの入力ポートを失ったことに注意してください。さらに、インタラクターに何らかのデータを要求して、実際の処理を行うように指示するのではなく、ここで「伝える、尋ねない」という原則を破っているように見えます。最初の場所。

はい!尋ねるのではなく伝えることは、手続き型ではなくこのオブジェクト指向を保つのに役立ちます。

ポイントへ

では、これら2つの選択肢のいずれかは、クリーンアーキテクチャによるユースケース出力ポートの「正しい」解釈ですか?両方とも実行可能ですか?

動作するものはすべて実行可能です。しかし、あなたが提示した2番目のオプションがClean Architectureに忠実に従うとは言いません。それはうまくいくかもしれません。しかし、Clean Architectureが求めるものではありません。


4
このような詳細な説明を書いてくれてありがとう。
スワニー

1
私はClean Architectureに頭を包もうとしてきましたが、この答えは素晴らしいリソースでした。非常によくやりました!
ネイサン

すばらしい詳細な回答..ありがとうございます.. UseCase実行中のGUIの更新、つまり大きなファイルのアップロード中のプログレスバーの更新に関するヒント(または説明)を教えてください。
イーウォック

1
@Ewoks、あなたの質問への迅速な回答として、Observableパターンを調べる必要があります。ユースケースは件名を返し、件名に進行状況の更新を通知できます。プレゼンターは件名にサブスクライブし、通知に応答します。
ネイサン

7

あなたの質問関連した議論で、ボブおじさんは彼のクリーンアーキテクチャにおける発表者の目的を説明しています

このコードサンプルを考えます:

namespace Some\Controller;

class UserController extends Controller {
    public function registerAction() {
        // Build the Request object
        $request = new RegisterRequest();
        $request->name = $this->getRequest()->get('username');
        $request->pass = $this->getRequest()->get('password');

        // Build the Interactor
        $usecase = new RegisterUser();

        // Execute the Interactors method and retrieve the response
        $response = $usecase->register($request);

        // Pass the result to the view
        $this->render(
            '/user/registration/template.html.twig', 
            array('id' =>  $response->getId()
        );
    }
}

ボブおじさんはこう言った:

" プレゼンターの目的は、ユースケースをUIの形式から分離することです。 この例では、$ response変数はインタラクターによって作成されますが、ビューによって使用されます。これにより、インタラクターがビューに結合されます。 、$ responseオブジェクトのフィールドの1つが日付であるとしましょう。そのフィールドは、さまざまな日付形式でレンダリングできるバイナリ日付オブジェクトになります。DD/ MM / YYYYなどの非常に具体的な日付形式が必要です。形式を作成するのは誰の責任ですか?インタラクターがその形式を作成する場合、ビューについての知識が深まりますが、ビューがバイナリ日付オブジェクト

を取得する場合、インタラクターについての知識が深すぎます。 応答オブジェクトからデータを取得し、ビュー用にフォーマットします。 ビューもインタラクターも、互いの形式を知りません。"

---ボブおじさん

(更新:2019年5月31日)

ボブおじさんの答えを考えるオプション#1(インタラクターがプレゼンターを使用できるようにするかどうか)はそれほど重要ではないと思います...

class UseCase
{
    private Presenter presenter;
    private Repository repository;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.presenter = presenter;
        this.repository = repository;
    }

    public void Execute(Request request)
    {
        ...
        Response response = new Response() {...}
        this.presenter.Show(response);
    }
}

...またはオプション#2を行います(インタラクターが応答を返すようにし、コントローラー内にプレゼンターを作成してから、プレゼンターに応答を渡します)...

class Controller
{
    public void ExecuteUseCase(Data data)
    {
        Request request = ...
        UseCase useCase = new UseCase(repository);
        Response response = useCase.Execute(request);
        Presenter presenter = new Presenter();
        presenter.Show(response);
    }
}

個人的には、次の例のように、データとエラーメッセージを表示するinteractor タイミングを制御できるようにするため、オプション#1を好みます

class UseCase
{
    private Presenter presenter;
    private Repository repository;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.presenter = presenter;
        this.repository = repository;
    }

    public void Execute(Request request)
    {
        if (<invalid request>) 
        {
            this.presenter.ShowError("...");
            return;
        }

        if (<there is another error>) 
        {
            this.presenter.ShowError("another error...");
            return;
        }

        ...
        Response response = new Response() {...}
        this.presenter.Show(response);
    }
}

... インタラクターの外部ではなくif/else、内部でのプレゼンテーションに関連するこれらを実行できるようにしたいinteractor

一方、我々はオプション#2をすれば、私たちは中にエラー・メッセージを保管しなければならないresponse、オブジェクトというリターンresponseからオブジェクトinteractorcontroller、そして作るcontroller 解析response対象物を...

class UseCase
{
    public Response Execute(Request request)
    {
        Response response = new Response();
        if (<invalid request>) 
        {
            response.AddError("...");
        }

        if (<there is another error>) 
        {
            response.AddError("another error...");
        }

        if (response.HasNoErrors)
        {
            response.Whatever = ...
        }

        ...
        return response;
    }
}
class Controller
{
    private UseCase useCase;

    public Controller(UseCase useCase)
    {
        this.useCase = useCase;
    }

    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        Response response = useCase.Execute(request);
        Presenter presenter = new Presenter();
        if (response.ErrorMessages.Count > 0)
        {
            if (response.ErrorMessages.Contains(<invalid request>))
            {
                presenter.ShowError("...");
            }
            else if (response.ErrorMessages.Contains("another error")
            {
                presenter.ShowError("another error...");
            }
        }
        else
        {
            presenter.Show(response);
        }
    }
}

response内部でエラーのデータを解析するのは好きではありません。controllerなぜなら、そうすることで冗長な作業interactorを行っているためcontrollerです。

私たちは後で私たちを再利用することを決定した場合にも、interactor例えば、コンソールを使用して現在のデータに、我々はすべてのそれらのコピー&ペーストすることを忘れないように持っているif/elsecontroller、当社のコンソールアプリケーションのを。

// in the controller for our console app
if (response.ErrorMessages.Count > 0)
{
    if (response.ErrorMessages.Contains(<invalid request>))
    {
        presenterForConsole.ShowError("...");
    }
    else if (response.ErrorMessages.Contains("another error")
    {
        presenterForConsole.ShowError("another error...");
    }
}
else
{
    presenterForConsole.Present(response);
}

我々はオプション#1を使用している場合、我々はこれを持っていますif/else 唯一の場所にinteractor


ASP.NET MVC(または他の同様のMVCフレームワーク)を使用している場合、オプション#2が最も簡単な方法です。

ただし、そのような環境でもオプション#1を実行できます。ASP.NET MVCでオプション#1を実行する例を次に示します。

public IActionResult ResultASP.NET MVCアプリのプレゼンターにいる必要があることに注意してください)

class UseCase
{
    private Repository repository;

    public UseCase(Repository repository)
    {
        this.repository = repository;
    }

    public void Execute(Request request, Presenter presenter)
    {
        if (<invalid request>) 
        {
            this.presenter.ShowError("...");
            return;
        }

        if (<there is another error>) 
        {
            this.presenter.ShowError("another error...");
            return;
        }

        ...
        Response response = new Response() {
            ...
        }
        this.presenter.Show(response);
    }
}
// controller for ASP.NET app

class AspNetController
{
    private UseCase useCase;

    public AspNetController(UseCase useCase)
    {
        this.useCase = useCase;
    }

    [HttpPost("dosomething")]
    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        var presenter = new AspNetPresenter();
        useCase.Execute(request, presenter);
        return presenter.Result;
    }
}
// presenter for ASP.NET app

public class AspNetPresenter
{
    public IActionResult Result { get; private set; }

    public AspNetPresenter(...)
    {
    }

    public async void Show(Response response)
    {
        Result = new OkObjectResult(new { });
    }

    public void ShowError(string errorMessage)
    {
        Result = new BadRequestObjectResult(errorMessage);
    }
}

public IActionResult ResultASP.NET MVCアプリのプレゼンターにいる必要があることに注意してください)

コンソール用に別のアプリを作成することにした場合、UseCase上記を再利用して、コンソール用にControllerand のみを作成できますPresenter

// controller for console app

class ConsoleController
{    
    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        var presenter = new ConsolePresenter();
        useCase.Execute(request, presenter);
    }
}
// presenter for console app

public class ConsolePresenter
{
    public ConsolePresenter(...)
    {
    }

    public async void Show(Response response)
    {
        // write response to console
    }

    public void ShowError(string errorMessage)
    {
        Console.WriteLine("Error: " + errorMessage);
    }
}

public IActionResult Resultコンソールアプリのプレゼンターにはないことに注意してください)


貢献してくれてありがとう。ただし、会話を読んでいると、理解できないことが1つあります。彼は、プレゼンターが応答からのデータをレンダリングすると同時に、インタラクターによって応答を作成すべきではないと言っています。しかし、誰が応答を作成していますか?アダプターレイヤーはアプリケーションレイヤーに依存する可能性があるため(ただし逆ではない)、インタラクターはアプリケーション固有の形式でプレゼンターにデータを提供する必要があると思います。
スワニー

申し訳ありません。議論からコード例を含めなかったため、混乱するかもしれません。コード例を含めるように更新します。
Jboyフラガ

ボブおじさんは、インタラクターが応答を作成すべきでないとは言わなかった。応答はインタラクターによって作成されます。ボブおじさんが言っているのは、インタラクターによって作成された応答がプレゼンターによって使用されるということです。次に、プレゼンターは「フォーマット」して、フォーマットされた応答をビューモデルに入れ、そのビューモデルをビューに渡します。<br/>それがそれを理解する方法です。
Jboyフラガ

1
それは理にかなっています。Clean Architectureでは「view」も「viewmodel」も言及していないため、「view」は「presenter」の同義語であるとの印象を受けました。アダプタ。
スワニー

2

ユースケースには、アプリケーションフローで必要なものに応じて、プレゼンターまたは返されるデータのいずれかを含めることができます。

さまざまなアプリケーションフローを理解する前に、いくつかの用語を理解しましょう。

  • ドメインオブジェクト:ドメインオブジェクトは、ビジネスロジック操作が実行されるドメイン層のデータコンテナーです。
  • モデルの表示:ドメインオブジェクトは通常、アプリケーションレイヤーのビューモデルにマップされ、ユーザーインターフェイスとの互換性と使いやすさを実現します。
  • プレゼンター:通常、アプリケーション層のコントローラーはユースケースを呼び出しますが、モデルマッピングロジックを表示するドメインを委任することをお勧めします(単一責任原則に従って)。

返されるデータを含むユースケース

通常の場合、ユースケースは、ドメインオブジェクトをアプリケーション層に返すだけで、アプリケーション層でさらに処理してUIで表示しやすくすることができます。

コントローラーはユースケースを呼び出す責任があるため、この場合は、レンダリングするビューに送信する前にドメインを実行してモデルマッピングを表示する各プレゼンターの参照も含まれます。

簡単なコードサンプルを次に示します。

namespace SimpleCleanArchitecture
{
    public class OutputDTO
    {
        //fields
    }

    public class Presenter 
    {
        public OutputDTO Present(Domain domain)
        {
            // Mapping takes action. Dummy object returned for demonstration purpose
            // Usually frameworks like automapper to the mapping job.
            return new OutputDTO();
        }
    }

    public class Domain
    {
        //fields
    }

    public class UseCaseInteractor
    {
        public Domain Process(Domain domain)
        {
            // additional processing takes place here
            return domain;
        }
    }

    // A simple controller. 
    // Usually frameworks like asp.net mvc provides url routing mechanism to reach here through this type of class.
    public class Controller
    {
        public View Action()
        {
            UseCaseInteractor userCase = new UseCaseInteractor();
            var domain = userCase.Process(new Domain());//passing dummy domain(for demonstration purpose) to process
            var presenter = new Presenter();//presenter might be initiated via dependency injection.

            return new View(presenter.Present(domain));
        }
    }

    // A simple view. 
    // Usually frameworks like asp.net mvc provides mechanism to render html based view through this type of class.
    public class View
    {
        OutputDTO _outputDTO;

        public View(OutputDTO outputDTO)
        {
            _outputDTO = outputDTO;
        }

    }
}

プレゼンターを含むユースケース

一般的ではありませんが、ユースケースでプレゼンターを呼び出す必要がある場合があります。その場合、プレゼンターの具体的な参照を保持する代わりに、インターフェイス(または抽象クラス)を参照ポイント(実行時に依存関係注入によって初期化する必要がある)として考慮することをお勧めします。

(コントローラー内部ではなく)別のクラスでモデルマッピングロジックを表示するドメインを持つことにより、コントローラーとユースケース間の循環依存関係も解除されます(ユースケースクラスでマッピングロジックへの参照が必要な場合)。

ここに画像の説明を入力してください

以下は、元の記事で説明した制御フローの単純化された実装であり、どのように実行できるかを示しています。図に示されているものとは異なり、簡単にするためにUseCaseInteractorは具象クラスであることに注意してください。

namespace CleanArchitectureWithPresenterInUseCase
{
    public class Domain
    {
        //fields
    }

    public class OutputDTO
    {
        //fields
    }

    // Use Case Output Port
    public interface IPresenter
    {
        OutputDTO Present(Domain domain);
    }

    public class Presenter: IPresenter
    {
        public OutputDTO Present(Domain domain)
        {
            // Mapping takes action. Dummy object returned for demonstration purpose
            // Usually frameworks like automapper to the mapping job.
            return new OutputDTO();
        }
    }

    // Use Case Input Port / Interactor   
    public class UseCaseInteractor
    {
        IPresenter _presenter;
        public UseCaseInteractor (IPresenter presenter)
        {
            _presenter = presenter;
        }

        public OutputDTO Process(Domain domain)
        {
            return _presenter.Present(domain);
        }
    }

    // A simple controller. 
    // Usually frameworks like asp.net mvc provides url routing mechanism to reach here through this type of class.
    public class Controller
    {
        public View Action()
        {
            IPresenter presenter = new Presenter();//presenter might be initiated via dependency injection.
            UseCaseInteractor userCase = new UseCaseInteractor(presenter);
            var outputDTO = userCase.Process(new Domain());//passing dummy domain (for demonstration purpose) to process
            return new View(outputDTO);
        }
    }

    // A simple view. 
    // Usually frameworks like asp.net mvc provides mechanism to render html based view through this type of class.
    public class View
    {
        OutputDTO _outputDTO;

        public View(OutputDTO outputDTO)
        {
            _outputDTO = outputDTO;
        }

    }
}

1

@CandiedOrangeからの回答には一般的に同意しますが、インタラクターがデータを再実行するだけで、コントローラーからプレゼンターに渡されるというアプローチにもメリットがあります。

これは、たとえば、Asp.Net MVCのコンテキストでクリーンアーキテクチャ(依存規則)のアイデアを使用する簡単な方法です。

この議論をさらに深く掘り下げるために、ブログ投稿を作成しました:https : //plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/


1

プレゼンターを含むユースケースまたはデータを返すユースケース?

では、これら2つの選択肢のいずれかは、クリーンアーキテクチャによるユースケース出力ポートの「正しい」解釈ですか?両方とも実行可能ですか?


要するに

はい。両方とも、ビジネスレイヤーと配信メカニズム間の制御の反転を考慮に入れている限り、どちらも実行可能です。2番目のアプローチでは、オブザーバー、メディエーター、他のいくつかのデザインパターンを利用して、IOCを導入することができます...

ボブおじさんは、Clean Architectureを使用して、多数の既知のアーキテクチャを合成し、OOP原則に幅広く準拠するための重要な概念とコンポーネントを明らかにしようとしています。

彼のUMLクラス図(下図)を独自のクリーンアーキテクチャ設計と考えると、逆効果になります。この図は、のために描かれている可能性が具体的な例 ...しかし、それははるかに少ない抽象彼が唯一であるインタラク出力ポートの設計その中の具体的な選択をしなければならなかった通常の建築表現よりもあるので、実装の詳細 ...

ボブおじさんのクリーンアーキテクチャのUMLクラス図


私の2セント

を返すことを好む主な理由UseCaseResponseは、このアプローチがユースケースの柔軟性を保ち、それらの間の合成汎用性一般化特定の世代)の両方を可能にすることです。基本的な例:

// A generic "entity type agnostic" use case encapsulating the interaction logic itself.
class UpdateUseCase implements UpdateUseCaseInterface
{
    function __construct(EntityGatewayInterface $entityGateway, GetUseCaseInterface $getUseCase)
    {
        $this->entityGateway = $entityGateway;
        $this->getUseCase = $getUseCase;
    }

    public function execute(UpdateUseCaseRequestInterface $request) : UpdateUseCaseResponseInterface
    {
        $getUseCaseResponse = $this->getUseCase->execute($request);

        // Update the entity and build the response...

        return $response;
    }
}

// "entity type aware" use cases encapsulating the interaction logic WITH the specific entity type.
final class UpdatePostUseCase extends UpdateUseCase;
final class UpdateProductUseCase extends UpdateUseCase;

UMLの使用例はお互いを含めたり拡張したりしており、異なるサブジェクト(エンティティ)で再利用可能として定義されていることに似ていることに注意してください。


データを返すインタラクター上

ただし、ユースケースは、実際のプレゼンテーションが実行されるタイミングを制御しません(たとえば、その時点でロギングなどの追加処理を行ったり、必要に応じて完全に中止したりする場合に便利です)。

これが何を意味するのか理解できないのですが、なぜプレゼンテーションのパフォーマンスを「コントロール」する必要があるのでしょうか?ユースケースの応答を返さない限り、それを制御しませんか?

ユースケースは、その応答でステータスコードを返して、操作中に正確に何が起こったかをクライアントレイヤーに知らせることができます。HTTP応答ステータスコードは、ユースケースの動作ステータスを記述するのに特に適しています…

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.