Background:サーバーアプリケーションを設計し、さまざまなサブシステム用に個別のDLLを作成しています。話を簡単にするために、2つのサブシステムがあるとします:1)Users
2)Projects
ユーザーのパブリックインターフェイスには、次のようなメソッドがあります。
IEnumerable<User> GetUser(int id);
また、Projectsの公開インターフェースには次のようなメソッドがあります。
IEnumerable<User> GetProjectUsers(int projectId);
したがって、たとえば、特定のプロジェクトのユーザーを表示する必要がある場合は、GetProjectUsers
を呼び出すことができます。これにより、データグリッドなどで表示するのに十分な情報を持つオブジェクトが返されます。
Problem:理想的には、Projects
サブシステムはユーザー情報も保存せず、プロジェクトに参加しているユーザーのIDのみを保存する必要があります。 GetProjectUsers
を提供するには、独自のデータベースに保存されている各ユーザーIDに対してGetUser
システムのUsers
を呼び出す必要があります。ただし、これには多数の個別のGetUser
呼び出しが必要になるため、User
サブシステム内に個別のSQLクエリが多数発生します。私は実際にこれをテストしていませんが、このおしゃべりなデザインを使用すると、システムのスケーラビリティに影響します。
サブシステムの分離を別にしておくと、両方のシステムからアクセス可能な単一のスキーマにすべての情報を保存することができますと、Projects
JOIN
を実行して、すべてのプロジェクトユーザーを1つのクエリで取得します。 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());
私がこれについて確信が持てない部分は、はい、ユーザーはプロジェクトにとらわれませんが、実際にはプロジェクトメンバーシップに関する情報はプロジェクトではなくユーザーシステムに保存されます。私はただ自然に感じないので、ここで私が見逃している大きな欠点があるかどうかを判断しようとしています。
システムに欠けているのはキャッシュです。
あなたは言う:
ただし、これには個別の
GetUser
呼び出しが必要になるため、User
サブシステム内で個別のSQLクエリが大量に発生します。
メソッドの呼び出し数は、SQLクエリの数と同じである必要はありません。ユーザーに関する情報を1回取得しますが、変更されていないのに、なぜ同じ情報をquery再度取得するのですか?おそらく、すべてのユーザーをメモリにキャッシュして、SQLクエリがゼロになる可能性もあります(ユーザーが変更しない限り)。
一方、Projects
サブシステムにプロジェクトとユーザーの両方にINNER JOIN
、追加の問題を導入します。コード内の2つの異なる場所で同じ情報をクエリするため、キャッシュの無効化が非常に困難になります。結果として:
後でキャッシュを導入しないか、
または、情報の一部が変更されたときに無効にする必要があるものを研究するために、数週間または数か月を費やすことになります。
または、キャッシュの無効化を簡単な場所に追加して、他の場所を忘れてしまい、バグの発見が困難になります。
あなたの質問をもう一度読んで、私が初めて見逃したキーワードに気づきました:スケーラビリティ。経験則として、次のパターンに従うことができます。
システムが遅いかどうかを確認してください(つまり、パフォーマンスの非機能要件に違反しているか、単に使用するのが悪夢か)。
システムが遅い遅い場合は、パフォーマンスについて気にしないでください。クリーンなコード、読みやすさ、保守性、テスト、ブランチカバレッジ、クリーンな設計、詳細で理解しやすいドキュメント、優れたコードコメントについて悩む。
はいの場合、ボトルネックを検索します。推測ではなく、プロファイリングで行います。プロファイリングすることで、ボトルネックの正確な場所を特定し(推測すると、ほぼ毎回間違っている可能性があります)コードのその部分に。
ボトルネックが見つかったら、解決策を探します。推測、ベンチマーク、プロファイリング、代替の記述、コンパイラの最適化の理解、最適化の理解、Stack Overflowでの質問、および低レベル言語(必要な場合はアセンブラーを含む)への移行によって行います。
Projects
サブシステムに情報を要求するUsers
サブシステムの実際の問題は何ですか?
最終的に将来のスケーラビリティの問題ですか?これは問題ではありません。すべてを1つのモノリシックソリューションにマージしたり、複数の場所から同じデータを照会したりすると(キャッシュの導入が難しいため、以下で説明するように)、スケーラビリティは悪夢になることがあります。
顕著なパフォーマンスの問題がすでにある場合は、ステップ2でボトルネックを検索します。
実際にボトルネックが存在しているように見え、これがProjects
サブシステムを通じてユーザーにUsers
リクエストを送信している(そしてデータベースクエリレベルにある)ためである場合、代替。
最も一般的な代替策は、キャッシュを実装してクエリの数を大幅に減らすことです。キャッシュが役に立たない状況にある場合、さらにプロファイリングを行うと、クエリの数を減らすか、データベースインデックスを追加(または削除)するか、ハードウェアを増やすか、システム全体を完全に再設計する必要があることがわかります。