Правильний дизайн api що таке «один», «багато», «нуль» і «ніщо»

  • 31.07.15 7:07 •
  • ph_piter •
  • # 263895 •
  • Хабрахабр •
  • 6 •
  • 14109

- такий же як Forbes, тільки краще.

Привіт, наші постійні і епізодичні Новомосковсктелі.

Сьогодні хочемо запропонувати вам цікаву статтю про дизайн API і пов'язаних з цим підводні камені. Не питайте, як ми на неї натрапили, творчий пошук - справа дуже нелінійне.

При проектуванні API необхідно враховувати безліч чинників. Безпека, узгодженість, управління станом, стиль; здається, цей список нескінченний. Однак один фактор часто не береться до уваги - мова йде про масштабі. Якщо при проектуванні API з самого початку враховувати масштаби системи, то згодом (коли система буде рости) можна заощадити сотні годин робочого часу.

Часом складно сформулювати, що ж являє собою інтерфейс програмування додатків (API). C технічної точки зору до API можна віднести будь-яку функцію, що викликається кодом іншого програміста. Дискусії про те, який код «тягне» на API, виходять за рамки цієї статті, тому ми будемо вважати, що API - це і найпростіші функції.

У цій статті спеціально підібрані прості приклади, які служать лише для ілюстрації її основної теми. Використано функції на мові C #, але основні принципи, викладені тут, застосовні практично в будь-яких мовах, фреймворк або системах. Структури даних в статті змодельовані в поширеному реляционном стилі, використовуваному в багатьох промислових базах даних. Знову ж, приклади написані тільки в якості ілюстрацій, не слід розглядати їх в якості рекомендацій.

Припустимо, ви пишете найпростішу систему обробки замовлень для клієнта, і у вже визначені три основні класи (або, якщо хочете, «структури даних»). У класі Customer є «зовнішній ключ» (за термінологією баз даних) для класу Address, а у класу Order є зовнішні ключі для класів Address і Customer. Перед вами стоїть завдання створити бібліотеку, яку можна буде використовувати для обробки замовлень (Orders). Перше бізнес-правило на такий випадок: стан HomeAddress клієнта (Customer) має бути таким же, як стан BillingAddress у Order (замовлення). Не питайте чому, бізнес-правила зазвичай розумом не зрозуміти :)

Перевірка того, чи збігаються два поля - очевидно, просте завдання. Ви сподіваєтеся вразити начальника, тому сфабрикували рішення менш ніж за 10 хвилин. Функція VerifyStatesMatch повертає логічне значення, за яким викликає сторона зможе визначити, чи виконується бізнес-правило чи ні. Ви проганяєте вашу бібліотеку через кілька найпростіших тестів і переконуєтесь, що на виконання коду витрачається в середньому 50 мс, ніяких косяків в ньому не видно. Начальник дуже задоволений, дає вашу бібліотеку іншим розробникам, щоб ті використовували її в своїх додатках.

На наступний день приходите на роботу, а у вас на моніторі красується стікер: «Зайдіть до мене терміново - Шеф». Ви здогадуєтеся, що вчора так досягли успіху зі своєю бібліотекою, що сьогодні начальник вирішив доручити вам ще більш серйозне завдання. Однак незабаром виявляється, що з вашим кодом виникли серйозні проблеми.

Чи не найкращий початок дня, правда? Мені здається, що більшість розробників коли-небудь стикалися з подібною ситуацією. Ви думали, що написали бібліотеку «ідеально», а вона принесла цілу купу проблем. Але якщо правильно розуміти, що таке «Один», «Багато», «Нуль» і «Ніщо», то ви навчитеся розрізняти, де ваш API не відповідає очікуванням колег.

Правильний дизайн api що таке «один», «багато», «нуль» і «ніщо»

Перше керівництво до дії - розуміти, що таке «Один», і як з ним працювати. Я маю на увазі, що ваш API повинен в будь-якому випадку обробляти одну порцію очікуваного введення без всяких помилок. Такі помилки теоретично можливі, але повідомляти про них викликає стороні ви не зобов'язані. «Хіба це не очевидно?», - можете подумати ви. Що ж, давайте звернемося до прикладу і розглянемо, які помилки можуть виникнути при обробці Order.

Це найпростіший приклад, так що давайте виправимо наш код і підемо далі.

Правильний дизайн api що таке «один», «багато», «нуль» і «ніщо»

Повертаємося до вищеописаного сценарію. Треба поговорити з Бобом. Боб поскаржився, що код працює повільно, але значення 50 мс цілком відповідає тривалості виконання, очікуваної в системі з даної архітектурою. Але виявляється, що Боб обробляє 100 замовлень вашого найбільшого користувача одним пакетом, тому в циклі Боба на виконання вашого методу витрачається 5 секунд.

