Підходи до проектування RESTful API

  1. Частина 1. Теорія
  2. Кращі рішення (незалежні від технологій)
  3. Властивості HTTP-методів
  4. Частина 2. Практика
  5. проектуємо інтерфейс
  6. контролер
  7. OData (www.odata.org)
  8. параметри запитів
  9. EnableQuery Attribute
  10. CRUD
  11. Антішаблони
  12. проектуємо API
  13. Доменна модель
  14. Bounded context (BC)
  15. CQRS - Command Query Responsibility Segregation
  16. REST without PUT
  17. Fine-grained VS coarse-grained
  18. нумерація версій
  19. документація

У цій статті я поділюся досвідом проектування RESTful API - на конкретних прикладах покажу, як робити хоча б прості сервіси красиво. Також ми поговоримо, що таке API і навіщо він потрібен, поговоримо про основи REST - обговоримо, на чому його можна реалізовувати; торкнемося основних веб-практик, які залежать і не залежать від цієї технології. Також дізнаємося, як складати хорошу документацію, витрачаючи на це мінімум зусиль, і подивимося, які існують способи нумерації версій для RESTful API.

Частина 1. Теорія

Отже, як ми всі знаємо, API - application programming interface (інтерфейс програмування додатків), набір правил і механізмів, за допомогою яких один додаток або компонент взаємодіє з іншими

Чому хороший API - це важливо?

  • Простота використання і підтримки. Хороший API просто використовувати і підтримувати.
  • Гарна конверсія в середовищі розробників. Якщо всім подобається ваш API, до вас приходять нові клієнти і користувачі.
  • Вище популярність вашого сервісу. Чим більше користувачів API, тим вище популярність вашого сервісу.
  • Краще ізоляція компонентів. Чим краще структура API, тим краще ізоляція компонентів.
  • Гарне враження про продукт. API - це як би UI розробників; це те, на що розробники звертають увагу в першу чергу при зустрічі з продуктом. Якщо API кривої, ви як технічний експерт не будете рекомендувати компаніям використовувати такий продукт, набуваючи щось стороннє.

Тепер подивимося, які бувають види API.

Види API за способом реалізації:

  • Web service APIs
    • XML-RPC and JSON-RPC
    • SOAP
    • REST
  • WebSockets APIs
  • Library-based APIs
  • Class-based APIs

Види API за категоріями застосування:

  • OS function and routines
    • Access to file system
    • Access to user interface
  • Object remoting APIs
  • Hardware APIs
    • Video acceleration (OpenCL ...)
    • Hard disk drives
    • PCI bus
    • ...

Як ми бачимо, до Web API відносяться XML-RPC і JSON-RPC, SOAP і REST.

RPC (remote procedure call - «віддалений виклик процедур») - поняття дуже старе, що поєднують давні, середні і сучасні протоколи, які дозволяють викликати метод в іншому додатку. XML-RPC - протокол, що з'явився в 1998 р незабаром після появи XML. Спочатку він підтримувався Microsoft, але незабаром Microsoft повністю переключилася на SOAP, тому в .Net Framework ми не знайдемо класів для підтримки цього протоколу. Незважаючи на це, XML-RPC продовжує жити до сих пір в різних мовах (особливо в PHP) - мабуть, заслужив любов розробників простотою.

SOAP також з'явився в 1998 р стараннями Microsoft. Він був анонсований як революція в світі ПО. Не можна сказати, що все пішло за планом Microsoft: була величезна кількість критики через складність і ваговитості протоколу. У той же час, були і ті, хто вважав SOAP справжнім проривом. Протокол продовжував розвиватися і плодитися десятками нових і нових специфікацій, поки в 2003 р W3C не затвердила в якості рекомендації SOAP 1.2, який і зараз - останній. Сімейство у SOAP вийшло значне: WS-Addressing, WS-Enumeration, WS-Eventing, WS-Transfer, WS-Trust, WS-Federation, Web Single Sign-On.

Потім, що закономірно, все ж з'явився дійсно простий підхід - REST. Абревіатура REST розшифровується як representational state transfer - «передача стану уявлення» або, краще сказати, представлення даних в зручному для клієнта форматі. Термін "REST" був введений Роєм Філдінгом в 2000 р Основна ідея REST в тому, що кожне звернення до сервісу переводить клієнтську програму в новий стан. По суті, REST - не протокол і не стандарт, а підхід, архітектурний стиль проектування API.

