おしゃべりなインターフェースを回避する方法


10

背景: サーバーアプリケーションを設計し、さまざまなサブシステム用に個別のDLLを作成しています。話を簡単にするために、2つのサブシステムがあるとします。1)Users2)Projects

ユーザーのパブリックインターフェイスには、次のようなメソッドがあります。

IEnumerable<User> GetUser(int id);

また、Projectsの公開インターフェースには次のようなメソッドがあります。

IEnumerable<User> GetProjectUsers(int projectId);

したがって、たとえば、特定のプロジェクトのユーザーを表示する必要がある場合は、呼び出すことができます。これによりGetProjectUsers、オブジェクトがデータグリッドなどに表示するのに十分な情報を返します。

問題: 理想的には、Projectsサブシステムはユーザー情報も保存せず、プロジェクトに参加しているユーザーのIDのみを保存する必要があります。提供するためにGetProjectUsers、それが呼び出す必要GetUserUsers自身のデータベースに格納された各ユーザーIDのシステム。ただし、これには多数の個別のGetUser呼び出しが必要であり、Userサブシステム内に多数の個別のSQLクエリが発生します。私は実際にこれをテストしていませんが、このおしゃべりなデザインを使用するとシステムのスケーラビリティに影響します。

私はさておき、サブシステムの分離を置けば、私は可能性があり、両方のシステムにより、単一のスキーマアクセス可能で、すべての情報を保存し、Projects簡単に行うことができJOIN、単一のクエリですべてのプロジェクトのユーザーを取得します。クエリ結果からオブジェクトProjectsを生成Userする方法も知っている必要があります。しかし、これは多くの利点を持つ分​​離を壊します。

質問: 誰もがこれらの個別のGetUser呼び出しをすべて回避しながら、分離を維持する方法を提案できますGetProjectUsersか?


たとえば、私が考えていたのは、ユーザーが外部システムにラベルと値のペアでユーザーに「タグを付ける」機能を与え、特定の値を持つユーザーに要求することでした。たとえば、

void AddUserTag(int userId, string tag, string value);
IEnumerable<User> GetUsersByTag(string tag, string value);

次に、プロジェクトシステムは、プロジェクトに追加された各ユーザーにタグを付けることができます。

AddUserTag(userId,"project id", myProjectId.ToString());

GetProjectUsersの実行中、1回の呼び出しですべてのプロジェクトユーザーをリクエストできます。

var projectUsers = usersService.GetUsersByTag("project id", myProjectId.ToString());

私がこれについて確信が持てない部分は、はい、ユーザーはプロジェクトにとらわれませんが、実際にはプロジェクトメンバーシップに関する情報はプロジェクトではなくユーザーシステムに保存されます。私は自然に感じないので、ここで私が見逃している大きな欠点があるかどうかを判断しようとしています。

回答:


10

システムに欠けているのはキャッシュです。

あなたは言う:

ただし、これには多数の個別のGetUser呼び出しが必要であり、Userサブシステム内に多数の個別のSQLクエリが発生します。

メソッドの呼び出し数は、SQLクエリの数と同じである必要はありません。ユーザーに関する情報を1回取得しますが、変更されていない場合に同じ情報を再度照会するのはなぜですか?おそらく、すべてのユーザーをメモリにキャッシュして、SQLクエリがゼロになる可能性もあります(ユーザーが変更しない限り)。

一方、Projectsサブシステムでプロジェクトとユーザーの両方にを使用してクエリを実行すると、INNER JOIN追加の問題が発生します。コードの2つの異なる場所で同じ情報をクエリするため、キャッシュの無効化が非常に困難になります。結果として:

  • 後でいつでもキャッシュを導入しないか、

  • または、情報の一部が変更されたときに無効にする必要があるものを研究するために、数週間または数か月を費やすことになります。

  • または、キャッシュの無効化を簡単な場所に追加して、他の場所を忘れてしまい、バグを見つけるのが困難になります。


