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


Автор: В'ячеслав Михайлов, Solutions Architect.

У цій статті я поділюся досвідом проектування 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
○     Java Script
●     Class-based APIs
○     C# API
○     Java
 
Види API за категоріями застосування:
●     OS function and routines
○     Access to file system
○     Access to user interface
●     Object remoting APIs
○     CORBA
○     .Net remoting
●     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 методів


Сьогодні ми будемо говорити тільки про 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, можете вільно, без змін коду, переносити його між платформами і використовувати те ж саме на інших ОС.

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 = /*initialization here*/
}


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

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 = /*initialization here*/
}


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/прибуття
  • 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);
}
}


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

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:
  • Багато маленьких об'єктів.
  • Бізнес-логіка йде на сторону клієнта.
  • Потрібно знати, як пов'язані об'єкти.


Соагѕе-grained API:
●     Створюєте більше сутностей.
●     Складно робити локальні зміни, наприклад
○     POST /blogs/{id}/likes.
●     Потрібно відстежувати стан клієнта.
●     Великі об'єкти можна зберегти частково.

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

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

На минулому проекті доводилося кілька разів відкочувати 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(«vmikhaylov@dataart.com»))
.License(lc => lc.Name(«License»).Url(«tempuri.org/license»));
c.IncludeXmlComme
nts(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-коментарі.



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



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



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



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

Джерела




Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.