Зачем нужен этот ваш REST, а также о некоторых тонкостях реализации RESTful приложений. Лучшие практики проектирования REST API Что такое rest api

Некоторые скажут:

«Это API , который использует HTTP-запросы для

GET, PUT, POST и DELETE ".

Многие также скажут:

«Речь идет об определении ресурсов с помощью URI»

Другие будут кричать:

В заключение можно сказать:

«В принципе, это просто API , который использует HTTP правильно!»

Некоторые из этих утверждений ошибочны,

Некоторые частично верны,

но это не имеет значения,

Потому что они все упускают суть .

Чтобы иметь возможность разрабатывать API RESTful,

Во-первых,

Что такое REST?

Что такое REST?

REST это:

    Определенно не HTTP

    Не протокол

    Не спецификация

Вот почему вокруг REST APIs так много споров.

И всё-таки...

REST это стиль архитектуры.

О, хорошо… но что такое стиль архитектуры?

Стиль архитектуры

Желаемая архитектура

Это просто архитектура, плюс набор ограничений, применяемых к архитектуре, создающей желаемую архитектуру.

Применяя эти ограничения, мы получаем желаемую архитектуру, оптимизированную для общих случаев использования.

REST выступает за представление состояния передачи

Это было записано Роем Филдингом в его докторской диссертации в 2000 году, где он описал существующую и современную веб-архитектуру как абстракцию.

Название было призвано вызывать представление о том, как ведет себя хорошо разработанное веб-приложение.

Рой описал REST на простом примере:

Рассмотрим сеть веб-страниц как виртуальную машину состояний.

Каждая страница представляет состояние:

1. Во-первых, пользователь получает первое состояние в виде индексного состояния.

2. Затем пользователь переходит через приложение, выбирая ссылку (здесь ссылка на страницу)

3. Результат передачи следующего состояния пользователю.

REST все еще не HTTP

Конечно, еще в 2000 году сеть уже работала на HTTP, и Рой со своими коллегами-инженерами много работали над этим.

Однако REST не определяет конкретные детали реализации системы и не определяет какой-либо синтаксис протокола.

Вполне возможно иметь архитектуру RESTful поверх протоколов, отличных от HTTP.

Например, CoAP (протокол ограниченного применения) является протоколом RESTful для встроенных устройств (Internet of Things) и предназначен для использования минимальных ресурсов как на устройстве, так и в сети.

Так зачем использовать REST?

Всемирная паутина основана на архитектуре REST.

Поэтому, если вы создаете API-интерфейс non-RESTful, который будет использоваться в Интернете, то вы получите неоптимальную систему.Не оптимальный в отношении оптимизированной архитектуры.

Это важно отметить, поскольку не-RESTful API может быть неоптимальным в сетевой архитектуре, но оптимальным для других проблем. Например, современные интерфейсные приложения могут иметь очень специфические потребности, следовательно, растет число библиотек сбора данных, таких как GraphQL или Falcor.

Итак, когда это API RESTful?

API является RESTful, когда он постоянно действует под ограничениями REST.

REST определяет 6 ограничений для достижения желаемой оптимизации системы:

1. Клиент-сервер

Это ограничение основано на принципе разделения интересов.

Это позволяет компонентам развиваться независимо. Создавая наш API, он действует как сервер, обслуживающий большое количество клиентов.

2. Без гражданства

Связь между клиентом и сервером должна быть без гражданства. Это означает, что каждый запрос от клиента к серверу должен содержать всю необходимую информацию для завершения транзакции.

Основным преимуществом этого ограничения является то, что система способна масштабироваться лучше, потому что серверу не нужно сохранять состояние клиента между запросами. Отсутствие необходимости запоминать информацию о состоянии клиента освобождает ресурсы сервера, поэтому он может обслуживать больше клиентов одновременно.

Наиболее эффективным сетевым запросом является тот, который не использует сеть.

Когда мы создаем наш API, он не должен игнорировать кеширование.

4. Правильный интерфейс

Чтобы иметь эффективное кэширование в сети, компоненты должны иметь возможность взаимодействовать через единый интерфейс. С единым интерфейсом полезная нагрузка может передаваться в стандартной форме.

4.1. Идентификация ресурсов

Это означает, что любая информация, которая может быть названа, может быть ресурсом (изображение, документ или даже набор других ресурсов)

4.2. Манипулирование ресурсами через представления

Ресурс может быть представлен различными способами.

Например, HTML, XML, JSON или даже JPEG-файл.

Это правило означает, что клиенты взаимодействуют с ресурсами через свои представления, что является мощным средством удержания абстрактных понятий ресурсов от их взаимодействий.

4.3 В-третьих, самоописательные сообщения

