Як уникнути балакучих інтерфейсів


10

Передумови: я розробляю серверну програму і створюю окремі DLL для різних підсистем. Для спрощення речей, скажімо, у мене є дві підсистеми: 1) Users2)Projects

Загальнодоступний інтерфейс користувачів має такий спосіб, як:

IEnumerable<User> GetUser(int id);

Загальнодоступний інтерфейс Проектів має такий метод, як:

IEnumerable<User> GetProjectUsers(int projectId);

Так, наприклад, коли нам потрібно відобразити користувачів для певного проекту, ми можемо зателефонувати, GetProjectUsersі це поверне об'єкти з достатньою інформацією для показу в сітці даних або подібному.

Проблема: в ідеалі Projectsпідсистема також не повинна зберігати інформацію про користувачів, а вона повинна просто зберігати ідентифікатори користувачів, які беруть участь у проекті. Для того , щоб служити GetProjectUsers, він повинен зателефонувати GetUserв Usersсистемі для кожного ідентифікатора користувача , що зберігається у власній базі даних. Однак для цього потрібно багато окремих GetUserвикликів, викликаючи безліч окремих запитів sql всередині Userпідсистеми. Я ще не дуже перевіряв це, але наявність цього балаканого дизайну вплине на масштабованість системи.

Якби я відклав поділ підсистем, я міг би зберігати всі дані в одній схемі , доступні обидва систем і 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, він може запитувати всіх користувачів проекту за один дзвінок:

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

частина Я не впевнений у цьому: так, користувачі виявляють агностик проектів, але насправді інформація про членство в проекті зберігається в системі користувачів, а не в проектах. Я просто не відчуваю себе природним, тому я намагаюся визначити, чи є тут великий недолік, який мені не вистачає.

Відповіді:


10

Чого не вистачає у вашій системі - кеш.

Ти кажеш:

Однак для цього потрібно багато окремих GetUserвикликів, викликаючи безліч окремих запитів sql всередині Userпідсистеми.

Кількість викликів методу не повинно бути таким самим, як кількість запитів SQL. Ви отримуєте інформацію про користувача один раз, чому б ви знову запитували ту саму інформацію, якщо вона не змінювалася? Напевно, ви можете навіть кешувати всіх користувачів у пам'яті, що призведе до нульових SQL-запитів (якщо користувач не зміниться).

З іншого боку, роблячи Projectsзапит на підсистему як для проектів, так і для користувачів INNER JOIN, ви створюєте додатковий випуск: ви запитуєте одну і ту ж інформацію в двох різних місцях вашого коду, що робить вимкнення кешу надзвичайно важким. Як наслідок:

  • Або пізніше ви взагалі не будете вводити кеш,

  • Або ви витратите тижні чи місяці на вивчення того, що має бути визнано недійсним, коли інформація зміниться,

  • Або ви додасте недійсне кеш в прямому розташуванні, забувши інші, і внаслідок цього буде важко знайти помилки.


Читаючи ваше запитання, я помічаю ключове слово, яке я пропустив перший раз: масштабованість . Як правило, ви можете слідувати наступному шаблону:

  1. Запитайте себе, чи система повільна (тобто вона порушує нефункціональну вимогу продуктивності, або просто кошмар для використання).

    Якщо система не повільна, не турбуйтеся про продуктивність. Займайтесь чистим кодом, читабельністю, ремонтопридатністю, тестуванням, охопленням філій, чистим дизайном, детальною та зрозумілою документацією, хорошими коментарями до коду.

  2. Якщо так, знайдіть вузьке місце. Ви робите це не здогадками, а профілюванням . Профілюючи, ви визначаєте точне місце вузького місця (враховуючи те, що, коли ви здогадуєтесь , ви можете майже кожен раз помилятися), і тепер можете зосередитись на цій частині коду.

  3. Як тільки вузьке місце знайдено, шукайте рішення. Ви робите це, здогадуючись, бенчмаркінг, профілювання, пишете альтернативи, розумієте оптимізацію компілятора, розумієте оптимізацію, яка залежить від вас, задаючи питання щодо переповнення стека та переходячи до мов низького рівня (включаючи Assembler, коли це необхідно).

Яка актуальна проблема з Projectsпідсистемою, яка запитує інформацію про Usersпідсистему?

Можливе майбутнє питання масштабування? Це не проблема. Масштабованість може стати кошмаром, якщо ви почнете об'єднувати все в одне монолітне рішення або запитувати однакові дані з декількох локацій (як пояснено нижче через труднощі введення кешу).

Якщо вже є помітна проблема продуктивності, тоді, крок 2, шукайте вузьке місце.

Якщо виявиться, що насправді вузьке місце існує і пояснюється тим, що Projectsзапити користувачів через Usersпідсистему (і знаходиться на рівні запиту до бази даних), тільки тоді слід шукати альтернативу.

Найпоширенішою альтернативою було б впровадження кешування, різко зменшивши кількість запитів. Якщо ви знаходитесь в ситуації, коли кешування не допомагає, то подальше профілювання може показати вам, що вам потрібно зменшити кількість запитів, або додати (або видалити) індекси баз даних, або кинути більше обладнання, або повністю переробити всю систему .


Якщо я вас не розумію, ви говорите "зберігайте окремі дзвінки GetUser, але використовуйте кешування, щоб уникнути зворотних переходів".
Eren Ersönmez

@ ErenErsönmez: GetUserзамість того, щоб запитувати базу даних, буде шукати в кеші. Це означає, що насправді не має значення, скільки разів ви будете телефонувати GetUser, оскільки він завантажить дані з пам'яті замість бази даних (якщо кеш недійсний).
Арсеній Муренко

це гарна пропозиція, враховуючи, що я не зробив гарної роботи, висвітлюючи головну проблему, яка полягає в тому, щоб "позбутися балачки без об'єднання систем в єдину систему". Мій приклад користувачів та проектів, природно, призведе до того, що ви вважаєте, що існує порівняно невелика кількість користувачів, які рідко змінюються. Можливо, кращим прикладом були документи та проекти. Уявіть, що у вас є кілька мільйонів документів, тисячі додаються щодня, і система Project використовує систему Document для зберігання своїх документів. Ви все-таки рекомендуєте кешувати? Напевно, ні, правда?
Eren Ersönmez

@ ErenErsönmez: чим більше у вас даних, тим критичніше з'являється кешування. Як правило, порівняйте кількість прочитаних з кількістю записів. Якщо щодня додаються "тисячі" документів, а в день є мільйони selectзапитів, краще використовувати кешування. З іншого боку, якщо ви додаєте в базу даних мільярди сутностей, але отримуєте лише кілька тисяч selects з дуже вибірковим wheres, кешування може бути не таким корисним.
Арсеній Муренко

ти, мабуть, маєш рацію - я, мабуть, намагаюся виправити проблему, якої у мене ще немає. Я, мабуть, буду реалізовувати так, як є, і спробую згодом вдосконалити, якщо потрібно. Якщо кешування не підходить, оскільки, наприклад, об'єкти, ймовірно, будуть прочитані лише 1-2 рази після додавання, чи вважаєте ви, що можливе рішення I, додане до питання, може спрацювати? Чи бачите ви з цим величезну проблему?
Eren Ersönmez
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.