web-dev-qa-db-ja.com

クリーンアーキテクチャでのREST APIの実装

ボブおじさんのクリーンアーキテクチャを使用してプルーフオブコンセプトアプリケーションを実装していて、少し問題に遭遇しました。

ボブおじさんのアーキテクチャーでは、インターフェースを使用して要求と応答を明示的に分離する必要があります。これはほとんどの場合(MVPパターンを使用してUIを実装する場合など)の問題ではありませんが、Spring MVCを使用してREST APIを作成するためにこれを適用する方法がわかりません。

私のControllerには、次のシグネチャを持つメソッドがあります。

Response<String> greet(String name)

/greetingにマップされ、名前を取り、名前の値に応じて異なる挨拶を出力します。

Controllerに挿入されるのは、名前を受け取って挨拶を作成し、挿入されたUseCaseを介して出力を送信するOutputPortです。

問題は、Controllerが入力と出力の両方と対話して応答を作成する必要があるため、この方法で入力と出力を分離できないことです。

これを "実装"する唯一の方法は、私が思いつくことですが、InputPortを使用して値を返すことです。これは、クリーンアーキテクチャが要求するものではなく、かなり悪いように聞こえます。

私はこれについて考えていましたが、私のControllerControllerPresenterの両方として同時に機能する方法を見つけることはできません。ここで何か不足していますか? REST APIの入力と出力の分離を、過度に複雑化することなく可能にする優れた設計はありますか?

編集:私は再びClean Architectureの第23章と第24章を読んでおり、私の質問をはるかにうまく表現できたはずです。 (実用的で完璧な)ソリューションとして私が現在持っているのは1次元の境界(219ページ)であり、これを拡張して、このインターフェースを2つの相互インターフェースに分離できるかどうか疑問に思っていました。うまくいけば、これは私のポイントを少し明確にします。

4
Carlos

わかりました、何が起こっているのかはわかります。コンテキストインピーダンスの不一致があります。

ボンドさん、今あなたは枠組みの中にいます。ここでは、構成のゲームのプレイ方法が少し異なります。

認識する基本的な考え方は次のとおりです。つまり、「ビュー」は、いわば、到着するすべてのHTTPリクエストによって変化します(より正確には、スコープはHTTPの存続期間に関連付けられていますConnectionが、それはここでは重要ではありません)。

つまり、 compose 新しいController->Use-Case-Interactor->Presenter各リクエストのパイプライン。

良い知らせは、Springは既にそれを隠蔽している。

悪いニュース:Springは、それらを分離するのではなく、単一の入出力境界を使用しています。

この丸いペグをマーティンが描いた四角い穴に合わせようとしているので、「悪いニュース」と言います。 Springがここで実行していることは「問題ない」と思います。これは、必要なインターフェースにとっては不便なだけです。

それでは、ふりをしてみましょう。入力境界と出力境界に接続されることを期待するユースケースインタラクターがあり、コントローラーにこのシグネチャがあります。それで?

Response<String> greet(String name) {
    InputBoundary in = inputBoundaryFor(name);

    // Arbitrary choice
    List<String> greetings = new ArrayList();
    OutputBoundary out = outputBoundaryFor(greetings);

    // Here's our composition with request scope.
    UseCaseInteractor uci = useCaseInteractorFor(in, out);

    uci.run();

    String greeting = greetings.get(0);

    return responseFrom(greetings.get(0));
}

UseCaseInteractorのAPIで少し運が良ければ、代わりに次のようになります。

final UseCaseInteractor uci = ...

Response<String> greet(String name) {
    InputBoundary in = inputBoundaryFor(name);

    // Arbitrary choice - any reasonable container for
    // for a result could be used here.
    CompleteableFuture<String> greetings = new ArrayList();
    OutputBoundary out = outputBoundaryFor(greetings);

    uci.run(in, out);

    String greeting = greetings.get();

    return responseFrom(greetings.get(0));
}

コールバックとして構造化すると、これはより身近なものになるでしょう。

Response<String> greet(String name) {
    // Arbitrary choice - any reasonable container for
    // for a result could be used here.
    CompleteableFuture<String> greetings = new ArrayList();

    uci.run(name, (greeting) -> {
        greetings.complete(greeting);
    });

    String greeting = greetings.get();

    return responseFrom(greetings.get(0));
}

ただし、核となる考えは、2つの機能があるということです。ユースケースインタラクターを含むモジュールには、次のような関数があります。

InputData -> OutputData

そしてあなたのWebレイヤーには、次のような関数があります

OutputData -> ViewModel

入力と出力の境界は単なるaこれらの2つの関数を作成する方法間違った方向を指す依存関係を導入することなくです。

3
VoiceOfUnreason