web-dev-qa-db-ja.com

セカンダリ(ドリブン)ポートのUIアダプターの依存関係-六角形のアーキテクチャ

六角形のアーキテクチャ (ポートとアダプタ)をシステムに適用していますが、プライマリ(ドライバ)側のアダプタからセカンダリ(駆動)側のポートへの依存関係に気づきました。これは正しくないようです。これを処理する方法があるはずです。

ドメインに2つの非常に基本的なポートがあるとします。 1つはドライバー側にあり、もう1つはドリブン側にあります。

// Primary Port 
interface ForecastGenerating {
    Forecast[] generateForecastsForAllLocations();
    Forecast[] generateForecastsForLocation(Location location);
}

// Secondary Port
interface LocationFetching {
    Location[] fetchAllLocations();
    Location fetchLocationbyId(String locationId);
}

次に、以下のドメインロジックを使用します。 LocationFetchingポートの具体的な実装を想定しています。

// Domain Implementation
class ApplicationForecastGenerator implements ForecastGenerating {

    private LocationFetching locationFetching;
    public ApplicationForecastGenerator(LocationFetching locationFetching) {
        this.locationFetching = locationFetching
    }

    Forecast[] generateForecastsForAllLocations() {
        Location[] locations = this.locationFetching.fetchAllLocations();
        // Do my domain thing and generate forecasts
    }
    Forecast[] generateForecastsForLocation(Location location) {
        // Do my domain thing and generate forecasts
    }
}

そして最後に、これをすべて結び付けるプライマリアダプタがあります。

// Primary Adapter Implementation
class UIBasedForecastGenerator {

    private ForecastGenerating forecastGenerating;
    public UIBasedForecastGenerator(ForecastGenerating forecastGenerating) {
        this.forecastGenerating = forecastGenerating;
    }

    public void userTappedOnGenerateButton() {        
        Location location; // How does the primary adapter get its hands on the Location object?
        Forecast[] forecasts = this.forecastGenerating.generateForecastsForLocation(location);
        System.out.println(forecasts);
    }
}

プライマリアダプターの実装における問題は、Locationオブジェクトへの参照をどのように取得するかということです。私は間違いなくLocationFetchingポートを使用してそれに依存することができますが、私には少し奇妙に聞こえます。駆動側ポートに依存するドライバ側アダプタ。ドメインがこのオブジェクトを提供する責任を負うべきだと思いますが、ForecastGeneratingポートはそのような機能を公開すべきではありません。予測生成の範囲外のようです。

このアーキテクチャでは、このような依存関係をどのように処理しますか?

2
Guven

ポートはアプリケーションに属します(六角形)、またはドメインを呼び出します。

つまり、Locationはドメインオブジェクトです。

それをUI(プライマリアダプター)に公開するかどうかはユーザー次第です(たとえば、プライマリポートはDTOをプライマリアダプターに公開します)。

それ以外に、私は目的に応じてポートに名前を付けますで、 "ForDoingSomething"形式に一致します。 「このポートの目的は何ですか?」と自問してください...応答はポートの名前になります。

  • プライマリポート: "ForGeneratingForecast"( "ForecastGenerating"の代わり)
  • セカンダリポート: "ForFetchingLocations"( "LocationFetching"の代わり)
2
user352370

ForecastGeneratingは扱いにくい位置にあります...

_interface ForecastGenerating {
    Forecast[] generateForecastsForAllLocations(); //<How does it know what All means?
    Forecast[] generateForecastsForLocation(Location location); //<Location is an index, is this index bounded by something?
}
_

一方では、場所が何であるかを知っているかのように動作しています。一方、場所を管理する役割はないというふりをしています。

修正1:すべてを削除し、多場所の関数で置き換える可能性があります。

_interface ForecastGenerating {
    Forecast[] generateForecastsForLocations(Location[] locations); //<optional can be dropped.
    Forecast[] generateForecastsForLocation(Location location);
}
_

現在、その場所は推定されていません。本当に知りません。誰でも(UIBasedForecastGeneratorのような)予測に場所へのアクセスを取得する必要があることを要求し、LocationFetchingがそれを提供します。

修正2:サポートされている場所の特定のセットを表現できるようにします...

_interface ForecastGenerating {
    Location[] locations(); //<this is the definition of All.
    Forecast[] generateForecastsForAllLocations();
    Forecast[] generateForecastsForLocations(Location[] locations); //<optional can be dropped.
    Forecast[] generateForecastsForLocation(Location location);
}
_

