web-dev-qa-db-ja.com

エンティティフレームワークエンティティ-Webサービスからのデータ-最高のアーキテクチャ?

現在、いくつかのWebアプリケーションでEntity FrameworkをORMとして使用しており、すべてのデータが単一のデータベースに格納されているため、これまではこれが適しています。リポジトリパターンを使用しており、これらを使用するサービス(ドメインレイヤー)があり、EFエンティティを直接ASP.NET MVCコントローラーに返します。

しかし、データベース内のユーザーに関連する追加情報を提供するサードパーティのAPI(Webサービスを介して)を利用する必要が出てきました。ローカルユーザーデータベースには、追加情報を取得するためにAPIに提供できる外部IDを保存します。利用可能な情報はかなりありますが、簡単にするために、そのうちの1つはユーザーの会社(名前、マネージャー、部屋、役職、場所など)に関連しています。この情報は、単一の場所で使用されるのではなく、Webアプリ全体のさまざまな場所で使用されます。

だから私の質問は、この情報を入力してアクセスするのに最適な場所はどこですか?さまざまな場所で使用されているため、Webアプリケーションで使用する場合は常にアドホックベースでフェッチすることはあまり賢明ではありません。そのため、ドメインレイヤーからこの追加データを返すことは理にかなっています。

私の最初の考えは、EFエンティティ(EFUser)を含むラッパーモデルクラス、および新しい情報を含む新しい 'ApiUser'クラスを作成することでした-ユーザーを取得すると、EFUserを取得し、追加のAPIからの情報、およびApiUserオブジェクトを設定します。ただし、これは単一のユーザーを取得する場合は問題ありませんが、複数のユーザーを取得する場合は失敗します。ユーザーのリストを取得するときにAPIをヒットすることはできません。

私の2番目の考えは、ApiUserを返すEFUserエンティティにシングルトンメソッドを追加し、必要なときにそれを設定することでした。必要なときだけアクセスするので、これは上記の問題を解決します。

または、最後の考えは、データベースにデータのローカルコピーを保持し、ユーザーがログインしたときにAPIと同期することでした。これは単なる同期プロセスであるため、最小限の作業であり、ヒットのオーバーヘッドはありません。ユーザー情報を取得するたびにDBとAPIを使用します。ただし、これらはデータを2か所に保存することを意味し、しばらくログインしていないユーザーのデータが古くなっていることも意味します。

この種のシナリオを処理するための最良の方法についてアドバイスや提案はありますか?

10
stevehayter

あなたの場合

あなたの場合、3つのオプションすべてが実行可能です。おそらく、最良のオプションは、asp.netアプリケーションが認識していない場所でデータソースを同期することです。つまり、フォアグラウンドでの2つのフェッチを毎回回避し、APIをデータベースとサイレントに同期します)。だからそれがあなたのケースで実行可能なオプションであるなら-私はそれをすると言う。

他の回答が示唆するようにフェッチを「1回」行うソリューションは、応答をどこにも永続化せず、ASP.NET MVCがすべてのリクエストに対して何度もフェッチを行うだけなので、あまり現実的ではないようです。

シングルトンは避けたいのですが、通常の理由がたくさんあるので、それはまったく良い考えではないと思います。

3番目のオプションがでない場合実行可能-1つのオプションは、遅延ロードすることです。つまり、クラスでエンティティを拡張し、need toベースでAPIにヒットさせるようにします。それは非常に危険な抽象化ですが、それはさらに魔法であり、自明ではない状態であるためです。

私はそれが本当にいくつかの質問に要約されると思います:

  • API呼び出しデータはどのくらいの頻度で変更されますか?頻繁ではありませんか? 3番目のオプション。しばしば?突然、3番目のオプションは実行可能ではありません。私はあなたと同じように臨時の電話に反対しているのかわかりません。
  • APIコールの費用はどれくらいですか?コールごとに支払いますか?彼らは速いですか?自由?それらが速い場合、毎回電話をかけることはうまくいくかもしれませんが、遅い場合は、何らかの予測を設定して電話をかける必要があります。もし彼らがお金がかかるなら-それはキャッシングの大きな動機です。
  • 応答時間はどのくらいの速さである必要がありますか?明らかに速い方が良いですが、場合によっては、特に単純化のために速度を犠牲にすることが価値がある場合があります。ユーザーに直接直面しています。
  • APIデータとデータはどのように異なりますか?2つは概念的に異なるものですか?その場合は、API結果を直接返すのではなく、外部にAPIを公開し、反対側で2番目の呼び出しを行い、その管理を処理する方がよい場合があります。

懸念の分離に関する一言または二言