Які принципи REST?

  • Клієнт-серверна архітектура - без цього REST немислимий.
  • Будь-які дані - ресурс.
  • Будь-ресурс має ID, за яким можна отримати дані.
  • Ресурси можуть бути пов'язані між собою - для цього в складі відповіді передається або ID, або, як частіше рекомендується, посилання. Але я поки не дійшов до того, щоб все було настільки добре, щоб можна було легко використовувати посилання.
  • Використовуються стандартні методи HTTP (GET, POST, PUT, DELETE) - т. К. Вони вже закладені в складі протоколу, ми їх можемо використовувати для того, щоб побудувати каркас взаємодії з нашим сервером.
  • Сервер не зберігає стан - це значить, сервер не відокремлює один виклик від іншого, не зберігає всі сесії в пам'яті. Якщо у вас є яка-небудь масштабується хмара, якась ферма з серверів, яка реалізує ваш сервіс, немає необхідності забезпечувати узгодженість стану цих сервісів між усіма вузлами, які у вас є. Це значною мірою знижує масштабування - при додаванні ще одного вузла все прекрасно працює.

Чим REST хороший?

  • Він дуже простий!
  • Ми переіспользуем існуючі стандарти, які в ходу вже дуже давно і застосовуються на багатьох пристроях.
  • REST грунтується на HTTP => доступні всі плюшки:
    • Кешування.
    • Масштабування.
    • Мінімум накладних витрат.
    • Стандартні коди помилок.
  • Дуже хороша поширеність (навіть IoT-пристрої вже вміють працювати на HTTP).

Кращі рішення (незалежні від технологій)

Які в сучасному світі є кращі рішення, не пов'язані з конкретною реалізацією? Ці рішення раджу використовувати обов'язково:

  • SSL всюди - найважливіше у вашому сервісі, т. К. Без SSL авторизація та аутентифікація безглузді.
  • Документація і версійність сервісу - з першого дня роботи.
  • Методи POST і PUT повинні повертати назад об'єкт, який вони змінили або створили, - це дозволить скоротити час звернення до сервісу вдвічі.
  • Підтримка фільтрації, сортування та посторінкового виведення - дуже бажано, щоб це було стандартно і працювало «з коробки».
  • Підтримка MediaType. MediaType - спосіб сказати сервера, в якому форматі ви хочете отримати вміст. Якщо ви візьмете будь-яку стандартну реалізацію web API і зайдете туди з браузера, API віддасть вам XML, а якщо зайдете через який-небудь Postman, він поверне JSON.
  • Prettyprint & gzip. Чи не мінімізуйте запити і не робіть компакт для JSON (того відповіді, який прийде від сервера). Накладні витрати на prettyprint -одиниці відсотків, що видно, якщо подивитися, скільки займають таби по відношенню до загального обсягу повідомлення. Якщо ви приберете таби і будете надсилати всі в один рядок, запарити з налагодженням. Що стосується gzip, він дає виграш в рази. Т. ч. Дуже раджу використовувати і prettyprint, і gzip.
  • Використовуйте тільки стандартний механізм кешування (ETag) і Last-Modified (дата останнього зміни) - цих двох параметрів серверу достатньо, щоб клієнт зрозумів, що вміст не вимагає оновлення. Придумувати щось своє тут не має сенсу.
  • Завжди використовуйте стандартні коди помилок HTTP. Інакше вам одного разу доведеться кому-небудь пояснювати, чому ви вирішили, що помилку 419 в вашому проекті клієнту потрібно трактувати саме так, як ви чомусь придумали. Це незручно і некрасиво - за це клієнт вам спасибі не скаже!

Властивості HTTP-методів

Властивості HTTP-методів

Сьогодні ми будемо говорити тільки про GET, POST, PUT, DELETE.

Якщо говорити коротко про інших, представлених в таблиці, OPTIONS - отримання налаштувань безпеки, HEAD - отримання заголовків без тіла повідомлення, PATCH - часткову зміну вмісту.