Это означает, что ресурс может быть описан в сообщении запроса, а сервер может отвечать описательными сообщениями о состоянии. Да, HTTP-заголовки и коды ответов являются хорошими реализациями для этого правила.

4.4. Hypermedia должна быть двигателем состояния приложения

Это на самом деле означает, что приложение должно управляться ссылками, позволяя клиентам обнаруживать ресурсы через гиперссылки.

Как вы можете видеть, многие из этих правил могут быть реализованы в протоколе HTTP. Поэтому, когда API использует HTTP правильно, это огромный шаг к тому, чтобы стать RESTful.

5. Многоуровневая система

В многоуровневой системе посредники, такие как прокси-серверы, могут размещаться между клиентом и сервером, используя единообразный интерфейс сети.

Одним из преимуществ многоуровневой системы является то, что посредники могут затем перехватывать трафик клиент-сервер для определенных целей/ Например, для кэширования.

6. Код по требованию

Это необязательное ограничение и позволяет клиентам загружать программы для выполнения на стороне клиента. Лучший пример для этого - интерфейсные JavaScript-приложения. Это может показаться нам сейчас очень очевидным, но в раннем возрасте Интернета это была эволюционирующая концепция, и это полезная часть интернет-архитектуры.

Итак, как создать REST API?

Правильно использовать HTTP

Если вы строите RESTful API, используйте протокол RESTful. Для Интернета HTTP-протокол является определенным выбором. Создайте свой API, чтобы он правильно использовал HTTP.

Создайте единый интерфейс

Сопоставьте свои концепции с ресурсами и назначьте соответствующие идентификаторы для каждого из них. Простым примером может служить служба базы данных пользователей. В такой службе мы можем назвать два ресурса; Пользователей и пользователей (ресурс сбора). Эти ресурсы могут быть идентифицированы с URI / users и / user / {id} URI вашего интерфейса API.

Управляйте своим API-гиперссылками

Помните об архитектуре REST

Для меня главное избавление от создания RESTful API заключается в том, насколько важно понимать Интернет и его базовую архитектуру. Мы можем либо воспользоваться этой оптимизацией, либо мы можем игнорировать ее.

Если у вас появились какие-либо вопросы, приглашаем на наши

Спасибо, что прочитали мой пост.
Обратная связь и мысли всегда приветствуются в разделе комментариев.

Многие из вас уже наверняка знают о требованиях Джеффа Безоса к разработчикам в Amazon. Если вы не слышали об этом, ниже перечислены основные его положения:

  1. Отныне все группы разработчиков должны раскрывать свои данные и функциональные средства через сервисные интерфейсы;
  2. Группы разработчиков должны связываться друг с другом с помощью этих интерфейсов;
  3. Неважно, какую технологию используют разработчики: HTTP, Cobra, Pubsub, встроенные протоколы;
  4. Больше никаких форм межпроцессного взаимодействия не допускается: никаких прямых ссылок и считываний данных других групп, никакой модели общей памяти, никаких «лазеек». Единственная разрешенная форма связи осуществляется через служебные интерфейсы по сети;
  5. Все без исключения сервисные интерфейсы с самого начала должны проектироваться выгружаемыми. Это значит, что группа должна планировать интерфейс так, чтобы его можно было раскрыть остальным разработчикам. Исключения не допускаются;
  6. Все, кто не слушаются этих правил, будут уволены.

В итоге эти требования оказались ключом к успеху Amazon. Компания смогла создавать эластичные системы, а позднее могла предложить эти услуги в лице Amazon Web Services.

Принципы разработки RESTful API

Для разработки RESTful API нужно следовать следующим принципам:

Простота

Нужно убедиться в простоте базового URL для API. Например, если нужно разработать запрос для продуктов, должно получаться так:

/products /products/12345

Первый запрос к API - для всех продуктов, второй - для специфического продукта.

Используйте существительные, а не глаголы

Многие разработчики совершают эту ошибку. Обычно они забывают, что у нас есть HTTP методы для лучшего описания API, и в итоге используют глаголы в URL. Например, запрос для получения всех продуктов звучит так:

/products

А не так:

/getAllProducts

Так часто поступают при создании URL.

Правильные HTTP методы

В RESTful API существуют различные методы, которые описывают тип операции, которую будет осуществлять API.

  • GET - для получения ресурса или группы ресурсов;
  • POST - для создания ресурса или группы ресурсов;
  • PUT/PATCH - для обновления уже существующего ресурса или группы ресурсов;
  • DELETE - для удаления уже существующего ресурса или группы ресурсов.

Нужно обязательно убедиться в том, что вы используете верный HTTP метод для каждой операции.

Не забывайте о множественном числе

Эта тема еще стоит под вопросом. Некоторым нравится называть URL ресурсов во множественном числе, некоторым - в единственном. Пример:

