it-swarm-ja.com

内部でAPI呼び出しを実行するリポジトリ-DDD

注:私の質問はDDDに関連していますが、アーキテクチャおよびOO設計の観点からも、この質問に興味があります。この質問は、CQRSおよび/またはインターフェース分離の単純なケースの場合もあります。コード例はSwiftにあります。これは、この2番目に頭に浮かぶ最初の言語ですが、これは重要ではありません。

私のドメインには、ダウンロードしてユーザーに表示するPanoramaの概念があります。実装方法:PanoramasはGoogleストリートビューAPIからダウンロードされ、メモリにキャッシュされてから、デバイスのローカルに保存されます。

リポジトリがそのパブリックAPIの背後にある目標を達成するために必要なことは何でも隠しても問題ないことを読みました。データベースまたはAPIエンドポイントから保存/読み取りを行うかどうかは関係ありません。

これを念頭に置いて、ドメインレイヤーに属する次のインターフェイスを作成しました。

_protocol Panoramas
{
    public function findAll(): [Panorama]
    public function findOne(_ lat: Double, lon: Double)
    public function delete(_ panorama: Panorama)
    public function save(_ panorama: Panorama)
}
_

私の最初の問題はこれです:GoogleのストリートビューAPIでsave()を呼び出せません。また、delete()を呼び出せません。したがって、私の最初の考えは、「モデルの読み取りと書き込み」とインターフェースの分離でした。

_protocol Panoramas
{
    public function findOne(_ lat: Double, lon: Double)
}

protocol QueryablePanoramas: Panoramas
{
    public function findAll(): [Panorama]
}

protocol ModifiablePanoramas: QueryablePanoramas
{
    public function delete(_ panorama: Panorama)
    public function save(_ panorama: Panorama)
}
_

次に、インフラストラクチャレイヤーに次の具体的な実装を作成します。

_struct StreetviewApiPanoramaRepository: Panoramas { /** ... **/ }
struct InMemoryPanoramaRepository: ModifiablePanoramas { /** ... **/ }
struct DatabasePanoramaRepository: ModifiablePanoramas { /** ... **/ }
_

しかし、私は何かが欠けていると思います。 Panoramasはドメインの問題です。しかしQueryableまたはModifiable:これらは私にとって技術的なものです。これは一般的なパターンですか?これらに別の名前を付けますか?これをどのように改善できますか?

-

編集:さらに悪いことに、内部に保存されたパノラマはfile用です。

_struct Panorama {
    public let id: Int // Unique id from the database
    public let filepath: String // The location of the file on-disk
}
_

そう:

  • 私のデータベースロードリポジトリはORMを介してデータをロードし、データベースに保存されているファイルパスを使用して、ファイルを読み取って返す必要があります。そのため、データベース内のエンティティにはfilepath値が含まれており、ファイルをメモリに読み込むための2番目の手順が必要です。
  • メモリ読み込みリポジトリは、メモリからイメージを読み込むだけです。
  • Api-loadingリポジトリは、API呼び出しからファイルを読み取ります。

ここでの問題は、リポジトリが有効なエンティティを返すことになっているということです。しかし、画像をファイルに保存して保存する必要がある場合もあれば、API呼び出しからロードする必要がある場合もあります。

ここでデータモデルをめちゃくちゃにしたと思います。助けてください :)

3
James

一般に、インターフェース、プロトコル、継承は、オブジェクトのtaxonomiesの記述には適していません。つまり「QueryablePanoramasリポジトリはPanoramasリポジトリの一種であり、利用可能なすべてのパノラマを一覧表示することもできます」という記述はほとんど役に立ちません。

代わりに、インターフェイスを使用して、一部のオブジェクトがsedになる方法を説明することをお勧めします。リポジトリをクエリするだけのコードとリポジトリに書き込むコードによってリポジトリが消費される場合、それらの個別の関心事を個別のインターフェースに分割することは理にかなっています(これがインターフェース分離の原則です)。しかし、ここでは、コンシューマはリポジトリへの完全な読み取り/書き込み/クエリアクセスを必要とするようです。次に、複数のインターフェースを作成しても効果がありません。

すべてのデータソースがリポジトリのすべての操作をサポートできるわけではないという問題に注意します。ただし、これらのデータソースは、必ずしもリポジトリと同じであるとは限りません。ここでは、Street View APIはいくつかの機能を提供できるようですが、アプリケーションで完全に機能させるには、永続ストレージとペアにする必要があります。次に、その接続を確立するのはリポジトリの仕事です。例えば。 (疑似コードb/c私はSwiftを知りません):

_struct PanoramaRepository {
  private let storage = ...
  private let streetView = ...

  // first search locally cached panoramas, otherwise fall back to Street View
  public function findOne(lat, lon): Panorama? {
    if (lat, lon) in storage {
      return storage[lat, lon]
    } else {
      return streetView.search(lat, lon)
    }
  }

  // only return the saved panoramaas
  public function findAll(): [Panorama] {
    return storage
  }

  ...
}
_

これが可能かどうかは、そのリポジトリの規約によって異なります。例えば。 findOne()によって返されるパノラマがfindAll()によって返されるリストになければならないという不変条件がある場合、これは機能しません。しかし、そのような制限が存在しない場合、リポジトリは、使用される特定のAPIおよびストレージメカニズムを抽象化できます。

これを行うには、要件に応じていくつかのバリアントがあります。たとえば、デコレータパターンを使用して、オプションで既存のリポジトリにストリートビューのサポートを追加できます。

_struct PanoramasWithStreetView(baseRepository: Panoramas) {
  private let streetView = ...

  // use street view as a fall back if the repository doesn't return a result
  public function findOne(lat, lon): Panorama? {
    if let panorama = baseRepository.findOne(lat, lon) {
      return panorama
    } else {
      return streetView.search(lat, lon)
    }
  }

  // all other methods just delegate to the base repository
  public function findAll(): [Panorama] { return baseRepository.findAll() }
  ...
}

let panoramas: Panoramas = PanoramasWithStreetView(PanoramasInDatabase(...))
_

これは実装がより洗練されているかもしれませんが、アプリケーションをより脆弱にすることができる柔軟性をさらに追加します。例えば。ストリートビューAPIを使用しないリポジトリを誤って使用する可能性があります。

最後に、ストリートビューAPIの使用が本当にリポジトリに属しているかどうかを検討します。このAPIが多数の1つのデータソースだけでなく、APIからのパノラマをローカルに格納されたパノラマとは異なる方法で処理するビジネスロジックがある場合は、APIコードを別のサービスに抽出する方が良い場合があります。 境界付きコンテキストの考え方が役立つかもしれません。 API呼び出しはどのコンテキストに属していますか?これらのAPI呼び出しが「パノラマストレージ」コンテキストに属するように、これはコア問題ドメインの観点からの実装の詳細ですか?

設計の選択によっては、Panoramaの定義で十分かどうかを再検討する必要がある場合もあります。

  • パノラマが複数のソースから取得できる場合、特定のIDを指定することは困難です。 IDには名前空間(URIなど)があるか、(変更不可!)コンテンツ(ブロックチェーン、Git、またはIPFSのようなハッシュ)を記述するか、名前空間、タイムスタンプ、ランダム性、およびローカルIDコンポーネントを組み合わせて一意性を確保する必要があります( UUID)。
  • Panoramaが、直接表示できる画像、ローカルに保存された画像、またはより抽象的な画像ロケーター(URLなど)を表すかどうかを検討してください。ファイルパスを使用する場合は、このファイルパスが永続的であるか、単なる一時的なキャッシュであるかを検討してください。
2
amon