ここでの懸念の分離についてボブソンが言っていることに反対することを許可します。結局のところ、そのようなロジックをそのようなエンティティに配置することは、懸念の分離と同様に悪いことです。

このようなリポジトリを使用すると、プレゼンテーション中心のロジックをビジネスロジックレイヤーに配置することで、懸念の分離に違反します。リポジトリは、asp.net mvcコントローラーでユーザーをどのように表示するかなど、プレゼンテーションに関連するものを突然認識します。

この関連する質問 では、コントローラから直接エンティティにアクセスする方法について質問しました。そこで答えの1つを引用させてください。

「カスタムピザショップであるBigPizzaへようこそ。注文を受け付けますか?」 「まあ、私はオリーブのピザを食べたいけど、トマトソースを上に、チーズを下にして、それを黒っぽい花崗岩の平らな岩のように固くなるまでオーブンで90分間焼きます。」 「わかりました、サー、カスタムピザは私たちの職業です。私たちは作ります。」

レジ係は台所に行きます。 「カウンターにサイコがあります。彼はピザを飲みたがっています...それは花崗岩の岩です...待ってください...私たちは最初に名前を付ける必要があります」と彼は料理人に言います。

「だめだ!」と料理人は叫びました。彼は400ページの紙のスタックを取り、「ここには2005年から花崗岩の岩がありますが...オリーブはありませんでしたが、代わりにパプリカ...またはここにトップトマトがあります...しかし、顧客はそれを望んでいましたたった30分焼きました。」 「たぶんそれをTopTomatoGraniteRockSpecialと呼ぶべきでしょうか?」 「しかし、それは底のチーズを考慮に入れていません...」レジ係:「それはスペシャルが表現することになっているものです。」 「しかし、ピザの岩がピラミッドのように形成されていることも特別だろう」と料理人は答えます。 「うーん...それは難しい...」と必死のレジ係は言う。

「私のピザはオーブンの中にありますか?」と突然、キッチンのドアから叫びました。 「このディスカッションはやめましょう。このピザの作り方を教えてください。そのようなピザは2度目にすることはありません」と料理人が決定します。 「OK、それはオリーブのピザですが、上部にトマトソースがあり、下部がチーズです。花崗岩の平らな岩のように黒く固くなるまで、オーブンで90分間焼きます。」

(残りの答えを読んでください、それは本当に素晴らしいimoです)。

データベースがあるという事実を無視するのは素朴です-データベースがあり、それを抽象化するのがいかに難しくても、どこにも行きません。アプリケーションはデータソースを認識します。 「ホットスワップ」することはできません。 ORMは便利です ですが、解決する問題がいかに複雑であるか、および多くのパフォーマンス上の理由(たとえば、Select n + 1を選択する)のためにリークします。

3

適切な 関心の分離 を使用すると、Entity Framework/APIレベルを超えるものは、データがどこから来ているのかさえ理解できません。 API呼び出しが(時間または処理の点で)高価でない限り、それを使用するデータへのアクセスは、データベースからのデータへのアクセスと同じくらい透過的でなければなりません。

これを実装する最良の方法は、必要に応じてAPIデータを遅延ロードするEFUserオブジェクトに追加のプロパティを追加することです。このようなもの:

partial class EFUser
{
    private APIUser _apiUser;
    private APIUser ApiUser
    {
       get { 
          if (_apiUser == null) _apiUser = API.LoadUser(this.ExternalID);
          return _apiUser;
       }
    }
    public string CompanyName { get { return ApiUser.UserCompanyName; } }
    public string JobTitle{ get { return ApiUser.UserJobTitle; } }
}

外部的には、CompanyNameまたはJobTitleのいずれかが初めて使用されるとき、単一のAPI呼び出しが行われるため(したがって、わずかな遅延)、オブジェクトが破棄されるまでの後続のすべての呼び出しは、次のようになります。データベースアクセスと同じくらい高速で簡単です。

2
Bobson

1つのアイデアは、常に追加情報を持たないようにApiUserを変更することです。代わりに、ApiUserにメソッドを配置してフェッチします。

ApiUser apiUser = backend.load($id);
//Locally available data directly on the ApiUser like so:
String name = apiUser.getName();
//Expensive extra data available after extra call:
UserExtraData extraData = apiUser.fetchExtraData();
String managerName = extraData.getManagerName();

ApiUserオブジェクトからUserExtraDataを抽出する必要がないように、これを少し変更して追加データの遅延読み込みを使用することもできます。

//Extra data fetched on first get:
String managerName = apiUser.lazyGetExtraData().getManagerName();

このように、リストがある場合、デフォルトでは追加のデータはフェッチされません。しかし、リストをたどっていてもアクセスできます!

0