/products /product

Мне нравится использовать множественное число, потому что в таком случае не создается путаница: работаем ли мы с одним ресурсом или с группой ресурсов? Также не нужно дополнять базовые URL, например, добавлением all: /product/all .

Кому-то может не нравиться мой подход. Мой единственный совет - делайте так, чтобы в проекте все было единообразно.

Параметры

Иногда нужен API, который должен работать не только по имени. В таком случае для разработки API понадобятся параметры запроса.

  • нужно использовать /products?name="ABC" , а не /getProductsByName ;
  • нужно использовать /products?type="xyz" , а не /getProductsByType .

В таком случае URL не будут слишком длинными, а структура останется простой.

Правильные HTTP коды

Существует множество HTTP кодов. Многие из нас используют только два из них: 200 и 500 ! Это плохая методика. Ниже перечислены часто используемые HTTP коды:

  • 200 OK - самый часто используемый код, свидетельствующий об успехе операции;
  • 201 CREATED - используется, когда с помощью метода POST создается ресурс;
  • 202 ACCEPTED - используется, чтобы сообщить, что ресурс принят на сервер;
  • 400 BAD REQUEST - используется, когда со стороны клиента допущена ошибка в вводе;
  • 401 UNAUTHORIZED / 403 FORBIDDEN - используются, если для выполнения операции требуется аутентификация пользователя или системы;
  • 404 NOT FOUND - используется, если в системе отсутствуют искомые ресурсы;
  • 500 INTERNAL SERVER ERROR - это никогда не используется просто так - в таком случае произошла ошибка в системе;
  • 502 BAD GATEWAY - используется, если сервер получил некорректный ответ от предыдущего сервера.

Версии

Версии API - важная вещь. Различные компании используют версии по-разному: кто-то - как даты, кто-то - как параметры запросов. Мне нравится указывать версии до названия ресурса. Пример:

/v1/products /v2/products

Мне также кажется, что стоит избегать использования /v1.2/products , так как это подразумевает, что API часто меняется. К тому же, точки в URL не так легко заметить. Так что чем проще, тем лучше.

Также хорошо бы сохранять совместимость с предыдущими версиями. В таком случае, если вы поменяете версию API, у пользователей будет время, прежде чем перейти на новую версию.

Разбиение на страницы

Обязательно нужно разбивать запрос на страницы, если вы работаете с API, который в качестве ответа может предоставить огромный объем данных. Если сбалансированность нагрузки не обеспечена, пользователь может обрушить сервер.

Всегда нужно иметь в виду, что структура API должна быть полностью защищена, в том числе от неосторожного обращения.

В таком случае стоит использовать limit и offset . Пример: /products?limit=25&offset=50 . Также стоит установить эти настройки по умолчанию.

Поддерживаемые форматы

Также нужно выбирать то, как будет отвечать API. Большинство современных приложений используют JSON. Приложения старых версий должны пользоваться XML ответами.

Верные сообщения об ошибках

Всегда хорошо, чтобы у приложения был набор сообщений об ошибках, которые используются уместно. Например, в случае ошибки графические API на Facebook выдают такое сообщение:

{ "error": { "message": "(#803) Some of the aliases you requested do not exist: products", "type": "OAuthException", "code": 803, "fbtrace_id": "FOXX2AhLh80" } }

Я также видел некоторые примеры, в которых люди помещают в сообщения ссылку на ошибку, в которой рассказывается о ней и о способах избавления от проблемы.

Open API Specification

Чтобы все группы разработчиков в компании подчинялись одним и тем же принципам, будет полезно использовать Open API Specification. Open API позволяет проектировать API и делиться ей с потребителями в более простой форме.

Заключение

Довольно очевидно, что для лучшей связи отлично подойдут API. Если же они неудачно спроектированы, это вызовет только больше путаницы. Так что выкладывайтесь на полную в разработке, а дальше уже последует этап реализации.

В этой статье я поделюсь опытом проектирования 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
  • WebSockets APIs
  • Library-based APIs
    • Java Script
  • Class-based APIs
    • C# 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).
  • Self-hosted
OWI
OWIN - не платформа и не библиотека, а спецификация, которая устраняет сильную связанность веб-приложения с реализацией сервера. Она позволяет запускать приложения на любой платформе, поддерживающей OWIN, без изменений. На самом деле, спецификация очень проста - это просто «словарь» из параметров и их значений. Базовые параметры определены в спецификации.

OWIN сводится к очень простой конструкции:

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

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

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.
Контроллер

Итак, у нас есть станции, и теперь нам нужно написать простейший контроллер:

Public class RailwayStationsController: ApiController { public IEnumerable GetAll() { return testData; } RailwayStationModel testData = /*initialization here*/ }