Ви. Боб, з чого ти взяв, що мій код занадто повільний? У ньому на обробку замовлення витрачається всього 50 мс.
Боб. Наш клієнт Acme Inc. вимагає, щоб їх пакетні замовлення оброблялися з максимальною швидкістю. Мені доводиться обслужити 100 замовлень, так що 5 секунд - це занадто довго.
Ви. Ой, я ж не знав, що нам доводиться обробляти замовлення пакетами.
Боб. Ну, це тільки для Acme, вони ж у нас найбільший клієнт.
Ви. Мені нічого не говорили ні про Acme, ні про пакетних замовленнях
Боб. Хіба твій код не повинен забезпечувати ефективну обробку декількох замовлень одночасно?
Ви. Ах ... так, звичайно.

Цілком очевидно, що сталося, і чому код здається Бобу «занадто повільним». Вам нічого не сказали ні про Acme, ні про пакетної обробки. Цикл Боба завантажує звичайний клас Customer і, швидше за все, 100 раз завантажує одну і ту ж запис Address. Цю проблему легко вирішити, якщо приймати масив замовлень, а не один, плюс додати якусь просте кешування. Ключове слово params в C # існує саме для таких ситуацій.

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

Варто вам відкрити свій API для «Багато» - і відразу доведеться підключити будь-який контроль кордонів. Що робити, наприклад, якщо хто-небудь відправить мільйон замовлень в ваш метод? Виходить таке велике число за межі можливостей даної архітектури? Саме в такому випадку стане в нагоді уявлення як про системну архітектурі, так і про бізнес-процесах. Якщо ви знаєте, що на практиці може знадобитися обробити максимум 10 000 замовлень, то можете впевнено встановити контроль на рівні 50 000. Таким чином ви гарантуєте, що ніхто не зможе покласти систему одним гігантським неприйнятним викликом.

Звичайно, список можливих оптимізацій цим не обмежується, але приклад показує, як можна позбутися від непотрібної роботи, якщо з самого початку розраховувати на «безліч» примірників.

Правильний дизайн api що таке «один», «багато», «нуль» і «ніщо»

Зрозуміло, перевірка всіх посилань на нуль - складна справа і часом надмірна міра. Однак ні в якому разі не слід довіряти введення, що приходить із джерела, який ви не контролюєте. Тому ми повинні перевіряти на нуль параметр «orders», а також екземпляри Order всередині нього на нуль.

Справно виконуючи перевірку на нуль, можна уникнути прикрих дзвінків від клієнтів, які звертаються за техподдержкой і запитували, що таке «екземпляр об'єкта». Я завжди віддаю перевагу перебдеть; нехай краще моя функція повертає значення за замовчуванням і логірует повідомлення (або надсилає попередження), ніж буде викидати досить-таки марну помилку «не вказує на екземпляр об'єкта». Зрозуміло, таке рішення повністю залежить від типу системи, від того, виконується код на клієнті або на сервері і т.д. Сенс в тому, що нуль можна ігнорувати, але тільки до тих пір, поки він вам не відгукнеться.

ПОЯСНЕННЯ: Чесно кажучи, я не стверджую, що функція повинна «сидіти склавши руки», якщо їй попадеться неприпустиме стан. Якщо нульові параметри для вашої системи неприйнятні, видавайте виняток (як ArgumentNull в .NET). Однак в деяких ситуаціях зовсім допустимо повернення значимого умовчання, а у видачі виключення немає ніякої необхідності. Наприклад, поточні методи зазвичай повертають те значення, яке було їм передано, якщо нічого не можуть зробити з цим значенням. Існує дуже багато факторів, що не дозволяють давати загальні рекомендації на випадок, коли доведеться зіткнутися з нулем.

Правильний дизайн api що таке «один», «багато», «нуль» і «ніщо»

Ви. Джон, що ти передаєш в мій код? На вигляд схоже на неповний Order.
Джон. Ой, вибач. Мені-то твій метод і не потрібен, але інша бібліотека вимагає, щоб я передавав параметр Order. Думаю, ця бібліотека викликає твій код. Я із замовленнями не працюю, але використовувати іншу бібліотеку повинен.
Ви. Цю бібліотеку потрібно поправити: криво же спроектовано!
Джон. Розумієш, та бібліотека органічно розвивалася разом з бізнес-завданнями - вони-то змінювалися. Написав її Метт, а його на цьому тижні не буде; в загальному, не знаю, як її змінити. А хіба твій код не повинен перевіряти, чи допустимо введення?
Ви. Так ... справді.

Сподіваюся, мені вдалося донести до Новомосковсктелей головну ідею цієї статті: не буває, щоб код міг прийняти будь-яку інформацію, що вводиться без проблем. При реалізації будь-якої функції або API доводиться враховувати, як буде використовуватися цей API. У нашому прикладі початкова функція збільшилася з 12 до 50 рядків, хоча ми не внесли в неї будь-яких кардинальних змін. Весь код, який ми додали, потрібен для забезпечення масштабування, контролю кордонів, а також для того, щоб функція зверталася з будь-яким введенням правильно і ефективно.
Обсяг даних, що зберігаються в останні роки зростав експоненціально, тому і масштаби вводяться будуть збільшуватися, тоді як якість цих даних може тільки падати. Якщо з самого початку правильно написати API, це може зіграти найважливішу роль для зростання бізнесу, адаптації під збільшується клієнтську базу, а в перспективі - для економії витрат на технічну підтримку (та й головного болю у вас буде менше).