あなたの質問をもう一度読んで、私が初めて見逃したキーワード、スケーラビリティに気づきました。経験則として、次のパターンに従うことができます。

  1. システムが遅いかどうか(つまり、パフォーマンスの非機能要件に違反しているか、単に使用するのが悪夢か)を自問してください。

    システムが遅くない場合は、パフォーマンスについて気にしないでください。クリーンなコード、読みやすさ、保守性、テスト、ブランチカバレッジ、クリーンな設計、詳細でわかりやすいドキュメント、優れたコードコメントについて悩む。

  2. はいの場合は、ボトルネックを検索します。推測ではなく、プロファイリングによって行います。プロファイリングすることで、ボトルネックの正確な場所を特定し(推測する、ほぼ毎回問題が発生する可能性があります)、コードのその部分に集中できるようになります。

  3. ボトルネックが見つかったら、解決策を探します。そのためには、推測、ベンチマーク、プロファイリング、代替案の記述、コンパイラの最適化の理解、最適化の理解、Stack Overflowでの質問、低レベル言語(必要に応じてアセンブラーを含む)への移行などを行います。

Projectsサブシステムに情報を求めるサブシステムの実際の問題は何Usersですか?

最終的な将来のスケーラビリティの問題は?これは問題ではありません。すべてを1つのモノリシックソリューションにマージしたり、複数の場所から同じデータをクエリしたりすると(キャッシュの導入が難しいため、以下で説明するように)、スケーラビリティは悪夢になることがあります。

ある場合は、すでに顕著なパフォーマンスの問題は、その後、ステップ2は、ボトルネックを検索します。

実際にボトルネックが存在し、サブシステムProjectsを介したユーザーへの要求Users(およびデータベースクエリレベルにある)が原因であると思われる場合は、別の方法を検索する必要があります。

最も一般的な代替手段は、キャッシュを実装してクエリの数を大幅に減らすことです。キャッシュが役に立たない状況にある場合、さらにプロファイリングを行うと、クエリの数を減らすか、データベースインデックスを追加(または削除)するか、ハードウェアを増やすか、システム全体を完全に再設計する必要があることがわかります。


私があなたを誤解していない限り、あなたは「個々のGetUser呼び出しは維持するが、dbラウンドトリップを回避するためにキャッシングを使用する」と言っています。
ErenErsönmez15年

@ErenErsönmez:GetUserデータベースを照会する代わりに、キャッシュを調べます。つまり、GetUser(キャッシュが無効化されていない限り)データベースではなくメモリからデータをロードするため、実際に何回呼び出すかは問題ではありません。
Arseni Mourzenko

これは、「システムを単一のシステムにマージせずに雑談を取り除く」という主要な問題を強調しているわけではないので、良い提案です。私のユーザーとプロジェクトの例では、比較的少数の、めったに変更されないユーザーがいると信じるようになります。おそらくより良い例は、ドキュメントとプロジェクトでしょう。数百万のドキュメントがあり、毎日数千が追加され、プロジェクトシステムがドキュメントシステムを使用してドキュメントを保存するとします。それでもキャッシュを勧めますか?おそらく違うでしょう?
ErenErsönmez15年

@ErenErsönmez:データが多いほど、重要なキャッシュが表示されます。経験則として、読み取り数と書き込み数を比較します。「数千」のドキュメントが1日に追加され、1日に数百万のselectクエリがある場合は、キャッシュを使用する方が適切です。一方、数十億のエンティティをデータベースに追加selectしているにwhereもかかわらず、非常に選択的なsで数千のs しか得られない場合、キャッシュはそれほど役に立たない場合があります。
Arseni Mourzenko

あなたはおそらく正しいです-私はおそらく私がまだ持っていない問題を修正しようとしています。私はおそらく現状のまま実装し、必要に応じて後で改善しようとするでしょう。エンティティが追加されてから1〜2回しか読み取られない可能性が高いなどの理由でキャッシュが適切でない場合、質問に追加した解決策が機能する可能性はあると思いますか?それで大きな問題が見えますか?
ErenErsönmez15年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.