Введение
В рамках публикации рассмотрим вопросы создания HTTP-сервиса согласно архитектурным принципам разработки Web API. Поведение сервиса должно быть детерминировано, прозрачно и предсказуемо. Для достижения этих целей используем архитектурный стиль REST.
Что такое API?
Есть хороший пример идеального API не из мира IT и это ресторан. Если вы идете в ресторан как гость, то вам запрещено проходить на кухню. В этом случае вы должны знать, что вы можете заказать. Для этого у вас есть меню. После изучения меню, вы делаете заказ официанту, который передает заказ на кухню, и он же принесет вам то, что вы заказали. Официант может принести только то, что может дать ему кухня. Как же это относится к API? Официант это API ресторана. Вы пользователь API. Меню это документация, которая объясняет, что вы можете запросить у API. Блюдо в вашем заказе это запрос. Кухня это сервер, которая знает, как готовить то или иное блюдо.
Стоит заметить, что API это не только про Web. Например, все экспортные методы общего модуля или модуля менеджера документа - это тоже API.
Термины URI, URL, URN
- URI - Uniform Resource Identifier (унифицированный идентификатор ресурса). Обозначает имя и адрес ресурса в сети. Как правило, делится на URL и URN.
- URL - Uniform Resource Locator (унифицированный определитель местонахождения ресурса). Адрес некоторого ресурса в веб. URL определяет местонахождение ресурса и способ обращения к нему.
- URN - Unifrorm Resource Name (унифицированное имя ресурса). Хороший пример это ISBN у книг (например, ISBN 978-5-699-12014-7 однозначно идентифицирует книгу, но ничего не говорит о ее местоположении). Смысл URN в том, что он определяет только название конкретного предмета, который может находится во множестве конкретных мест.
Можно сказать, что
URI = URL + URN
Приведу простой пример как это может выглядит на практике:
- URI - https://mydomain.com/products.html
- URL - https://mydomain.com
- URN - /products.html
Еще пример структуры URI в виде картинки:
Протокол HTTP
HTTP - текстовый протокол передачи данных, который работает по схеме запрос-ответ (клиент-сервер). На данный момент является основным протоколом в Интернете. Поверх него часто делают прикладные протоколы (SOAP, WebDAV). Актуальная версия 1.1.
HTTP-запрос
Используется для отправки клиентом серверу запроса на получение информации. Формат запроса - текстовый.
Общий вид запроса
Метод Путь HTTP/Версия протокола
Host: Адрес
Другие заголовки (опционально)
(пустая строка)
Тело запроса (опционально)
Минимальный GET запрос
GET /wiki/HTTP HTTP/1.1
Host: ru.wikipedia.org
HTTP-ответ
Используется для отправки сервером клиенту запрошенных данных. Формат ответа - текстовый.
Общий вид ответа
HTTP/Версия протокола Код статуса/Текст статуса
Заголовки
(пустая строка)
Тело ответа (опционально)
Пример ответа сервера
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8 Content-Length: 512
(пустая строка)
(тело ответа)
Методы запросов
Для каждого запроса обязательным является указание метода. Рассмотрим самые популярные методы в контексте CRUD операций. Если говорить о SQL, то вы не сможете оператором select удалить данные, а оператором update вставить новые. Что касается методов HTTP-запросов, то технически такого ограничения нет. Если вы разрабатываете веб-сервис, то можете сделать так, что любым методом можно получить данные или удалить. Но так делать крайне не рекомендуется, потому что это будет неожиданное поведение сервиса, да и в принципе для этих операций есть свои методы.
Распространенные методы
- GET - получение содержимого указанного ресурса (Retrieve в контексте CRUD).
- POST - передача данных или для операции Create в контексте CRUD.
- PUT - изменение данных ресурса целиком (операция Update в контексте CRUD).
- DELETE - удаление указанного ресурса (Delete в контексте CRUD).
- PATCH - частичное изменение ресурса (применяется не часто, обычно используется PUT)
- HEAD - практически тоже самое, что GET, но без тела ответа. Можно использовать как валидацию URL через кастомный заголовок в ответе.
Если веб сервис разрабатывается для работы с формами HTML, то можно использовать только методы GET, POST. Но для поддержки остальных достаточно использовать JS. Если говорить не про формы, то в любом современном ЯП есть поддержка остальных методов "из коробки".
Идемпотентность
Выдыхайте, это про методы запросов. Запрос является идемпотентным, если повторный идентичный запрос, сделанный с его помощью, гарантирует одинаковое воздействие на систему, при этом возвращаемые ответами коды статусов могут быть различными. Другими словами, идемпотентный метод не должен иметь никаких побочных эффектов (side-effects).
Идемпотентные методы
- GET, HEAD - независимо от того, сколько будет раз запрошен ресурс, состояние системы никак не меняется.
- PUT, PATH - состояние ресурса при идентичных запросах будет изменено только в первый раз (ресурс с id=0 имел имя name0, после первого изменения имя стало name1, но и при последующих запросах оно вновь будет name1).
- DELETE - удалить ресурс с помощью идентичных запросов можно также только один раз.
Важно понимать, что ответ сервера при идемпотентных запросах может быть различным. Например, при попытке удалить ресурс, ответ на второй и последующие запросы может быть с информацией о том, что такого ресурса в принципе нет. При этом состояние системы никак не меняется.
Не идемпотентные методы
- POST - независимо от того, сколько раз мы передадим данные, они будут добавлены (не берем в расчет контроль уникальности реквизитов), то есть состояние системы меняется при каждом запросе.
Почему идемпотентность важна? Это соглашение. То есть это то, на что пользователи будут рассчитывать при работе с вашим сервером.
Версионирование
На практике приходилось несколько раз сталкиваться с ситуацией, когда разработчик API менял его, считая что он монополист и может делать с ним что угодно. По факту, даже если у вас есть хотя бы один пользователь, то необходимо использовать версионирование, так как клиентский код опирается на ваши методы. Сделать его несложно. Мне известно два способа:
- URL - {host}/v1/products
- Custom header - api-version:1
Best practices
Поделюсь списком лучших практик, которые использую в своей работе:
- Никакой кириллицы при именовании ресурсов. Иначе адрес придется кодировать и при интеграции со сторонними системами никто вам спасибо за такое не скажет.
- В модуле http-сервиса только логика работы с объектом HTTPRequest. Вся бизнес-логика должна быть вынесена в общий модуль. Так вы получите независимые слои приложения (с натяжкой сюда можно прикрутить термин MVC) и простоту тестирования бизнес-логики.
- Методы POST и PUT должны отдавать соответственно созданный и измененный объекты обратно. Этим вы избавите клиента от повторного вызова вашего сервиса.
- Поддержка обработки заголовка Content-Type. Например, если заголовок не указан - сервис отдает ответ в формате json, а если указан application/xml - то ответ в xml.
- Стандартные коды статусов ответов. Например, код 415 используйте тогда, когда клиент запрашивает ответ в формате xml, а вы его не поддерживаете. В противном случае поведение придется описывать в документации.
- Pretty print для сериализованных данных. Экономия на символах табуляции дает не так много, зато убивает читаемость ответа при отладке.
- Если на сервере опубликован не только ваш веб-сервис, а допустим веб-клиент, то необходимо разделение таких ресурсов через URI. Обычно, сервис начинается с /api/.... В нашем случае платформа за нас добавляет /hs. Так что этот пункт не обязателен, но помнить про него нужно.
REST
REST (Representational State Transfer — «передача состояния представления»). Не является протоколом, но описывает набор архитектурных принципов формирования API.
Основные идеи REST
- Клиент-серверная архитектура.
- Все есть ресурс.
- Любой ресурс имеет ID (путь из URI).
- Сервер не хранит никакого состояния, то есть все обработчики stateless. Вся информация, которая необходима для обработки запроса, передается клиентом вместе с запросом.
- Список доступных действий определяется стандартными методами HTTP.
В связи с недавней популярностью этой аббревиатуры, многие ее смысл понимали как что угодно, лишь бы это было связано с формированием Web API. На самом деле, это правила формирования URI. В качестве примера можно привести структуру папок на диске и доступные операции с ними. Допустим есть папка D:/files и есть файл Dummy.data. Если необходимо получить содержимое файла, то используется D:/files/dummy.data. Если нужно изменить файл, то опять же D:/files/dummy.data. Если нужны все файлы из это папки, то D:/files. При любой операции с файлами остается неизменным путь к папке. Именно такой подход используется в REST.
Но такой подход далеко не всегда удобен. Например, если вы пишете сервис для обмена с другой ИС на платформе 1С и в обоих случаях используются планы обмена, то REST здесь вряд ли подойдет, потому что клиент в теле запроса присылает целую пачку данных и скорей всего ваш сервис будет состоять из 1-2 методов. Ранее я освещал эту тему - Обмен без правил.
Примеры конкретных URI
Рассмотрим конкретный пример сервиса для работы с товарами.
Действие | Метод | URI |
---|---|---|
Получить список всех товаров | GET | /products |
Получить список всех товаров марки NoName и классом продукта A | GET | /products?brand=NoName&class=A |
Получить определенный товар (id=0) | GET | /products/0 |
Добавить товар | POST | /products |
Изменить товар | PUT |
/products/0 |
Удалить товар | DELETE | /products/0 |
Примеры неправильных URI
- /products/getAll
- /products/brand/NoName
- /products/?action=put&id=0
- /products/delete/?id=0