Як ви бачите, всі методи, крім POST, представлені в таблиці, ідемпотентна. Ідемпотентність - можливість виконати одну і ту ж звернення до сервісу кілька разів, при цьому відповідь кожен раз буде однаковим. Іншими словами, не важливо, з якої причини і скільки разів ви виконали цю дію. Припустимо, ви виконували дію зі зміни об'єкта (PUT), і вам прийшла помилка. Ви не знаєте, що її викликало і в який момент, ви не знаєте, змінився об'єкт чи ні. Але, завдяки ідемпотентності, ви гарантовано можете виконати цієї дію ще раз, т. Ч. Клієнти можуть бути спокійні за цілісність своїх даних.

"Safe" ж означає, що звернення до сервера не змінює вміст. Так, GET може бути викликаний багато разів, але він не змінить ніякого вмісту. Якби він зраджував вміст, в силу того, що GET може бути закешовану, вам довелося б боротися з кешуванням, винаходити якісь хитрі параметри.

Частина 2. Практика

вибираємо технологію

Тепер, коли ми зрозуміли, як працює REST, можемо приступити до написання RESTful API ¬ сервісу, що відповідає принципам REST. Почнемо з вибору технології.

Перший варіант - WCF Services. Всі, хто працював з цією технологією, зазвичай повертатися до неї більше не хочуть - у неї є серйозні недоліки і мало плюсів:
- webHttpBinding only (а навіщо тоді решта? ..).
- Підтримуються тільки HTTP Get & POST (і все).
+ Різні формати XML, JSON, ATOM.

Другий варіант - Web API. В цьому випадку плюси очевидні:
+ Дуже простий.
+ Відкритий вихідний код.
+ Усі можливості HTTP.
+ Усі можливості MVC.
+ Легкий.
+ Теж підтримує купу форматів.

Природно, ми вибираємо Web API. Тепер виберемо підходящий хостинг для Web API.

Вибираємо хостинг для Web API

Тут є досить варіантів:

  • ASP.NET MVC (старий добрий).
  • Azure (хмарна структура).
  • OWIN - Open Web Interface for .NET (свіжа розробка від Microsoft).
  • IIS
  • Self-hosted

OWIN - НЕ платформа і не бібліотека, а специфікація, яка усуває сильну зв'язаність веб-додатки з реалізацією сервера. Вона дозволяє запускати додатки на будь-якій платформі, що підтримує OWIN, без змін. Насправді, специфікація дуже проста - це просто «словник» з параметрів і їх значень. Базові параметри визначені в специфікації.

OWIN зводиться до дуже простої конструкції:

OWIN зводиться до дуже простої конструкції:

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

Katana - реалізація OWIN від Microsoft. Вона дозволяє розміщувати OWIN-збірки в IIS. Ось так вона виглядає, дуже просто:

[Assembly: OwinStartup (typeof (Startup))] namespace RestApiDemo {public class Startup {public void Configuration (IAppBuilder app) {var config = new HttpConfiguration (); config.MapHttpAttributeRoutes (); app.UseWebApi (config); }}}

Ви вказуєте, який клас є у вас Startup. Це простий dll, який піднімається IIS. Викликається конфигуратор. Цього коду достатньо, щоб все запрацювало.

проектуємо інтерфейс

Тепер спроектуємо інтерфейс і подивимося, як все повинно виглядати і якими правилами відповідати. Усі матеріали в REST - іменники, то, що можна помацати і помацати.

Як приклад візьмемо просту модель з розкладом руху поїздів на станціях. Ось приклади найпростіших запитів REST:

  • Кореневі (незалежні) сутності API:
    • GET / stations - отримати всі вокзали.
    • GET / stations / 123 - отримати інформацію по вокзалу з ID = 123.
    • GET / trains - розклад всіх потягів.
  • Зовсім (від кореневої) сутності:
    • GET / stations / 555 / departures - поїзди, що йдуть з вокзалу 555.

Далі я ще розповім про DDD, чому ми робимо саме так.

контролер

Отже, у нас є станції, і тепер нам потрібно написати найпростіший контролер:

[RoutePrefix ( "stations")] public class RailwayStationsController: ApiController {[HttpGet] [Route] public IEnumerable <RailwayStationModel> GetAll () {return testData; } RailwayStationModel [] testData =}