今、それはすべてが何を意味するかについて秘密にされていません。すべてのlocations()を意味することは明らかです。 UIBasedForecastGeneratorが簡単に質問できるようになりました。


Locationオブジェクトが一般的な説明であり(おそらくいくつかの結合/交差機能がある)、任意のForecastGenerator実装で動作する場合-私は修正1を使用します。

LocationオブジェクトがそのForecastGeneratorだけを操作するための特別な説明である場合、私はフィックス2に頼ります。

1
Kain0_0

予測の生成は1つの孤立した問題であり、場所の取得は別の問題ですが、これら2つのプレゼンテーションは分離されていないようですはLocationの-​​combinationであり、Forecastはそれ自体が懸念事項です。

したがって、2つのシステムの調整を処理し、UIが必要とする可能性のあるすべてのデータを提供できるコントローラを中央に置くことは、私には適切と思われます。これは、ForecastオブジェクトとLocationオブジェクトの両方を返すことができるコントローラーを意味します。

現在、プライマリドライバーは、ドメインの調整(セカンダリアダプターの実行)の両方で結果を生成します。代わりに、ForecastGeneratingサービスをLocationFetching依存関係から解放します(それはwhereの場所がどこから来るかは関係ありませんareの場所だけです) )、LocationFetchingポートで動作する別のサービスを作成します。

ステップ1:サービスからポートを分離する

// Primary port
interface LocationForecastController {
    Forecast[] generateForecastForLocation(Location location);
    Forecast[] generateForecastForAll();
    Location[] getAllLocations();
    Location getLocationById(String locationId);
}
// Secondary Port
interface LocationFetching {
    Location[] fetchAllLocations();
    Location fetchLocationById(String locationId);
}

// Services
interface ForecastGeneratingService {
    Forecast[] generateForecastsForLocation(Location location);
    Forecast[] generateForecastsForMultipleLocations(Location[] locations);
}

interface LocationFetchingService {
    Location[] fetchAllLocations();
    Location fetchLocationById(String locationId);
}

ステップ2:具体的なコントローラーでサービスを調整する

// Application side port implementation
class ApplicationLocationForecastController extends LocationForecastController {
    private LocationService locationService;
    private ForecastGeneratingService forecastService;

    public ApplicationLocationForecastController(LocationService locationService, 
      ForecastGeneratingService forecastService) {
      this.locationService = locationService;
      this.forecastService = forecastService;
    }

    public Forecast[] generateForecastsForLocation(Location location) {
      return this.forecastService.generateForecastForLocation(location);
    }

    public Forecast[] generateForecastsForAll() {
      Location[] locations = this.locationService.fetchAllLocations();
      return this.forecastService.generateForecastsForMultipleLocations(locations);
    }

    public Location[] getAllLocations() {
      return this.locationService.getAllLocations();
    }

    public Location getLocationById(String locationId) {
      return this.locationService.fetchLocationById(locationId);
    }
}

ステップ3:サービスを実装する

// Location Service
class ConcreteLocationService extends LocationService {
  private LocationFetching locationFetching;

  public ConcreteLocationService(LocationFetching locationFetching) {
      this.locationFetching = locationFetching;
  }

  // ... Wrapper around locationFetching functions
}

class ConcreteForecastGeneratingService extends ForecastGeneratingService {
  // Presumably stateless?
  public Forecast[] generateForecastsForLocation(Location location) {
    // Domain logic, possibly delegated to Forecast object
  }

  public Forecast[] generateForecastsForMultipleLocations(Location[] locations) {
    // More domain logic.
  }
}

最後に:LocationForecastControllerを使用してアダプターを実装します

 // UI Adapter
class UIBasedForecastGenerator {
    private LocationForecastController locationForecastController;
    public UIBasedForecastGenerator(LocationForecastController locationForecastController) {
        this.locationForecastController = locationForecastController;
    }

    public void userTappedOnGenerateButton() {
        Location location = this.locationForecastController.getLocationById(locationId) // Assumes the relevant location ID is somewhere in the UI.
        Forecast[] forecasts = this.locationForecastController.generateForecastsForLocation(location);
        System.out.println(forecasts);
    }
}

この例ではおそらく1:1になるため、LocationFetchingの周りに別のラッパーを作成するのは少し工夫されているように見えるかもしれませんが、この方法では、プライマリポートがレイヤーによるセカンダリポートの実装の変更から分離されます抽象化とプライマリポートは、セカンダリポートの実装にもドメインロジック自体にも直接依存しません。これにより、これら2つの懸念事項を調整する必要のある他のプライマリポートによるサービスの再利用も解放されます。

1
JimJam