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


10

現在、いくつかの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か所に保存することを意味し、しばらくログインしていないユーザーのデータが古くなっていることも意味します。

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


it's not really sensible to fetch it on an ad-hoc basis - なぜ?パフォーマンス上の理由から?
Robert Harvey

アドホックベースでAPIを実行するつもりはありません。つまり、既存のエンティティ構造をそのまま維持し、必要に応じてWebアプリケーションでAPIアドホックを呼び出すだけです。つまり、多くの場所で行う必要があるので賢明です。
stevehayter

回答:


3

あなたの場合

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

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

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

3番目のオプションが実行可能でない場合、1つ目のオプションは遅延読み込みです。つまり、クラスでエンティティを拡張し、必要に応じて APIにヒットさせるようにします。それは非常に危険な抽象化ですが、それはさらに魔法で明白でない状態だからです。

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

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

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

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

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

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

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

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

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

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

(残りの答えを読んでください、それは本当にいいイモです)。

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


@Benjaminの非常に徹底的な応答に感謝します。私は最初、上記のボブソンのソリューションを使用してプロトタイプの作成を開始しました(彼が回答を投稿する前でも)が、あなたはいくつかの重要な点を挙げています。あなたの質問に答えるには:-API呼び出しはそれほど高価ではありません(無料で、しかも高速です)。-データの一部がかなり定期的に(場合によっては数時間おきに)変更されます。-速度はかなり重要ですが、アプリケーションの対象者は、高速読み込みの軽量化が絶対的な要件ではないようです。
stevehayter 2013

@stevehayterその場合、私はおそらくクライアント側からAPIへの呼び出しを実行します。安価で高速であり、きめ細かい制御が可能です。
Benjamin Gruenbaum

1
これらの回答に基づいて、私はデータのローカルコピーを保持することにあまり傾いていません。私は実際にはAPIを個別に公開し、追加のデータをそのように処理することに傾倒しています。これは、@ボブソンのソリューションのシンプルさの間の良い妥協かもしれないと思うが、私が少し快適である程度の分離も追加します。プロトタイプでこの戦略を検討し、調査結果を報告します(そして賞金を授与します!)。
stevehayter 2013

@BenjaminGruenbaum-私はあなたの主張に従っているのかわかりません。私の提案はリポジトリにプレゼンテーションをどのように認識させるのですか?確かに、APIでサポートされているフィールドがアクセスされたことは認識していますが、その情報を使用してビューが実行していることとは何の関係もありません。
ボブソン

1
私はすべてをクライアント側に移動することを選択しましたが、EFUserの拡張メソッドとして(これは、プレゼンテーションレイヤーの別のアセンブリに存在します)。このメソッドは単純にAPIからデータを返し、シングルトンを設定して繰り返しヒットしないようにします。オブジェクトの寿命は非常に短いので、ここではシングルトンを使用しても問題はありません。このように、ある程度の分離がありますが、EFUserエンティティを操作する便利さはまだ得られます。助けてくれたすべての回答者に感謝します。間違いなく興味深い議論:)。
stevehayter 2013

2

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

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

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呼び出しが行われます(したがって、わずかな遅延があります)が、オブジェクトが破棄されるまでの後続のすべての呼び出しは、データベースアクセスと同じくらい速くて簡単です。


@Bobsonに感謝します...これは実際に私が下り始めた最初のルートでした(ユーザーのリストの詳細を一括でロードするためにいくつかの拡張メソッドが追加されています-たとえば、ユーザーのリストの会社名を表示します)。これまでのところ私のニーズによく適合しているようですが、以下のベンジャミンはいくつかの重要なポイントを提起しているので、今週も評価を続けます。
stevehayter 2013

0

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();

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


ここであなたが何を意味するのか本当にわかりません-backend.load()では、すでにロードを行っています-確かにAPIデータをロードするでしょうか?
stevehayter 2013

つまり、明示的に要求されるまで、追加のロードを行うのを待つ必要があります-APIデータの遅延ロード。
Alexander Torstling 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.