Це роутинг, побудований на атрибутах. Тут ми вказуємо ім'я контролера і просимо віддати список (в даному випадку - випадкові тестові дані).

OData (www.odata.org)

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

[RoutePrefix ( "stations")] public class RailwayStationsController: ApiController {[HttpGet] [Route] [EnableQuery] public IQueryable <RailwayStationModel> GetAll () {return testData.AsQueryable (); } RailwayStationModel [] testData =}

IQueryable дозволяє вам використовувати кілька простих, але ефективних механізмів фільтрації та управління даними на стороні клієнта. Єдине, що потрібно зробити, - підключити OData-збірку з NuGet, вказати EnableQuery і повертати інтерфейс iQueryable.

Основна відмінність такої полегшеної верси від повноцінної в тому, що тут немає контролера, який повертає метадані. Повноцінна OData трохи змінює відповідь (загортає в спец. Обгортку модель, яку ви збираєтеся повертати) і вміє повертати пов'язане дерево об'єктів, які ви хочете їй віддати. Також полегшена версія OData не вміє робити штуки на кшталт join, count та т. Д.

параметри запитів

А ось що можна робити:

  • $ Filter - фільтр, на ім'я, наприклад. Всі функції можна подивитися на сайті OData - вони дуже допомагають і дозволяють істотно обмежити вибірку.
  • $ Select - дуже важлива штука. Якщо у вас велика колекція і всі об'єкти товсті, але при цьому вам потрібно сформувати якийсь dropdown, в якому немає нічого, крім ID і імені, яке ви хочете відобразити, - допоможе ця функція, яка спростить і прискорить взаємодію з сервером.
  • $ Orderby - сортування.
  • $ Top і $ skip - обмеження по вибірках.

Цього достатньо, щоб самому не винаходити велосипеда. Все це вміє стандартна JS-бібліотека на кшталт Breeze.

EnableQuery Attribute

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

Саме для таких випадків у атрибута EnableQuery (див. Код вище) є такий набір параметрів, за допомогою яких дуже багато можна обмежити: не давати більше рядків, ніж треба, не давати робити join, арифметичні операції і т. Д. При цьому писати самому нічого не треба.

  • AllowedArithmeticOperators
  • AllowedFunctions
  • AllowedLogicalOperators
  • AllowedOrderByProperties
  • AllowedQueryOptions
  • EnableConstantParameterization
  • EnsureStableOrdering
  • HandleNullPropagation
  • MaxAnyAllExpressionDepth
  • MaxExpansionDepth
  • MaxNodeCount
  • MaxOrderByNodeCount
  • MaxSkip
  • MaxTop
  • PageSize

залежний контролер

Отже, ось приклади найпростіших запитів REST:

  • GET / stations - отримати всі вокзали
  • GET / trains - розклад всіх потягів
  • GET / stations / 555 / arrivals
  • GET / stations / 555 / departures

Припустимо, у нас є вокзал 555, і ми хочемо отримати все його відправлення і прибуття. Очевидно, що тут повинна використовуватися сутність, яка залежить від сутності вокзалу. Але як це зробити в контролерах? Якщо ми все це буде робити роутинг-атрибутами і складати в один клас, зрозуміло, що в такому прикладі, як у нас, проблем немає. Але якщо у вас буде десяток вкладених сутностей і глибина буде рости ще далі, все це виросте в підтримуваний формат.

І тут є просте рішення - в роутинг-атрибутах в контролерах можна робити змінні:

[RoutePrefix ( "stations / {station} / departures")] public class TrainsFromController: TrainsController {[HttpGet] [Route] [EnableQuery] public IQueryable <TrainTripModel> GetAll (int station) {return GetAllTrips (). Where (x => x.OriginRailwayStationId == station); }}

Відповідно, всі залежні сутності виносьте в окремий контролер. Скільки їх - зовсім неважливо, так як вони живуть окремо. З точки зору Web API, вони будуть сприйматися різними контролерами - сама система як би не знає, що вони залежні, незважаючи на те, що в URL вони виглядають такими.

Єдине, виникає проблема - тут у нас "stations", і до цього був "stations". Якщо ви в одному місці щось поміняєте, а в іншому - нічого не поміняєте, нічого працювати не буде. Однак тут є просте рішення - використання констант для роутінга:

public static class TrainsFromControllerRoutes {public const string BasePrefix = RailwayStationsControllerRoutes.BasePrefix + "/ {station: int} / departures"; public const string GetById = "{id: int}"; }

Тоді залежний контролер буде виглядати так:

[RoutePrefix (TrainsFromControllerRoutes.BasePrefix)] public class TrainsFromController: TrainsController {[HttpGet] [Route] [EnableQuery] public IQueryable <TrainTripModel> GetAll (int station) {return GetAll (). Where (x => x.OriginRailwayStationId == station ); }}

Ви можете робити для залежних контролерів найпростіші операції - ви просто берете і обчислюєте роут самі, і тоді ви не помилитеся. Крім того, ці штуки корисно використовувати в тестуванні. Якщо ви хочете написати тест і потім хочете цим керувати, а не бігати кожен раз по всім мільйонам ваших тестів і виправляти всі рядки, де вказані ці відносні URL'и, то ви також можете використовувати ці константи. Коли ви їх міняєте, дані у вас міняються скрізь. Це дуже зручно.

CRUD

Отже, ми з вами обговорили, як можуть виглядати найпростіші GET-операції. Всі розуміють, як зробити одиничний GET. Але, крім нього, нам потрібно обговорити ще три операції.

  • POST - створити нову сутність
    • POST / Stations - JSON-опис суті цілком. Дія додає нову сутність в колекцію.
    • Повертає створену сутність (по-перше, щоб не було подвійних походів до сервера, по-друге, щоб, якщо це потрібно, повернути з боку сервера параметри, які зважили в цьому об'єкті і потрібні вам на клієнті).
  • PUT - змінити сутність
    • PUT / Stations / 12 - Змінити сутність з ID = 12. JSON, який прийде в параметрі, буде записаний поверх.
    • Повертає змінену сутність. Шлях, який був застосований багато разів, повинен приводити систему до одного і того ж стану.
  • DELETE
    • DELETE / Stations / 12 - видалити сутність з ID = 12.
  • Ще приклади CRUD:

    • POST / Stations - додаємо вокзал.
    • POST / Stations / 1 / Departures - додаємо інформацію про відправлення з вокзалу 1.
    • DELETE / Stations / 1 / Departures / 14 - видаляємо запис про відправлення з вокзалу 1.
    • GET / Stations / 33 / Departures / 10 / Tickets - список проданих квитків для відправлення 10 з вокзалу 33.

    Важливо розуміти, що вузли - обов'язково якісь сутності, то, що можна «помацати» (квиток, поїзд, факт відправлення поїзда і т. Д.).

    Антішаблони

    А ось приклади, як робити не треба:

    • GET / Stations /? Op = departure & train = 11
      Тут query string вікорістовується НЕ только для передачі Даних, но и для Дій.
    • GET / Stations / DeleteAll
      Це реальний приклад з життя. Тут ми робимо GET на Цю адресою, и ВІН, по Ідеї, винен ВИДАЛИТИ всі сутності з Колекції - в результате ВІН поводиться дуже непередбачувано через кешування.
    • POST / GetUserActivity
      Насправді тут GET, Який Записаний як POST. POST потрібен БУВ через параметрів запиту в body, но в body у GET нельзя Нічого Передат - GET можна Передат только в query string. GET даже за стандартом не підтрімує body.
    • POST / Stations / Create
      Тут дія зазначилися в складі URL - це надмірно.

    проектуємо API

    Припустимо, у вас є API, який ви хочете запропонувати людям, і є доменна модель. Як пов'язані суті API з доменної моделлю? Та ніяк вони не пов'язані, насправді. У цьому немає ніякої необхідності: то, що ви робите в API, ніяк не пов'язане з вашої внутрішньої доменної моделлю.

    Може виникнути питання, як проектувати API, якщо це не CRUD? Для цього ми записуємо будь-які дії як команди на зміни. Ми робимо збереження, читання, видалення команди, GET, перевірку статусу цієї команди. GET з колекції команд - ви отримуєте список всіх команд, які ви відправляли для будь-якої конкретної сутності.

    Доменна модель

    Ми поговоримо про зв'язок доменної моделі з об'єктами. У прикладі у нас є готель (Hotel), є бронювання (Reservation), кімнати (Room) і пристрої (Device), до них прив'язані. У нашому проекті це дозволяло керувати кімнатами за допомогою цих пристроїв.

    Але от невдача: пристрій - сутність, яка живе своїм життям, і незрозуміло, як її відокремлювати від готелю. Але розділити готель і пристрої, насправді, дуже просто - допоможе DDD. В першу чергу, потрібно розібратися, де кордону доменних областей і де межі сутностей, відповідальних за узгодженість системи.

    Bounded context (BC)

    Bounded context (ізольований піддомен) - фактично, набори об'єктів, що не залежні один від одного і мають абсолютно незалежні моделі (різні). У прикладі ми можемо взяти і розтягнути готелі і пристрої на два різних BC - вони не пов'язані між собою, але є дублювання. Виникає додаткова сутність (AttachedDevice):

    Тут у нас різні уявлення одного і того ж пристрою, і в цьому немає нічого страшного.

    В DDD aggregate route - сутність, яка володіє всіма нащадками. Це вершина нашого дерева (Hotel); то, за що можна витягнути все інше. А AttachedDevice так взяти не можна - його не існує, і він не має ніякого сенсу. Так само і класи Room і Reservation не мають ніякого сенсу, будучи відірваними від Hotel. Тому доступ до всіх цих класів - виключно через рутовий сутність, через Hotel, в даному випадку. Device ж - інший route з самого початку, інше дерево з іншим набором полів.

    Отже, якщо ви розумієте, що одна сутність у вас грає в двох різних доменах, просто розпиляти її - і це буде всього лише проекція майстер-суті. У AttachedDevice будуть, наприклад, поля з номером кімнати, а в Device такі поля не потрібні.

    А ось приклади запитів, як вони можуть виглядати в такий доменної моделі:

    • PUT / hotels / 555 / rooms / 105 / attachedDevices - замінити всю колекцію прив'язаних пристроїв на нову.
    • POST / hotels / 555 / rooms / 105 / attachedDevices - прив'язати ще один пристрій.
    • DELETE / hotels / 12 - видалити опис готелю з ID = 12.
    • POST / hotels / 123 / reservations - створити нову резервацію в готелі ID = 123.

    CQRS - Command Query Responsibility Segregation

    Я не буду зараз розповідати про це архітектуру, але хочу коротко окреслити, в чому її принцип дії. Архітектура CQRS заснована на поділі потоків даних.

    У нас є один потік, через який користувач відправляє на сервер команду про зміну домену. Однак не факт, що зміна дійсно станеться, - користувач не оперує даними безпосередньо. Отже, після того як користувач посилає команду на зміну суті, сервер її обробляє і перекладає в якусь модель, яка оптимізована на читання - UI зчитує це.

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

    REST without PUT

    У простому CRUD-світі PUT - це штука, яка змінює об'єкти. Але якщо ми строго дотримуємося принципу CQRS і робимо все через команди, PUT у нас пропадає, т. К. Ми не можемо змінювати об'єкти. Замість цього можемо лише послати об'єкту команду на зміну. При цьому можна відстежувати статус виконання, скасовувати команди (DELETE), легко зберігати історію змін, а користувач нічого не змінює, а просто повідомляє про наміри.

    Парадигма REST without PUT - поки що спірна і не до кінця перевірена, але для якихось випадків дійсно добре застосовна.

    Fine-grained VS coarse-grained

    Уявіть, що ви робите великий сервіс, великий об'єкт. Тут у вас є два підходи: fine-grained API і coarse-grained API ( «дрібнозернистий» і «крупнозернистий» API).

    Fine-grained API:

    • Багато маленьких об'єктів.
    • Бізнес-логіка йде на сторону клієнта.
    • Потрібно знати, як пов'язані об'єкти.

    Сoarse-grained API:

    • Створюєте більше сутностей.
    • Складно робити локальні зміни, наприклад
    • Потрібно відстежувати стан на клієнті.
    • Великі об'єкти не можна зберегти частково.

    Для початку раджу проектувати fine-grained API: кожен раз, коли ви створюєте об'єкт, відправляєте його на сервер. На кожну дію на стороні клієнта відбувається звернення до сервера. Однак з маленькими сутностями працювати простіше, ніж з великими: якщо ви напишете велику сутність, вам важко буде потім її розпиляти, важко буде робити невеликі зміни і висмикувати з неї незалежні шматки. Т. ч. Краще починати з маленьких сутностей і поступово їх укрупнювати.

    нумерація версій

    Так уже склалося, що до контрактів у нас в галузі дуже розслаблений відношення. Чомусь люди вважають, що, якщо вони взяли і зробили API, це їх API, з яким вони можуть робити що завгодно. Альо це не так. Якщо ви коли-то написали API і віддали його хоч одному контрагенту, все - це версія 1.0. Будь-які зміни тепер повинні призводити до зміни версії. Адже люди будуть прив'язувати свій код до тієї версії, яку ви їм надали.

    На минулому проекті доводилося кілька разів відкочувати API назад просто тому, що він був відданий клієнту - ми поміняли коди помилок, але клієнт вже встиг звикнути до старих кодами.

    Які відомі на поточний момент варіанти нумерації версій Web API?

    Які відомі на поточний момент варіанти нумерації версій Web API

    Найпростіше - вказати версію в URL.

    Ось готові варіанти, коли самому нічого робити не треба:

    бібліотека Climax.Web.Http

    Ось один цікавий готовий варіант.

    Це всього лише роутинг атрибутів з constraint - якщо ви робили будь-які серйозні об'єкти, напевно робили constraint. За номером версії в цьому атрибуті хлопці просто реалізували constraint. Відповідно, на один і той же атрибут з різними версіями, але однаковим ім'ям контролера вішаєте на два різних класи і вказуєте різні версії. Все працює «з коробки ....

[VersionedRoute ( "v2 / values", Version = 2)]

config.ConfigureVersioning (
versioningHeaderName: "version", vesioningMediaTypes: null);

config.ConfigureVersioning (
versioningHeaderName: null,
vesioningMediaTypes: new [] { "application / vnd.model"});

документація

Є чудова open-source-штука, що має безліч різних застосувань - Swagger. Ми її використовуємо зі спеціальним адаптером - Swashbuckle.

Swashbuckle:

httpConfiguration
.EnableSwagger (c => c.SingleApiVersion ( «v1», "Demo API")) .EnableSwaggerUi ();
public static void RegisterSwagger (this HttpConfiguration config)
{
config.EnableSwagger (c =>
{
c.SingleApiVersion ( «v1», «DotNextRZD.PublicAPI»)
.Description ( «DotNextRZD Public API»)
.TermsOfService ( «Terms and conditions»)
.Contact (cc => cc
.Name ( «Vyacheslav Mikhaylov»)
.Url ( « www.dotnextrzd.com »)
.Email ( «[email protected]»))
.License (lc => lc.Name ( «License»). Url ( « tempuri.org/license »));
c.IncludeXmlComments (GetXmlCommentFile ()); c.GroupActionsBy (GetControllerGroupingKey); c.OrderActionGroupsBy (new CustomActionNameComparer ()); c.CustomProvider (p => new CustomSwaggerProvider (config, p)); }) .EnableSwaggerUi (c => {c.InjectStylesheet (Assembly.GetExecutingAssembly (), "DotNextRZD.PublicApi.Swagger.Styles.SwaggerCustom.css");}); }}

Як бачите, Swagger витягнув все, що у нас є, витягнув XML-коментарі.

Як бачите, Swagger витягнув все, що у нас є, витягнув XML-коментарі

Нижче - повний опис моделі GET. Якщо натиснути на кнопку, він її справді виконає і поверне результат.

Якщо натиснути на кнопку, він її справді виконає і поверне результат

А ось так виглядає документація до POST, перша частина:

А ось так виглядає документація до POST, перша частина:

Ось друга частина:

Ось друга частина:

Все, що було написано в XML-коментарі, - тут.

джерела

Які принципи REST?
Чим REST хороший?
А навіщо тоді решта?
Але як це зробити в контролерах?
Як пов'язані суті API з доменної моделлю?
Може виникнути питання, як проектувати API, якщо це не CRUD?
Які відомі на поточний момент варіанти нумерації версій Web API?

Дополнительная информация

rss
Карта