Это роутинг, построенный на атрибутах. Здесь мы указываем имя контроллера и просим отдать список (в данном случае - случайные тестовые данные).

OData (www.odata.org)

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

Public class RailwayStationsController: ApiController { public IQueryable 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/arrivals
  • GET /stations/555/departures

Допустим, у нас есть вокзал 555, и мы хотим получить все его отправления и прибытия. Очевидно, что здесь должна использоваться сущность, которая зависит от сущности вокзала. Но как это сделать в контроллерах? Если мы все это будет делать роутинг-атрибутами и складывать в один класс, понятно, что в таком примере, как у нас, проблем нет. Но если у вас будет десяток вложенных сущностей и глубина будет расти еще дальше, все это вырастет в неподдерживаемый формат.

И тут есть простое решение - в роутинг-атрибутах в контроллерах можно делать переменные:

Public class TrainsFromController: TrainsController { public IQueryable GetAll(int station) { return GetAllTrips().Where(x =>

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

Единственное, возникает проблема - здесь у нас “stations”, и до этого был “stations”. Если вы в одном месте что-то поменяете, а в другом - ничего не поменяете, ничего работать не будет. Однако тут есть простое решение - использование констант для роутинга :

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

Тогда зависимый контроллер будет выглядеть так:

Public class TrainsFromController: TrainsController { public IQueryable 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:

  • Создаете больше сущностей.
  • Сложно делать локальные изменения, например
    • POST /blogs/{id}/likes.
  • Нужно отслеживать состояние на клиенте.
  • Большие объекты нельзя сохранить частично.

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

Нумерация версий
Так уж сложилось, что к контрактам у нас в отрасли очень расслабленное отношение. Почему-то люди считают, что, если они взяли и сделали API, это их API, с которым они могут делать что угодно. Но это не так. Если вы когда-то написали API и отдали его хоть одному контрагенту, все - это версия 1.0. Любые изменения теперь должны приводить к изменению версии. Ведь люди будут привязывать свой код к той версии, которую вы им предоставили.

На прошлом проекте приходилось несколько раз откатывать API назад просто потому, что он был отдан клиенту - мы поменяли коды ошибок, но клиент уже успел привыкнуть к старым кодам.

Какие известны на текущий момент варианты нумерации версий Web API?

Самое простое - указать версию в URL.

Вот готовые варианты, когда самому ничего делать не надо:

Вот один интересный готовый вариант.

Это всего лишь роутинг атрибутов с 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.
  • http://swagger.io/
  • https://github.com/domaindrivendev/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("http://www.dotnextrzd.com") .Email("[email protected]")) .License(lc => lc.Name("License").Url("http://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"); }); } }

Теги: Добавить метки

Зачем, например, заморачиваться с методом DELETE или там заголовком Accept? Не проще ли использовать метод GET и передавать все в параметрах, например, delete=true или format=json ? Вбил в браузере, и работает! А вот этот ваш DELETE так просто через браузер не пошлешь. На что я ответил примерно так.

Вот, допустим, у вас есть некоторые ресурсы. Для определенности, пусть это будут книги и пользователи. Что, собственно, означает иметь REST API для работы с этими ресурсами? В первом приближении, следующее. Если мы хотим получить какую-то книгу, то говорим GET /books/123 . Аналогично информация о пользователе получается запросом GET /users/456 . Вообще-то, в начале URL неплохо бы иметь что-то вроде /api/v1.0/ , но для краткости мы это опустим. По умолчанию данные отдаются, например, в JSON’е , но при желании мы можем передать Accept-заголовок с другим форматом. Для создания или обновления существующей книги следует использовать метод PUT, передав данные в теле запроса и указав формат этих данных в заголовке Content-type. Для удаления данных используется метод DELETE.

Внимательный читатель спросит, а для чего тогда нужен POST? Вообще, если делать все по науке, он должен использоваться для добавления элементов в сущность, словно она является неким контейнером, например, словарем. Однако на практике так обычно не делают, ведь при использовании API несколькими клиентами один клиент может изменить название книги, а второй — ее цену, в результате чего получится ерунда. Поэтому POST либо вообще не используют, либо используют в качестве замены методов PUT и DELETE. То есть, POST с каким-то телом запроса работает, как PUT, а без тела запроса — как DELETE. Это позволяет работать с клиентами, которые почему-то не умеют посылать PUT и DELETE.

Можно работать и сразу с целыми коллекциями. Для получения списка всех пользователей говорим GET /users , а для создания нового пользователя с автоматически сгенерированным id — POST /users . Как и ранее, в последнем случае данные передаются в теле запроса. Также можно перезаписать всю коллекцию, сказав PUT /users , и удалить сразу всех пользователей, сказав DELETE /users . Еще иногда требуется фильтрация по полям или пагинация, в этих случаях делают так:

GET /api/v1.0/users?fields=id,email,url&offset=100&limit=10&order_by=id

… или как-то так:

GET /api/v1.0/logs?from=2013-01-01+00:00:00&to=2013-12-31+23:59:59

Как бы, это все. Довольно однообразно и даже логично, не так ли? Так чем такой подход лучше описанного в начале поста?

В свое время я имел удовольствие работать над проектом, где API был устроен «простым и понятным» образом, на методах GET и POST, со всякими delete=1 и так далее. Смею вас заверить, что на самом деле вы этого не хотите. Потому что на практике работа с этим API превращается в какой-то кошмар.

Допустим, один программист занимается книгами, а второй пользователями. Первый решает, что для получения списка всех сущностей будет использоваться запрос GET /all_books , а второй решает перечислять только id и использовать URL GET /select_user_ids . Для удаления сущности первый программист решает использовать параметр del=true , а второй — delete=1 . Для экспорта данных в CSV первый программист делает поддержку export=text/csv , а второй — format=CSV . Потом выясняется, что некоторые библиотеки не умеют посылать GET-запросы со слишком длинными query string и ходить за данными на чтение начинают методом POST. А затем кто-то случайно удаляет через браузер всех пользователей в боевом окружении… И так далее, и тому подобное, полный бардак в общем.

Вы спросите, что же мешает привести все это безобразие в одному стандарту, например, использовать только del=1 и export=csv ? Так вот, REST — это и есть то самое приведение к одному стандарту , с учетом всяческих граблей типа случайного удаления данных через браузер и так далее. Притом у разных компаний этот стандарт одинаковый. Когда в команду разработчиков приходит новичок, вы просто говорите ему, что у вас всюду REST, а основные ресурсы — это пользователи и книги. Все, после этого одного предложения ваш новый коллега знает 90% API, безо всякого там чтения Wiki. Если вы хотите говорить с иностранцами, вы же просто используете общепринятый английский язык , а не изобретаете новый? Вот так же и здесь. Нельзя также не напомнить о пользе повторного использования протоколов и кода. А ведь для работы с REST, и HTTP вообще, написана куча библиотек и фреймворков.

Вы скажите «я, конечно, согласен, что REST такой весь из себя интуитивно понятный и общепринятый, но что, если я просто хочу загрузить через браузер список книг в формате CSV»? Тут важно понимать, что REST — это не о том, как сделать все через браузер . Предполагается, что должен быть клиент, который умеет работать с вашим API, вот через него и экспортируете. Но если по каким-то причинам это затруднительно, вы можете, например, использовать curl. Если у вас нелады с консолью, вы без труда найдете множество GUI-клиентов или, скажем, какой-нибудь плагин для Chrome, с аналогичным функционалом. Однако я все же советую попробовать curl. Пользоваться им совсем не так сложно, как вам может казаться. Всего-то нужно запомнить десяток параметров.

Так задаются дополнительные HTTP-заголовки:

H "Accept: text/csv" -H "Content-type: application/json"

Выбираем используемый метод:

X{GET|PUT|POST|DELETE}

Указываем тело запроса:

D "{"name":"Alex","url":"http://сайт/"}"

D @filename.json
# чтобы при этом не удалялись символы новой строки:
--data-binary @filename.json

Выводим заголовки из ответа сервера в stdout:

Говорим передавать данные в gzip’е:

Сохраняем тело ответа в указанный файл вместо stdout:

Наконец, для отключения буферизации используйте флаг -N . Может пригодится, если вы работаете с большими объемами данных или бесконечными потоками.

Теперь рассмотрим пару примеров.

Экспорт книг в формате CSV:

curl -H "Accept: text/csv" http://localhost/api/v1.0/books -o books.csv

Создание пользователя c выводом заголовков из ответа сервера в stdout:

curl -XPOST -H "Content-type: application/json" -d "{"name":"Alex"}" \
http://localhost/api/v1.0/users -D -

Удаление пользователя с заданным id:

curl -XDELETE http://localhost/api/v1.0/users/123

Несложно, правда ведь?

Несколько финальных замечаний, относящихся не совсем к REST. Во-первых, иногда от приложения требуется не только предоставлять доступ к некоторым ресурсам, но и выполнять какие-то команды. Таким командам имеет смысл выделять URL-адреса, начинающиеся с /commands/ . Например, запуск почтовой рассылки по всем пользователям будет выглядеть как-то так:

curl -XPOST -H "Content-type: application/json" \
-d "{"subject":"Good news, everyone!","body":"..."}" \
http://localhost/api/v1.0/commands/notify_all_users_via_email

Дополнение: Некоторые команды должны быть доступны только в тестовом окружении, для них можно выделить URL-адреса, начинающиеся с /debug/ .

Во-вторых, иногда требуется реализовать бесконечные потоки событий , или отправку текущего состояния, а затем обновлений к нему. Таким концам разумно выделить URL, начинающиеся, например, со /streams/ . Вот как примерно это должно работать:

curl -H "Accept: application/x-json-stream" \
http://localhost/api/v1.0/streams/users -N

{"type":"user","data":{"id":123,"name":"Alex","url":"http://сайт/"}}
{"type":"user","data":{"id":456,"name":"Bob","url":"http://ya.ru/"}}
...
{"type":"sync"}
{"type":"heartbeat"}
{"type":"heartbeat"}
{"type":"user_deleted","data":{"id":123}}
...

Нужно обратить внимание на несколько моментов. Здесь используется формат x-json-stream , то есть, поток JSON-объектов, разделенных символом \n. Если этот символ встречается в самом JSON-объекте, его, соответственно, следует кодировать. Некоторым клиентам может быть удобнее работать с честным JSON’ом, то есть, списком JSON-объектов. Предусмотреть поддержку сразу нескольких форматов довольно просто. Во втором случае список объектов должен начинаться с открывающейся квадратной скобки, а объекты должны разделяться запятыми. Для удобства работы со стримом нужно либо ставить после запятых символ \n, либо делать это на стороне клиента с помощью sed:

curl ... | sed "s/},/}\n/g"

Каждый объект имеет поле type и опциональное поле data. Объекты с типом heartbeat посылаются несмотря ни на что один раз в пять секунд. Если клиент не видит такого объекта в течение десяти секунд, он считает, что либо что-то сломалось на стороне сервера, либо что-то не так с сетью, и закрывает соединение. Объект с типом sync используется в стримах, посылающих некое состояние, а затем обновления к нему, для разделения первого от второго. Наконец, все остальные типы представляют собой полезную нагрузку. Поле data нужно по той причине, что вложенные данные также могут иметь поле type, что приводило бы к неразберихе.

В-третьих, когда вы пишите RESTful приложение, старайтесь с самого начала придерживаться некоторых соглашений. Например, с самого начала договоритесь, что имена полей в JSON-объектах должны всегда писаться в camelCase. Раз и навсегда запретите использовать в идентификаторах такие спецсимволы, как знак плюс и пробелы. Договоритесь, что в случае получения кода 301 клиент должен посылать точно такой же запрос на URL, указанный в заголовке Location. Примите соглашение о том, как будет передаваться автоматически сгенерированные id. Например, в Riak для этого используется заголовок Location . Подумайте о том, как вы будете сообщать о различных типах ошибок, в том числе временной недоступности БД, ошибках валидации полей и так далее. Пользователи почти наверняка предпочтут увидеть:

{"message":"validation_error","description":"..."}

… вместо кода 500 без каких-либо дополнительных пояснений. Если для вашего приложения важна точность представления чисел, договоритесь передавать все числа в виде строк, чтобы json-декодер не терял точность из-за преобразования строк во float’ы.

Но помните, хотя все написанное выше — это идеал, к которому стоит стремиться, на практике всем наплевать на стандарты . А значит, вас ждет много подпорок, слепленных на скорую руку, нежелание коллег переходить на более правильные версии API (зачем, если все работает?), и многие другие увлекательные вещи.

От переводчика:
Я впервые попробовал перевести статью такого объёма и IT-тематики, с радостью прочту ваши комментарии и замечания. Что же касается самой статьи: я не согласен с автором как минимум потому, что, по сути, он заменяет REST на… REST (!!!), но немного в другом обрамлении. Однако, не смотря на то, что в статье преподносится много очевидных вещей, мне она показалась достойной обсуждения на Хабре.

Почему Вам стоит похоронить эту популярную технологию

RESTful api - это чудесно, ведь так?

Если за последние 10 лет Вы читали резюме веб-разработчиков, то Вам простительно думать, что RESTful API - это некое божественное дарование, сошедшее к нам с небес. REST API используется повсюду, даже маркетологи постоянно упоминают о нём в материалах, предназначенных сугубо для руководства или персонала.

Так на сколько всё же хороша идея REST API? Перед тем как мы разберемся с этим вопросом, давайте посмотрим откуда растут корни…

Откуда вообще взялся REST?

Данная технология стала популярной, когда она была подробно описана и представлена Роем Филдингом в его докторской диссертации под названием Architectural Styles and the Design of Network-based Software Architectures в 2000 году. Рой известен своими вкладами в развитие веба, в особенности HTTP.

Так что же такое RESTful API?

REST - это стиль архитектуры программного обеспечения для построения распределенных масштабируемых веб-сервисов. Рой выступал за использование стандартных HTTP методов так, чтобы придавать запросам определённый смысл.

Таким образом, данные HTTP-запросы будут иметь различную смысловую нагрузку в REST:

  • GET /object/list
  • POST /object/list
  • PUT /object/list
Выше только некоторые виды запросов, а вот весь их список: CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE . Если вы даже не слышали о некоторых из них - не беда, так как есть методы, которые практически никогда не поддерживаются ни клиентским, ни серверным приложением.

Рой также утверждал, что HTTP-коды ответов помогут в определении смысла самих ответов. Существует около 38 кодов ответа и ниже вы можете увидеть их список. Названия некоторых я немного сократил для удобства:

Итак, одна транзакция по такому API будет состоять, как минимум, из следующего:

  • Метод запроса , например, GET
  • Путь запроса , например, /object/list
  • Тело запроса , например, форма
  • Код ответа , например, 200 ОК
  • Тело ответа , например, данные в формате JSON
Многие положительно отнеслись к такой парадигме и стали использовать её в разработке веб-сервисов с использованием HTTP. Это и есть то, что мы называем RESTful API .

Кстати, транзакция может быть более сложной и причины этому мы еще обсудим. Однако мы опустим те усложняющие факторы, которые связаны с сетями и кешированием, так как эти вопросы актуальны и для других технологий.

На самом деле RESTful API довольно ужасно

REST является отличным механизмом для многих вещей, например, таких как получение контента, и он отслужил нам верой и правдой почти 20 лет. Однако, настало время раскрыть глаза и признать, что концепция RESTful API является одной из худших идей, когда-либо существовавших в веб-разработке. Нет, я не спорю, Рой - отличный парень и, конечно же, у него было множество классных идей… Тем не менее, я не уверен, что RESTful API попадает в их список.

Вскоре мы посмотрим на другое, более правильное решение для построения API, но, перед тем как сделать это, нам следует понять 5 главных проблем RESTful API, которые делают его дорогим, уязвимым к ошибкам и неудобным. Начнём!

Проблема №1: До сих пор нет общего согласования того, что такое RESTful API

Вряд ли кто-то задумывался над тем почему эта технология называется именно «RESTful», а не «RESTpure»? (прим. переводчика: pure - чёткий, понятный ) А потому что никто не может определиться с тем, что из себя представляют все методы запроса, коды ответа, тела и т.д.

Например, когда мы должны использовать код 200 ОК ? Можем ли мы использовать его для подтверждения успешного апдейта записи, или нам стоит использовать код 201 Created ? Судя по всему, нужно использовать код 250 Updated , однако его не существует. И еще, кто-нибудь может объяснить что означает код 417 Expectation failed ?! Кто-нибудь кроме Роя, конечно.

Словарь HTTP методов и кодов слишком расплывчатый и неполный, чтобы прийти наконец к единым определениям. Нет никого, если я не ошибаюсь, кто нашел единый, общий порядок и призвал остальных его соблюдать. То, что подразумевается под 200 ОК в одной компании может обозначать вовсе иную информацию в другой, что делает обсуждаемую технологию непредсказуемой .

Если бы это было единственной проблемой, то я, наверное, смирился бы и продолжал писать RESTful API по сей день. Однако, наш список только раскрывается…

Проблема №2: Словарь REST поддерживается не полностью

Даже если бы мы решили первую проблему, то столкнулись бы со следующей, практической: большинство клиентских и серверных приложений поддерживают не все коды ответа и, собственно, глаголы, означающие HTTP-методы. Например, большинство браузеров имеют ограниченную поддержку PUT и DELETE.

Как же мы с этим справляемся? Одним из способов является вставка глагола , обозначающего нужный метод, в отправляемую форму. Это значит, что в данном случае запрос включает в себя:

  • Метод HTTP запроса , например, POST
  • Адрес запроса , например, /object/list
  • Метод, который мы на самом деле подразумеваем , например, DELETE
  • Тело запроса , например, данные из формы
Ситуация с кодами ответа не лучше. Разные браузеры (и серверные приложения тоже) часто понимают эти коды по-разному. Например, получив код 307 Temporary redirect , один браузер может позволить пользовательскому скрипту рассмотреть этот ответ и отменить действие до его выполнения. Другой браузер может просто напросто запретить скрипту делать что-либо. На самом деле, единственными кодами, обработки которых можно не бояться, являются 200 ОК и 500 Internal server error . В остальных же случаях поддержка ответов варьируется от «довольно хорошей» до «просто ужасной». Именно по-этому нам часто приходится дополнять тело ответа кодом, который мы на самом деле подразумевали .

Даже если мы всё же смогли бы согласовать всё вышеописанное, а еще магическим образом пофиксили всё подключённое к интернету, но не приспособленное к REST программное обеспечение - мы всё равно столкнёмся с очередной проблемой.

Проблема №3: Словарь REST недостаточно насыщен

Словарь, состоящий только из HTTP методов и кодов ответа, является слишком ограниченным для эффективной передачи и приёма разнообразной информации, необходимой всем приложениям. Представьте, что мы создали приложение, из которого мы хотим отправить клиенту ответ «render complete». К сожалению, мы не можем сделать это с помощью HTTP кодов, так как, во-первых, такого кода не существует , а во-вторых мы не можем его создать, так как HTTP - не расширяемый . Минутка разочарования. Думаю нам снова придётся вставлять то, что мы подразумеваем в тело ответа.

Также проблема в том, что у нас не один словарь, у нас их три! Коды ответов - это числовые значения (200, 201, 500), которые отличаются от представления методов запроса (GET, POST, PUT и т.д.), а тело ответа и вовсе в формате JSON. Выполнение REST транзакций - это как отправка письма на английском языке в Китай и получение оттуда ответа морзянкой. Все эти сложности являются крупным источником путаницы и ошибок. Вот мы и перешли к следующей глобальной проблеме: дебаггинг.

Проблема №4: RESTful API очень трудно дебажить

Если Вы когда-то работали с REST API, то Вы наверняка в курсе, что его почти невозможно дебажить. Для того, чтобы понять то, что происходит во время транзакции, нам приходится просматривать сразу 7 мест:
  • Метод HTTP запроса , например, POST
  • Адрес запроса , например, /object/list
  • Метод, который мы на самом деле подразумеваем (в теле запроса) , например, DELETE
  • Собственно, тело запроса , например, данные из формы
  • Код ответа , например, 200 ОК
  • Код, который мы подразумевали (в теле ответа) , например, 206 Partial Content
  • Собственно, тело ответа
Так вот теперь у нас не только два сильно ограниченных словаря, так еще и 7 разных точек в которых может крыться ошибка. Единственное, что могло бы еще более усугубить ситуацию - это если бы технология REST была полностью привязана к одному протоколу и было бы невозможно использовать какой-либо другой канал связи. Собственно, так и есть, и это - наша следующая большая проблема!

Проблема №5: Как правило, RESTful API привязаны к протоколу HTTP

RESTful API в дребезги разбивает один из фундаментальных законов о хорошей связи: содержимое сообщения должно быть абсолютно независимо от канала передачи . Их смешивание - это путь к сплошной путанице.

Постоянное переплетение HTTP протокола и передаваемой информации полностью лишает нас возможности переноса RESTful API на другие каналы связи. Портирование RESTfulAPI с HTTP на какой-либо другой протокол передачи данных требует полного распутывания и реструктуризации информации из семи разных точек, о которых мы говорили ранее.

К счастью, есть хорошее решение, которое позволяет избежать либо минимизировать все проблемы RESTful API. Встречайте!

Шаг вперёд: JSON-pure API

JSON-pure API справляется с большинством проблем, которые мы только что рассмотрели.
  • Использует только один метод для передачи данных - обычно POST для HTTP и SEND в случае использования Web Sockets
  • Механизм передачи и содержимое запроса полностью независимы. Все ошибки, предупреждения и данные передаются в теле запроса, в формате JSON
  • Используется лишь один код ответа, чтобы подтвердить успешную передачу, обычно это 200 ОК
  • Механизм передачи и содержимое ответа полностью независимы. Все ошибки, предупреждения и данные передаются в теле ответа, в формате JSON
  • Гораздо проще дебажить, ведь все данные находятся в одном месте в легко-читаемом формате JSON
  • Легко перенести на любой канал связи, например, HTTP/S, WebSockets, XMPP, telnet, SFTP, SCP, or SSH
JSON-pure API появилось в следствии осознания разработчиками того факта, что RESTful API не особо дружелюбно к браузерам и самим разработчикам. Разделение сообщения и способа передачи делает JSON-pure API быстрым, надежным, простым в использовании, портировании и поиске ошибок. Сегодня, если нам понадобится, например, использовать API Твиттера, то мазохисты выберут RESTful API. Остальные же обратятся к JSON-pure API, или, как его еще называют, «Web API».

За последние десять лет меня не раз просили использовать RESTful вместо JSON-pure. Крайний раз, когда мне чуть было не пришлось поддерживать RESTful API, был в 2011 году. К моему счастью, бэк-енд команда согласилась параллельно с RESTful запустить JSON-pure API, просто перенеся все свои методы и коды в JSON.
Спустя несколько месяцев все мои знакомые, ранее использовавшие RESTful, перешли на JSON-pure, осознав, что это гораздо удобнее.

Обзоры