Именование сложных действий в REST API

    Во всех руководствах в описаниях REST дают простые примеры, типа вот вам пользователи, они будут ресурсом /users, вот вам один пользователь, он будет /users/[id] и действия с ним добавить\удалить\изменить.

    А что если действия сложные или комплексные и не вписываются в GET\POST\DELETE?



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

    Вкратце о проблеме:

    Главное отличие REST от RPC заключается в подходе, что URL — адрес ресурса, действия с которым выполняются с помощью стандартных HTTP-методов: GET\POST\DELETE\PATCH\… Если кратко — ресурс это существительное, а не глагол.

    Т.е. если вы видите что-то типа /api/users/do_some_cool_staff или GET /users/1?action=delete, знайте — это не REST!


    Предположим, мы пишем приложение «ядерный чемоданчик» с REST интерфейсом. Функционал будет такой:

    1. Действия с ракетой [id]: запуск по стране, продажа в страну, обслуживание, списать в утиль

    Логично использовать ресурс /missiles/[id], но для этих действий просто использовать PUT или PATCH некорректно и неинформативно.

    Неправильные варианты:
    1. Можно расширять список стандартных HTTP-методов. Но могут быть сложности с поддержкой у разных клиентов, готовых библиотек и фильтрации настройками сервера.
    2. /missiles/[id]?action=launch лишает смысла весь REST подход, т.к. бОльшая часть функционала будет в действиях.
    3. /missiles/[id]/launch — тащить глагол в адрес ресурса неправильно.
    4. LINK /missiles/[id]/countries/[country_id]/ — непонятно что именно происходит — продажа или ядерный удар.

    Правильный вариант
    Правильно обращаться к свойствам ресурса. Т.е. не действие "/users/[id]/disable", а ресурс "/users/[id]/disabled", к которому логично применимы стандартные методы GET\POST\…

    Запуск ракеты: POST /missiles/[id]/countries-landed/[country_id]
    Продажа ракеты: POST /countries/[country_id]/missiles-bought/[id] или POST /missiles/[id]/owner/[country_id]
    Обслуживание: POST /missiles/[id]/on-service

    UPD:
    Запуск ракеты: POST /missiles/[id]/launcher {target: [country_id]}
    Продажа ракеты: PATCH /missiles/[id] {owner: [country_id]}
    Обслуживание: PUT /missiles/[id]/on-service или если есть отдельный сервис обслуживания ракет POST /missiles_service/ {missile: [id]}
    Списать в утиль: DELETE /missiles/[id] или если могут быть другие варианты «удаления» PUT /missiles/[id]/utilized

    2. Запустить любую подходящую ракету по стране [country_id]

    Предположим, что ракет очень дофига и делать сначала поиск, а потом запуск накладно и долго, нужен один запрос на моментальное исполнение.
    Неправильные варианты:
    1. Выполнить «запуск» на всю коллекцию ракет ([id] конкретной ракеты мы не знаем).
    2. Действие «получить ракетой» у выбранной страны

    Правильный вариант
    Если действие комплексное и не может быть выражено через атомарные действия над ресурсом-объектом, создается ресурс-процесс. Аналогично ресурс-процесс создается в случае, если требуется произвести составное действие со сложной бизнес-логикой (состоящее из нескольких зависимых действий), например банковская транзакция.

    Мысль более развернуто
    Let us relook at what Roy Fielding’s dissertation says about resource: “...any concept that might be the target of an author's hypertext reference must fit within the definition of a resource….”. Business capabilities / processes can neatly fit the definition of resources. In other words, for complex business processes spanning multiple resources, we can consider the business process as a resource itself. For example, the process of setting up a new customer in a banking domain can be modeled as a resource. CRUD is just a minimal business process applicable to almost any resource. This allows us to model business processes as true resources which can be tracked in their own right.


    Т.е. правильным будет создание ресурса «Запуск ракеты».
    Тогда POST будет запускать ракету, GET узнавать состояние процесса, DELETE отменять его. При этом сохраняется консистентность, т.е. ракета уже летит, а статус страны «еще существует».

    Бонусом:

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

    Пара примеров ахтунгов:
    • Twitter
      POST friendships/create
      POST friendships/destroy
      POST friendships/update
      GET friendships/show
      GET friends/list
    • Mail.ru
      Все вызовы методов API — это GET или POST HTTP-запросы к URL www.appsmail.ru/platform/api с некоторым набором параметров. [...] На данный момент, API не делает различий между GET- и POST-запросами.


    Единственное приличное REST API, которое я смог обнаружить GitHub. Аминь.
    Share post

    Similar posts

    Comments 113

      –5
      Оказалось, что многие крупные компании вообще не имеют представления, что такое REST, при этом в описании используют именно это название

      Думаю имеют, а вот вы не имеете представления что такое HATEOAS, но уже вовсю критикуете различные реализации.
        0
        это я про пример api твиттера, если че
          –1
          Haters gonna hate
            +2
            Мне всегда казалось, что HATEOAS не является часть концепции RESTrul API и более применим к гиперссылочным ресурсам, чем к ресурсам сериализуемым в JSON. Конечно можно и на основе JSON эмулировать гиперссылочное связыванием, но нужно ли?

            Если есть ссылочка на вменяемую реализацию REST API + HATEOAS было бы интересно почитать.
              0
              martinfowler.com/articles/richardsonMaturityModel.html

              HATEOAS больше дружит с XML, это так. Но как бы и сам по себе REST больше дружит с XML — JSON просто оказался популярнее за счет того что он проще.

              Мне лично нравится проект стандарта jsonapi.org
                +1
                > Но как бы и сам по себе REST больше дружит с XML

                Хм. Ни разу не слышал этого мнения… мне всегда казалось, что REST не описывает способ репрезентации ресурса. Поэтому сам по себе формат вывода не так важен. Более того, у нас хорошим REST API считается то, которое умеет отдавать данные и различных форматах.
                  0
                  Проблем с изменением структуры ответов у XML поменьше будет. Если у вас хранился один элемент и в обновлении будет храниться коллекция элементов, клиент использующий старую версию не отвалится. С json же так не выйдет.

                  Но в целом да, просто вопрос структуры.
                    0
                    Не понял прямой связи между простотой изменения структуры и тем, что REST больше дружит с XML. Пока так и не нашёл в описании концепции RESTful API хоть какого-то указания на формат данных. Если можете, опишите свою мысль более развернуто.
                      –1
                      Это дружит весьма субъективно. У XML есть масса преимуществ (атрибуты, xpath) которые позволяют более качественно организовывать представление ресурсов так, что бы изменения этой структуры были не так страшны (не нужно вводить версионизацию для небольших изменений).

                      Вот вам пример. У вас в одной версии API был один элемент, а во второй — это уже должна быть коллекция элементов. В случае если используется XPath + XML то клиентам вашей апишки будет пофиг, а в случае с json будет наблюдаться значительные изменения структуры при десериализации и 99% что приложение на клиенте, если у API не было организовано версионизации, просто крэшнится.

                      Опять же, сам по себе REST не регламентирует ничего относительно формата представления ресурсов. И я не призываю всех использовать XML.
                        0
                        Да, действительно весьма субъективно. Но мысль примерно понял.
            +8
            Не нужно стараться впихнуть невпихуемое, REST не везде подходит: habrahabr.ru/post/204958/
              +3
              В заметке я как раз пытался осмыслить впихуемость более сложных ситуаций в REST.

              Моё понимание stateless применимо к REST с авторизацией — сервер не должен хранить состояние между запросами, связанных сессией.
              Т.е. не должно быть последовательностей типа вызова prepareQuery -> executeQuery, которые были бы привязаны к сессии, а не к своему идентификатору. Надо что-то подготовить\сгенерить — отдал идентификатор, по которому выполняется следующий запрос.

              Первый вопрос: ну, дорогие мои, а как эти ваши setUserCity, calculateMatrix и startVideoConverter могут быть реализованы на REST?

              setUserCity(userId, cityId) -> PATCH /users/[id] {city_id: [city_id]} // частичное изменение данных
              calculateMatrix(data) -> POST /matrix_calculation/ {data}
              startVideoConverter(options, source, destination) -> POST /video/[id]/convertation {options}
              
            +4
            Мда… сами критикуете, а что предлагаете?
            «Запуск ракеты: POST /missiles/[id]/countries-landed/[country_id]»
            В каком это месте запуск? Это приземление.
            Запуск это:
            PUT /missile/[id]/state/launched/
              –2
              Мы ракету запускаем куда-то, поэтому задаем страну приземления, остальная бизнес-логика по запуску и доставке крутится внутри.

              GET /missiles/[id]/countries-landed — получаем список стран которые накрыло (например, ядерным по лихтенштейну еще зацепит тройку-другую),
              GET /missiles/[id]/countries-landed/[country_id] состояние запуска\полета\приземления.

              А ресурс /missile/[id]/launched/ отвечает за то, была ли она вообще запущена (возможна ситуация, когда запуск уже назначен, но еще не осуществлен)
                +6
                Проблема в том, что ракета летит какое-то время, вот пока она летит — живет состояние, т.е. первая команда может прислать ей цель, остальные корректировать и уточнять цель, типичный случай для RPC, даже, если команда одна — стартовая, то состояние сохраняется до конца полета и можно, как минимум, проверять состояние процесса, прогресс, координаты.
                +4
                запуск это:

                POST /launches/
                {
                country: ...,
                }
                и пусть какую ракету выбрать само апи решает (ближе к цели, развернута и т.п.)
                тут и параметры можно задавать и получив id запуска цель у него поменять с помощью PATCH и т.п.
                  +3
                  Поддерживаю. Такой вариант и выглядит более понятным, и вписывается в «строгий» REST.
                    +2
                    Поддерживаю. Запуск для ракеты – тоже самое что транзакция для денег.

                    POST /launches/
                    {
                        missle: ...,
                        country: ...,
                    }
                    
                      0
                      Спасибо, была мысль в этом направлении, только никак не смог определиться с операцией (PUT или POST).
                    +7
                    Самый простой и сравнительно честный способ оформлять «действия» в REST — вводить ресурс, представляющий результат этого действия:
                    Пример
                    // Это не REST
                    POST /AddNumbers
                    {
                      "a": 2,
                      "b": 3
                    }
                    
                    ответ:
                    { "result": 5 }
                    
                    
                    // Это REST
                    POST /NumberSums
                    {
                      "a": 2,
                      "b": 3
                    }
                    
                    ответ:
                    201 - created
                    Location: /NumberSums/uhfweiufhi234uhr23e42342
                    
                    далее:
                    GET /NumberSums/uhfweiufhi234uhr23e42342
                    {
                      "value": 5
                    }
                    

                      0
                      именно. Что в свою очередь добавляет нам идемпотентность в случае использования PUT! А это дает гарантию, что действие выполнится только 1 раз
                        0
                        Думаю, это не универсальный вариант – иногда «результат» действия может быть отложенным во времени. Например некорректно команду запуска ракеты определять через создание ресурса результата приземления ракеты – ракета определенное время будет находится в воздухе, и не приземленной, и не в состоянии быть запущенной снова.
                          0
                          Вопрос решается статусами. На эту тему есть хорошая статья про кофе и старбакс.
                            0
                            Вы сразу несколько переходов сделали. Действие: «запустить ракету». Результат действия: «запуск ракеты» (читать как «один запуск ракеты»). В моём примере было действие: «сложить 2 числа», результат действия: «сумма двух чисел».
                          0
                          Все действия, помимо тривиальных GET, POST, PUT, DELETE, выносятся в отдельную коллекцию:
                          POST /missiles/{id}/actions/launch
                          POST /missiles/{id}/actions/sell

                            +5
                            Есть такой подход:

                            • Рассматриваем запуск ракеты как отдельную сущность
                            • Раз ракета это сущность, у нее будет свой URL: POST /rocket_launches/...
                              +1
                              Забавно, что двумя комментариями выше я предложил этот же самый вариант, но, кажется, был непонят.
                              +3
                              Главная проблема с REST — мало кто понимает зачем он нужен.

                              Поэтому делают API так как удобно на практике — RPC поверх http + json + url-encoded параметры.
                              Почему?
                              1) Это просто. Почти везде есть http клиент. (Но не все клиенты умеют все необходимое для REST)
                              2) Это легко отлаживать. Вы можете делать запросы обычным браузером на любом компьютере.
                              3) Это не ломает мозг с именованием. Как назвать метод «сделать красные кнопки зелеными», доступную только для партнеров или айфона босса?

                              Вообще у REST мало хороших примеров.
                              Календари (google-calendar точно) — хороший пример.
                              Кто знает еще?

                                +2
                                RPC поверх http + json + url-encoded параметры

                                Золотые слова. Мыслю аналогично. Четыре года, как ничего удобнее/лучше/управляемей придумать не могу. Всякие WCF (даже курсы MS окончил в свое время, так что представление имею), REST в мире интернета и реальных задач иногда накладывают ограничения, усложняют, скрывают детали реализации, мешают.

                                Да и что может быть проще и удобнее, чем взять инстанс, взять интерфейс и по интерфейсу предоставить к нему доступ?
                                Тогда абсолютно всё равно — дёргаешь ты его как reference в памяти или по такому вот http/json/rpc API.
                                –5
                                По совокупности комментариев рождается решение для выбора ракеты

                                POST /missiles/targets/country
                                {
                                «name»: Russia,
                                }

                                ответ:
                                201 — created
                                Location: /missiles/uhfweiufhi234uhr23e42342

                                далее:
                                GET /missiles/uhfweiufhi234uhr23e42342
                                {
                                «value»: Черная Каракатица из-под Риги
                                }
                                  +14
                                  В первую очередь country_id — это не часть объекта missile. Ситуация, когда используется подход POST /missiles/[id]/countries-landed/[country_id] — это примерно то же самое, когда в контроллере, где им совсем не место, пишутся SQL-запросы.

                                  Одно из архитектурных свойств REST — упрощение интерфейсов. Ракета не должна каким-нибудь извращённым способом через REST влиять на популяцию рыбы в Керченском проливе. Через интерфейс ракеты не нужно получать список стран, в которых приземлялись ракеты. Само по себе выражение «countries-landed» извращает смысл и усложняет работу. Countries — это множественное число, которое, к тому же, ограничивает возможные цели ракеты только странами (а ракеты, вообще, должны приземляться на точки с определёнными координатами; «страна» — это вообще слишком большой разброс координат). Landed — это прошедшее время, которое подразумевает уже свершившийся факт. А информация о том, куда ракета продавалась — это информация, которая не связана с функциями ракеты, что вообще засоряет интерфейс работы с ракетой. Когда ракета выполнила свою задачу, информация о ней должна храниться в архивах военной базы, так как после уничтожения ракеты у неё по определению не может быть интерфейса, так как её больше не существует.

                                  В целом, из-за своей природы, компьютер сожрёт что угодно. Можно написать так, что вызов «cancel» будет запускать ракету, а вызов «launch» будет аварийно отключать всю электронику. Человекопонятные названия — это для людей. Значит эти названия не должны извращать смысл действий. «countries-landed» по своему смыслу означает, что нужно вывести список стран, в которых ракета уже приземлялась. В такой ситуации вместо использования одного понятного выражения придётся писать кучу документации, чтобы объяснить, что вы имели ввиду под «countries-landed». К тому же, если вы вызываете country_id, значит у этого конкретного объекта «страна» должны быть все свойства для нормальной работы со странами. Иначе получится, что у вас в одном интерфейсе объект «страна» представлена одним способом, в другом объекте — другим способом, в третьем объекте — третьим способом. И становится вообще непонятно, что это за фигня такая: «страна»?

                                  У ракеты не может быть списка стран, в которых она приземлялась. Ракета имеет одноразовый характер. Если она приземлилась один раз, она больше не будет приземляться ещё где-то, потому что приземление вызывает её уничтожение. Есть смыл использовать «countries-landed» где-то в статистических отчётах о прошедших запусках какой-то военной базы, но это точно не та информация, которая должна храниться в объекте «ракета».

                                  Важно не искажать смысл объекта и не делегировать ему лишних полномочий. То, что в REST-интерфейсе не должно быть глаголов — это фантазии. Если использование глаголов упрощает интерфейс, лучше использовать глаголы, а не плодить кучу мусора, которая выполняет действие с помощью существительных. Это всё-таки не конкурс писателей.

                                  С вашим подходом вообще можно прийти к такому интерфейсу:

                                  [GET|POST|PUT|DELETE] /missiles/[missile-id]/owner/[country_id]/cities/[city-id]/people/[human-id]/current-wearing

                                  Или к такому:

                                  [POST] /missiles/[missile-id]/owner/[country_id]/bases/[base-id]/missiles/[missile-id]/targets/[target-id]/bases/[base-id]/missiles/[missile-id]/countries-landed/[country_id]

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

                                  У вас, возможно, была какая-то отличная мысль, но примерами вы эту мысль завели в неправильную сторону. И это совсем не помогает оценить все прелести вашего подхода, так как они искажают смысл.
                                    +2
                                    Спасибо за развернутый коммент, с большей частью согласен, свойства в примерах действительно получились неудачные и не совсем корректные. Вложенность объектов в адресе не стоит использовать там где это не отражает их связей. Поменял в статье на более осмысленные.

                                    То, что в REST-интерфейсе не должно быть глаголов — это фантазии.


                                    С этим всё же не согласен, т.к. по смыслу как глагол может быть ресурсом? В чем будет смысловая разница между GET и POST на глагол LAUNCH.
                                    Понятно, что должен быть здравый смысл, но если упрощать «до победного» получим RPC.
                                      +1
                                      По-моему, так вполне очевидно, что PUT на launch запустит ракету, GET — узнает статус запущенной, POST поменяет целеуказание (доступно пока ракета летит, разумеется) а DELETE — взорвет ракету в воздухе. Знаете, почему? Потому что чуть не 99% глаголов, передающих на письме осмысленное действие, которое имет смысл запрограммировать, в английском языке полные омонимы существительным с тем же смыслом.

                                      Более того, в разумных пределах можно из существительного сделать глагол приписав перед ним «to», а из глагола существительное — приписав перед ним «the» (кроме непонятно в каких муках рожденного «a must»).
                                        0
                                        В целом верное замечание про омонимы, пусть ресурсы иногда будут совпадать по написанию, но если не понимать разницу, то можно докатиться до твиттеровских ахтунгов вроде POST friendships/destroy или POST friendships/update.

                                        По-моему, так вполне очевидно, что PUT на launch запустит ракету, GET — узнает статус запущенной, POST поменяет целеуказание (доступно пока ракета летит, разумеется) а DELETE — взорвет ракету в воздухе


                                        Не уверен насчет «очевидно». GET, PUT, DELETE — идемпотентны (сколько бы раз мы не вызывали такой метод — результат будет один и тот же.), а ракету нельзя запустить дважды.

                                        И в целом мне не кажется очевидной языковая конструкция GET «глагол». По смыслу получается «Я хочу получить запустить ракету» — что здесь действие: получить или запустить?
                                          0
                                          А удалить юзера можно дважды? Идемпотентность означает не одинаковость результата, а неизменность объекта при повторном применении операции (PUT, очевидно, можно вызвать на запущенной ракете, если результат определен как «да отстань, уже запущено» :)

                                          твиттеровский POST ★/destroy конечно должен быть DELETE ★, но по принципу бритвы Оккама, а не потому, что destroy это глагол.

                                            0
                                            POST — создание=запуск; PUT/PATH — изменение

                                            Про конкретный пример — правильнее использовать такое API:
                                            GET /launches/:id - получение информации о существующем запуске
                                            POST /launches - запуск ракеты и возвращение { missile: ..., country: ... }
                                            PUT / PATCH /launches/:id - изменение параметров запуска
                                            DELETE /launches/:id - отмена конкретного запуска
                                            


                                            Вообще у CodeSchool есть прекрасный курс по правильному REST API для Ruby on Rails: www.codeschool.com/courses/surviving-apis-with-rails
                                              0
                                              Уже не могу изменить свой комментарий, должно быть так:

                                              POST /launches { missile: ..., country: ... } - запуск ракеты, возвращение 201 статуса и ссылки на созданный запуск
                                              
                                          +4
                                          Просто, возможно, есть смысл не использовать в названиях глаголы, описанные в разделе 9 описания протокола HTTP 1.1 (который сейчас считается стандартом). Возможно, есть смысл не использовать глаголы, которые можно легко привести к какому-то глаголу из списка в 9-м разделе. Но это не повод отказываться от глаголов вообще.

                                          У вас в вашем вопросе на Тостере просто есть ошибки — системные ошибки. Чтобы построить хороший интерфейс, нужно хорошо описать предметную область. Хорошее понимание предметной области очень сильно поможет в реализации всей архитектуры. С точки зрения ракеты, её нельзя запустить по стране или продать в страну.

                                          Верховного Главнокомандующего Страны-Р можно заставить через REST бабахнуть по Стране-А. Это его ответственность и именно он может принимать такие решения, так как является в этой ситуации ключевым ресурсом. Перед тем, как ракета прилетит в Страну-А, произойдёт ещё куча REST-вызовов к различным ресурсам. Один ресурс содержит коллекцию координат, по которым эффективнее всего долбить в конкретной стране. Второй ресурс содержит коллекцию ракет, которые наиболее эффективным образом смогут пролететь от точки с текущими координатами до цели с какими-то ещё координатами. Третий — что-ещё. Сама ракета несёт ответственность только за себя. Работа двигателей, работа навигационного оборудования, координаты точки назначения, текущее состояние — это вещи, которые относятся к ракете.

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

                                          Именно из-за этого начинают возникать вопросы, вроде: как описать интерфейс ракеты, чтобы её можно было запустить или продать через «/missiles/[missile-id]» в country_id. Её не нужно запускать или продавать через неё саму. Если организовать архитектуру корректно, то у вас даже и мысли не возникнет, чтобы через «/missiles/[missile-id]» это делать. И не возникнет вопросов с тем, как именовать сложные действия. Если возникает ситуация со сложным действием, есть смысл делегировать отдельные шаги этого действия ресурсам, которые имеют соответствующую компетенцию, чтобы сама ракета могла сосредоточиться на процессе полёта и взрыва, а всякие философские вопросы при этом не грузили её процессорное время.
                                        –1
                                        запуск по стране

                                        PATCH /missile=1
                                        > missile target =/country=usa
                                        < missile
                                        	uri =/missile=1
                                        	target =/country=usa
                                        	state =fly
                                        	owner =russia
                                        	size =XXL
                                        

                                        продажа в страну

                                        POST /hold
                                        > hold
                                        	subject =/country=usa
                                        	size =1000btc
                                        < hold
                                        	uri =/hold=1
                                        	subject =/country=usa
                                        	size =1000btc
                                        

                                        POST /exchange
                                        > exchange
                                        	one =/missile=1
                                        	two =/hold=1
                                        < exchange
                                        	uri =/exchange=1
                                        	state =checking
                                        	one =/missile=1
                                        	two=/hold=1
                                        

                                        обслуживание

                                        PATCH /missile=2
                                        > missile state =service
                                        < missile
                                        	uri =/missile=2
                                        	state =service
                                        	owner =russia
                                        	size =M
                                        

                                        списать в утиль

                                        PATCH /missile=3
                                        > missile state =destroyed
                                        < missile
                                        	uri =/missile=3
                                        	state =destroyed
                                        	owner =russia
                                        	size =XXS
                                        

                                        Запустить любую подходящую ракету по стране

                                        PATCH /missile/owner=russia/state=ready/size>XXL/limit=1
                                        > missile target =/country=usa
                                        < missile
                                        	uri =/missile=4
                                        	state =fly
                                        	target =/country=usa
                                        	owner =russia
                                        	size =XXL
                                        
                                          0
                                          Если все действия выполнять через PATCH одного ресурса /missile/[id] то фактически мы утащим туда весь функционал.
                                          Опять же, неочевидно, что должно происходить при вызове /missile/[id]?state=destroyed&target=usa? Как узнать статус или результаты запуска?
                                            0
                                            Да, весь функционал, связанный с ракетами, будет там. Это разве плохо?

                                            Последний пример как раз это и иллюстрирует — у ракеты есть поле state с её текущим статусом.
                                            0
                                            Тезисно:
                                            1. uri должен идентифицировать ресурс, а не его положение в какой-либо иерархии ("/resourceName=resourceId "- необходимо и достаточно).
                                            2. любые действия можно свести к созданию ресурса и/или изменению уже существующего (клиент указывает, что хочет получить, а сервер уже выполняет 100500 действий для достижения этой цели, либо возвращает ошибку, если её достичь невозможно).
                                            3. к коллекциям можно применять не только глагол GET (но и PATCH, например)
                                            4. глагол DELETE лучше не использовать (он нарушает ссылочную целостность)
                                            5. связи между ресурсами удобно выражать в виде uri (subject может содержать uri страны, а может и uri юридического или физического лица)
                                              0
                                              А можно пару уточнений по тезисам?

                                              Касательно первого. Допустим, в базе есть сущности Entity и Item, такие что Entity has many Items. Как читать, обновлять и удалять Item из набора Entity.Items почти понятно, а как добавить Item в Entity? Я лично делаю POST /entities/$id/items/?

                                              Касательно четвертого. Если не DELETE, то как удалять /items/id?
                                              Заранее спасибо.
                                                0
                                                Как без DELETE удалять я вам не отвечу. Хотя предположу, что имелось ввиду что удалять записи вообще не нужно. Можно менять их статус на «удаленный» методом PUT. Хотя как мне кажется совсем отказываться от удаления не всегда правильно. Да и при правильно спроектированной БД ссылочная целостность не должна быть нарушена.

                                                > Допустим в базе есть сущность Entity и item…

                                                Тут все просто. Раз связь has-many, значит в item есть некий внешний ключ entity_id, указывающий на entity. В этом случае entity_id не отличается от любого другого поля item. И если для фильтрации вы используете GET параметры, то не вижу причин для этого поля придумывать иную семантику:

                                                GET /items?entity_id=$id
                                                POST /items
                                                entity_id=$id

                                                etc

                                                  0
                                                  Можно делать пост на /items/ с указанием в теле entityId.
                                                  Можно сначала сделать пост на /items/ для создания, а потом PATCH на /entities/$id/ для привязки (больше подходит для many2many связей).

                                                  В том-то и дело, что не надо удалять. Иначе все ресурсы (в том числе и внешние), которые ссылаются на данный будут содержать битую ссылку, что ведёт к слабопредсказуемым последствиям. Кроме того, по ошибке удалённый ресурс вы уже не сможете восстановить. Лучше всего удалять не сам ресурс, а ссылки на него или помечать ресурс специальным флагом. Ракета списанная в утиль или достигшая цели — не должна бесследно пропадать из базы, хоть она более и не может выполнять свои функции.
                                              +1
                                              Давайте разберемся какие у нас есть ресурсы на вашем примере:
                                              — Ракета (тут я имя ресурса оставлю без изменений но я бы разделил на средство доставки и боеголовку, это больше подходит к задаче. Но оставлю missile)
                                              — страна (country)
                                              — Запуск ракеты (смотрите на это с позиции бюрократа, назовем это дело launch)
                                              — Сделка по продаже или договор. Такие вещи уж точно стоит документировать.

                                              Дальше по списку. Действия с ракетой. Начнем с конца, обслуживание и списать в утиль — изменение статуса ресурса. Подходит метод PATCH

                                              Запуск ракеты — создаем соответствующий ресурс. Что-то типа записи в журнале что мы запустили такую-то ракету по такой-то стране. Удобно потом делать выборки по этому ресурсу и смотреть полную историю апокалипсиса. Собственно подходит POST. Любая подходящая ракета — просто не указываем в запросе какую ракету мы собирались пускать. У вас для этого есть правила бизнес логики на сервере.

                                              Если же не вводить новых ресурсов вариант vintage более чем валидный. Если вас смущает слишком частое использование метода PATCH — в этом нет ничего страшного. Мы не нарушаем принципы REST.

                                              Что до примера с twitter — friendships это связь между ресурсами. Тут чуть сложнее. По сути «удаление» связи не удаляет связь, а создает связь другого типа… или разрывает с одной стороны…

                                              Что до «никто не умеет делать rest» — любой кто возвращает в ответ на POST ответ, содержащий тело, уже нарушил REST. Есть еще вопрос прагматизма. Если вы будете загоняться по всем этим правилам, то есть вероятность что вы создадите крайне не эффективную апишку. Хотя в большинстве случаев это помогает реализовать крайне гибкую апишку.
                                                0
                                                А можете дать ссылочку где расписано почему в REST не стоит возвращать представление ресурса после исполнения POST запроса? Мне всегда казалось, что это хорошая практика возвращать ресурс + его ID (иначе откуда его узнать) и 201 Created
                                                    0
                                                    иначе откуда его узнать

                                                    в ответ на POST должен посылаться заголовок Location со ссылкой на созданный ресурс. То есть что бы забрать созданный ресурс нужно в дополнение к POST запросу сделать еще и GET. Так как это оверхэд большая часть людей на это дело забивает.
                                                      0
                                                      and a representation that describes the status of the request while referring to the new resource(s).
                                                        0
                                                        Но мне больше нравится такой вариант: генерируем уникальный идентификатор и постим сразу на него:

                                                        POST /resource={GUID}

                                                        200 OK

                                                        Ответ ложится в кэш и дальнейшие запросы используют всю мощь http кеширования:

                                                        GET /resource={GUID}

                                                        304 Not Modified
                                                        0
                                                        В приведенной выше ссылке так и не нашёл запрета на отправку ресурса ответом на POST. Про Location не написал, потому что им почти никто не пользуется, но вообще возвращать его так же стоит.
                                                      0
                                                      «Запуск» — понятие намного более широкое, чем непосредственно действие запуска.
                                                      В реальной жизни, скорее всего, это будет отдельная большая и нетривиальная сущность.
                                                      Проблема в том, что «создать запуск» != «запустить ракету». Далеко не одно и то же.
                                                      +1
                                                      Нацеливание, предпусковую подготовку и пуски осуществляет (и ведёт учёт) орудие или пусковая установка, так что объект /launch логично создавать через объект /launcher, а не /missile. Кроме того, у одной пусковой установки может быть несколько ракет, и она может вести несколько целей одновременно, и она может свободно переназначать цели между ракетами, так что логично ракеты, цели и проч подчинить установке, а не наоборот, потому что это её ресурсы. Да и вообще, боевой единицей является именно пусковая установка/комплекс, а не ракета. Ракеты — расходный материал, как патроны в автомате.

                                                      POST /launchers/{id}/targets/: { «primary»: [..,..], «secondary»: [..,..] } } — ввести целеуказание в пусковую установку
                                                      POST /launchers/{id}/missiles/: { «type»: missileType, «serialNo»: serialNumber,… } — загрузить ракету в пусковую установку.
                                                      PUT /launchers/{id}/missiles/{missileId}: { «target»: targetId} — передать целеуказание конкретной ракете
                                                      POST /launchers/{id}/launches/: { «missile»: missileId, «target»: targetId, «authorizationCode»: code,… } — инициировать пуск
                                                      DELETE /launchers/{id}/launches/{launchId} — отменить пуск
                                                      GET /launchers/{id}/launches — вывести журнал пусков данной установки
                                                      GET /launchers/{id}/launches/{launchId} — вывести детали/статус конкретного пуска

                                                        +1
                                                        Не правильно вы как-то принципы построения REST API уяснили. По моему опыту, чтобы спроектировать хороший REST API нужно сперва базу спроектировать. Далее для каждой таблицы, описывающей какую-то сущность, сделать представление в виде ресурса и с ним уже работать так, будто вы напрямую с данной таблицей работаете. Такой подход отучает мыслить действиями и процедурами и учит мыслить ресурсами.

                                                        Часто встречающийся пример: нужна «ручка» для проверки существования email в базе для формы регистрации.

                                                        Подход по типу RPC, основанный на вызове процедур (т.е.действий) подскажет вам, что нужно сделать функцию для проверки email, вроде: checkEmailExist. Что само по себе вполне вариант.

                                                        Однако ресурсный подход позволит спуститься на более прикладной уровень и понять, что нам не нужен сам «check», а нужно понять если ли ресурс (GET) и более того, нам даже данные этого ресурса не нужны (HEAD). Иными словами в REST это будет: HEAD /users?email=exp@mail.com

                                                        Если держать в голове структуру базы и ресурсный подход, то можно спроектировать REST API практически для любых ситуаций. Однако это не панацея и за возможность обобщения иногда приходится платить лишними вызовами апи, а некоторые вещи вообще на чистом REST не реализуемы.
                                                          0
                                                          Можно о нереализуемых вещах по подробнее?
                                                            0
                                                            Бывают уж очень комплексные действия, которые на самом деле должны производиться на несколькими ресурсами одновременно, либо по определённым условиям. Я такие вещи встречал на практике и не вижу ничего плохого в частичном использовании RPC подхода параллельно с REST. REST хорош для публичных API (особенно за возможность стандартизации), но внутренние «ручки» приложений бывают значительно сложнее в плане логики.
                                                              +1
                                                              Можно всё же пример?
                                                                0
                                                                Ну из совсем последнего: было 2 ресурса — заказы (orders) и подписки (subscriptions) и требование — при успешном оплате заказа и на основе его данных (оплаченный период подписки) создавать новую подписку. Итого имеем работа с двумя ресурсами в одном запросе. Итого:

                                                                PUT /orders/:id
                                                                status='success'

                                                                а внутри еще POST /subscriptions/

                                                                Возможно плохо объяснил, но надеюсь понятно.
                                                                  0
                                                                  Ну, как бы REST не запрещает одним запросом менять сразу несколько ресурсов.
                                                                    +1
                                                                    В чистом REST вы делаете запросы и действия непосредственно к ресурсу. Таким образом если делается запрос PUT /orders/:id, то появление при этом нового ресурса в subscriptions не кажется мне чисто RESTful подходом.

                                                                    Другое дело, что часто ресурс != таблица БД. Ресурсы бывают составные, но это не тот случай, ибо subscriptions это отдельный полноценный ресурс, для которого есть API для всех действий кроме его создания (POST), а создание его происходит «за сценой», что уже является отходом от чистого REST API. И в реальной практике таких отходов может быть довольно много.
                                                                      0
                                                                      И всё же, REST не накладывает ограничений на сопутствующие изменения других ресурсов. Такое ограничение выглядело бы очень странно, ведь ресурсы как минимум должны иметь двусторонние связи с другими ресурсами, а это значит, что изменение одного из них неизбежно приводит к изменению связанных.
                                                                        +1
                                                                        Извините, но я с вами не соглашусь. Иначе REST частично теряет смысл, потому как совершенно не очевидно, почему создавая один ресурс «за сценой» изменяется другой ресурс. Согласен, что на практике чистый REST реализовать как правило не получается, но не потому что это не возможно, а потому что это не слишком удобно. В чистом REST API мой пример должен бы был выглядеть примерно так:

                                                                        POST /subscriptions/
                                                                        period = :period
                                                                        product = :product_id


                                                                        PUT /orders/:id
                                                                        status='success'

                                                                        Но 2 запроса вместо одного, это уже не камильфо, поэтому так никто не делает. Каждый решает эту проблему как ему кажется более правильным. Мы для себя выработали следующий вариант — мы внедряем RPC для отдельных ресурсов (аналог метода у объекта класса). В данном случае семантика такая:

                                                                        PUT /orders/:id/success/

                                                                        где success — это некая функция, которую можно вызвать для объекта ;id из коллекции orders. В нем находится вся логика не относящаяся к базовому PUT /orders/:id. При этом базовый роут отрабатывает как обычно, далее вызывается соответствующая функция.

                                                                        Это наш метод. Нам он помогает решать сложные проблемы поверх более-менее канонического REST API, но на универсальность я конечно не претендую.
                                                                          –1
                                                                          «за сценой» изменяется другой ресурс

                                                                          Это часть вашей бизнес логики. REST — только интерфейс. Предположим что у вас при изменении одного ресурса надо что бы были изменены еще несколько (или добавлены), так же изменены связи между ресурсами и т.д. Если делать это в несколько запросов, то есть «правильно» с вашей точки зрения, то есть риск того что какой-то запрос не дойдет до сервера, и целостность данных нарушится.
                                                                            +1
                                                                            Не согласен с вами в том, что REST — это только интерфейс. К тому же он подразумевает выполнение действий над самим ресурсом. Опять же если почитать определение REST, то он подразумевает, что клиент в запросе сам сообщает все необходимое для выполнения этого запроса, а веб-служба только исполняет. Как я уже говорил, в реальности бизнес-логика сложнее, поэтому чистого RESTful API добиться не всегда получается. Мы решаем эту проблему внедрением RPC поверх REST, при этом сам REST API остается практически каноническим.

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

                                                                            0
                                                                            Давайте я приведу вам более наглядный пример.

                                                                            GET /posts/?author=me&type=topic — возвращает список постов, отфильтрованных по параметрам

                                                                            POST /posts/ — создаёт новый пост и редиректит на урл поста вида /posts/1/

                                                                            GET /posts/1/ — вернёт содержимое свежесозданного поста.

                                                                            А теперь вопрос, что должен вернуть GET /posts/?author=me&type=topic после создания нового поста?

                                                                            В соответствии с «чистым рестом», он должен возвращать пустой список, пока мы явно не изменим его новым, через PUT /posts/?author=me&type=topic

                                                                            Но это как-то глупо. Так что имеет смысл ограничиться обычным, классическим рестом.
                                                                              0
                                                                              > GET /posts/?author=me&type=topic — возвращает список постов, отфильтрованных по параметрам

                                                                              Так

                                                                              > POST /posts/ — создаёт новый пост и редиректит на урл поста вида /posts/1/

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

                                                                              > GET /posts/1/ — вернёт содержимое свежесозданного поста.

                                                                              Ок

                                                                              > А теперь вопрос, что должен вернуть GET /posts/?author=me&type=topic после создания нового поста?

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

                                                                              > В соответствии с «чистым рестом», он должен возвращать пустой список, пока мы явно не изменим его новым, через PUT /posts/?author=me&type=topic

                                                                              Совершенно не так. Не понимаю почему он должен вернуть пустой список?

                                                                              К тому же метод PUT рекомендуют применять только с уточнением конкретного ресурса (:id). Для для группового изменения списка принято использовать PATCH.
                                                                                0
                                                                                Вы уж определитесь, чем чистый рест отличается от грязного :-)
                                                                                  0
                                                                                  Вы уж поясните, в чем не моя не определенность? ;-) Вы спросили какие вещи на чистом REST не реализуемы, я вам привел пример «из жизни», который каноническому REST не соответствует (ну нельзя в REST архитектуре менять один ресурс вместе с другим «за сценой»). С чего взялся ваш «наглядный пример» я так и не понял. Сорри.
                                                                                    0
                                                                                    Вас же не затруднит привести цитату, где возбраняется одним REST ресурсам иметь сайд эффекты на другие REST ресурсы?

                                                                                      0
                                                                                      Зачем цитата, если это просто ломает весь концепт? Чтобы понять, лучше всего представлять ресурс в виде таблицы БД, а чтобы еще проще, лучше в виде файла. Есть файл-ресурс:

                                                                                      /posts.json

                                                                                      и есть список операций над этим файлом (HTTP Methods), фактически близкий к CRUD. Важно понимать что при REST запросе действия производятся над самим ресурсом и только над ним.

                                                                                      Это же понятно и описания концепции: «каждый запрос (REST-запрос) клиента к серверу содержит в себе исчерпывающую информацию о желаемом ответе сервера (желаемом репрезентативном состоянии), и сервер не обязан сохранять информацию о состоянии клиента»

                                                                                      Таким образом, если я отправляю PUT запрос на изменение ресурса (файла) post.json, то не понятно откуда сервер вообще возьмет информацию о том, что нужно изменить или создать какой-то иной ресурс.
                                                                                        0
                                                                                        Цитата нужна, чтобы не выдавать свои фантазии за действительность.

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

                                                                                        Представьте себе такую ситуацию:
                                                                                        1. Клиент Алиса меняет ресурс «профиль»
                                                                                        2. Клиент Боб слушает ресурс «профиль» и, когда тот меняется, меняет ресурс «журнал изменений».
                                                                                        3. С точки зрения Алисы изменение ресурса «профиль» приводит к побочному действию — добавлению записи в «журнал изменений».
                                                                                        4. Чтобы следить за изменениями «профиля» в реальном времени, Боб пересаживается из-за своего домашнего компьютера, за серверную консоль и благодаря отладчику имеет возможность отслеживать все изменения профиля, не упустив ни одного.
                                                                                        5. Чтобы не заниматься унылой однообразной работой, Боб пишет скрипт, который делает всё это за него. Теперь сервер автоматически логирует изменения профиля в журнале, а Боб лишь следит за правильностью работы.
                                                                                        6. Алиса по прежнему не знает о существовании Боба и с её точки зрения изменение ресурса «профиль» по прежнему приводит к побочному действию — добавлению записи в «журнал изменений».

                                                                                        Надеюсь теперь стало понятней, что требование «отсутствия побочных действий» не имеет никакого смысла и поэтому его и нет в соответствующей диссертации Роя Филдинга.
                                                                                          0
                                                                                          > Цитата нужна, чтобы не выдавать свои фантазии за действительность.

                                                                                          Чью именно цитату вам нужно привести? Ниже я скинул вам ссылку на чуваков, которые считаются профи в RESTful-сервисах. Но ведь они тоже могут фантизировать? Могу скинуть еще тонну ссылок кто и как делает REST API.

                                                                                          > Представьте себе такую ситуацию…

                                                                                          Не понимаю как данная ситуация вообще относится к паттерну REST. Так что понятнее не стало.

                                                                                          > Клиент Боб слушает ресурс «профиль» и, когда тот меняется, меняет ресурс «журнал изменений»

                                                                                          Что за значит «случает»? REST — не событийная модель, поэтому там нет «слушателей».

                                                                                          > 3. С точки зрения Алисы изменение ресурса «профиль» приводит к побочному действию — добавлению записи в «журнал изменений».

                                                                                          Такое возможно, если поверх RESTful-сервиса мы имеем несколько «толстых» клиентов, которые реализуют какую-то свою специфичную логику и используются stateless REST API для хранения состояния совместных ресурсов. Такое возможно, но конкретно к REST это отношения не имеет.

                                                                                          Чтобы закрепить: RESTful подход не является «событийным» и никто никого не «слушает».
                                                                                          И еще раз: RESTful подход подразумевает работу непосредственно над ресурсом и чтобы понять, почему не нужно «побочных действий» и т.п. представляйте что ресурс — это файл.

                                                                                          Опять же сама архитектура как раз и подразумевает масштабируемость за счет stateless подхода и отсутствия сайт-эффектов.

                                                                                0
                                                                                GET /posts/?author=me&type=topic
                                                                                — это метод
                                                                                GET /posts/
                                                                                которому передали два параметра (а то, что они uri-encoded и в него вписаны — вообще отдельная тема),
                                                                                перед этим мы сделали
                                                                                POST /posts/
                                                                                (модифицировали список), следовательно список должен обновиться.
                                                                                  0
                                                                                  GET — это метод
                                                                                  /posts/?author=me&type=topic — это идентификатор ресурса

                                                                                  Если вас смутил знак вопроса, то можете заменить его на:
                                                                                  /posts/author=me/type=topic/
                                                                                  Или даже на совсем «традиционное»:
                                                                                  /posts/author/me/type/topic/
                                                                                  Суть от этого не изменится.

                                                                                  А чтобы уж совсем добить, то предлагаю посмотреть на следующий ресурс, который уже совсем ничем не связан с /posts/:

                                                                                  /search/author/me/query=REST/

                                                                                  Который возвращает и посты и теги и разделы и коментарии и картинки удовлетворяющие заданным условиям.
                                                                                    0
                                                                                    /search/author/me/query/REST/ конечно же
                                                                                      0
                                                                                      Извините, но ваше представление о REST API весьма далеко от общепринятого. Вот ребята довольно не плохо рассказывают о том, как строить RESTful API: apigee.com/about/resources/webcasts/restful-api-design-second-edition

                                                                                      > Или даже на совсем «традиционное»:
                                                                                      /posts/author/me/type/topic/
                                                                                      Суть от этого не изменится.

                                                                                      Это прям совсем не «традиционное». Структура URI в REST API никогда не описывается никакую иерархию. Все это от лукавого.

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

                                                                                      > /search/author/me/query=REST/

                                                                                      Который возвращает и посты и теги и разделы и коментарии и картинки удовлетворяющие заданным условиям.

                                                                                      Это прям мега-злище и к RESTful API не имеет никакого отношения.
                                                                                        +1
                                                                                        1. Вы лучше не хипстерские подкасты слушайте, а читайте первоисточники. В статье на википедии есть все необходимые ссылки.

                                                                                        2. REST не накладывает никаких ограничений на формат урлов — он может быть совершенно любым.

                                                                                        3. Не существует никаких GET-параметров. Есть часть URI именуемая query. Она может быть в формате application/x-www-form-urlencoded, а может быть и в любом другом. query — такая же полноправная часть идентификатора ресурса как и path и authority.

                                                                                        4. Ресурс с результатами поиска — такой же полноценный ресурс, как и любой другой.
                                                                                          0
                                                                                          1. Первоисточники описывают концепт в целом, но совершенно не говорит как делать на практике. Именно поэтому существует столько «типа REStful сервисов». Однако одна из основных концепций REST это все же стандартизация и поэтому необходим общепринятый подход к созданию REST API. Так что не вижу ничего плохого в том, чтобы смотреть кто и как делает.

                                                                                          2. А причем тут формат урлов? Дело не в формате, а в выстраивании иерархии, которая не предусмотрена в REST. Вы же засунули параметры запроса (query) в иерархическую часть (path), что в корне не верно даже просто с точки зрения URI.

                                                                                          3. Опять передергиваете. Разговор становится скучным.

                                                                                          4. В REST API результаты поиска (как и любой другой фильтр) — это не сам ресурс, а выборка по коллекции ресурса. Выборка это как «срез» или подмножество, таким образом вполне вписывается в концепт URI.
                                                                                            +1
                                                                                            1. «В статье на википедии» — вообще отличное начало доказательства.

                                                                                            3. REST — он не просто так, он поверх HTTP, а значит и способы передачи параметров совпадают:
                                                                                            в качестве query params, либо включение в тело запроса (вообще говоря — равнозначные способы). При этом и то и другое — параметры запроса, а не его URI

                                                                                            4. Ресурс с результатами поиска — метаресурс, базирующийся на исходном, если иное не сказано прямо (например через другой URI).
                                                                            0
                                                                            Если у вас правилами бизнес логики прописано что после обновления статуса заказа на определенный нужно создавать подписку — второй запрос и не нужен. Это вообще слабо относится к REST.
                                                                      0
                                                                      А минусующие могут как-то развернуто высказать мнение? Если я где-то не прав, то толку просто ставить минус? Поправьте.
                                                                      +1
                                                                      А почему запуск не
                                                                      POST /missile-launches/{id}
                                                                      

                                                                      Ведь это тоже ресурс. Вы вполне можете захотеть получить уже запущенные ракеты (GET), отменить запуск (DELETE), сменить страну (PUT).
                                                                        +1
                                                                        У меня ощущение, что REST, это идея утопия, как пресловутая семантичность HTML. Идея вроде здравая, но на практике усложняет проектирование более значительно, чем дает профита. В итоге всем плевать, и API проектируют просто как удобнее и быстрее. Поэтому найти непротиворечащий канонам REST на этой планете не проще, чем семантичный HTML.

                                                                        За статью и комментарии спасибо, я, как и многие, не раз ломал голову над этой проблемой.
                                                                          –1
                                                                          Я вот не пойму, что автору важнее — решение его задачи или соблюдение каких-то там абстрактных паттернов проектирования? Если конкретная задача не впихивается в рамки классического REST, зачем уминать ее в это прокрустово ложе насильно? Нужно делать так, как будет логично, понятно и удобно, а не применять сомнительные трюки, лишь бы только теории соответствовать.
                                                                            0
                                                                            Теория всё же не на пустом месте родилась. А «мне так удобно» — сомнительный довод при разработке архитектуры.
                                                                              0
                                                                              REST это не закон, это лишь паттерн. Один из.
                                                                              А главный довод — здравый смысл.
                                                                                0
                                                                                Ох уж этот сферический здравый смысл. Приведите пример, что ли, когда вместо REST по вашему мнению стоит использовать «другой паттерн».
                                                                                  0
                                                                                  Так вся статья выше про такой пример.
                                                                                      +1
                                                                                      Выкрутиться всегда можно. Но прозрачно это не выглядит.
                                                                                      Это как раз искусственная подгонка задачи под не очень подходящую идеологию.
                                                                                        +1
                                                                                        — у меня есть ресурс
                                                                                        — я могу посмотреть его состояние
                                                                                        — я могу поискать ресурсы с нужным мне состоянием
                                                                                        — у нужных ресурсов я эти состояния могу изменять
                                                                                        — я могу не помнить какие действия с ресурсом я совершал, но в любой момент могу получить актуальное состояние всех интересующих меня ресурсов.

                                                                                        Что именно не выглядит прозрачно?

                                                                                        Почему для оперирования ракетой я должен разучивать 100500 глаголов (активировать, деактивировать, запустить, навести на цель, изменить цель, отменить запуск, взорвать в воздухе), если состояние ракеты в любой момент времени описывается сравнительно небольшим числом параметров (координаты, цель, состояние)?
                                                                                    0
                                                                                    REST не подойдёт для большинства real-time приложений с постоянным соединением, да и вообще всего, что не работает поверх HTTP. Все же основа REST и HTTP это stateless и сервисный подход. Для более сложных и «тесных» видов коммуникации это не лучший вариант.

                                                                                    К примеру, мы сейчас работает над проектом Умного дома. Общение между желёзками исключительно на сокетах по протобафу. REST API будет только для паблика.
                                                                                      0
                                                                                      И зачем вам целых 2 апи? REST замечательно заворачивается в WebSockets.
                                                                                        +1
                                                                                        REST по-умолчанию stateless, а то, что делаем мы stateful. Не понимаю, что во что должно заворачиваться. И почему вы ставите в один ряд транспорт (websockets) и паттерн (rest)…
                                                                                          0
                                                                                          REST не по умолчанию, а по определению stateless.
                                                                                            +2
                                                                                            Не более чем передергивание и игра слов. В таком случае WebSocket'ы это по определению stateful и я не понимаю как вы туда собрались REST заворачивать.
                                                                                              0
                                                                                              Что же в WS такого stateful кроме сессии? HTTP вон идет по TCP который тоже имеет понятие сессии и очень даже stateful. Почитайте про COAP например. Паттерн REST не обязывает использовать HTTP.

                                                                                              Не понимаю почему все привязались к REST over HTTP при этом, что такое REST забыли. Меня конечно бесит когда я делаю `POST /something/:id/delete' вместо 'DELETE /something/:id'.

                                                                                              Сферический REST в вакуме это вообще утопия вносит больше проблем чем пряников. Лучше уж json/msgpack гонять по Web Socket'ам. Потому, что в 99% случаев API требует совершение действий над объектом, в большом кол-ве случаев действия не идемпотентное. Нельзя дважды запустить одну и туже ракету, а REST требует, чтобы можно было. Иначе надо городить еще одну сущность — запуск. Однако теневое изменение другой сущности тоже нельзя. Считаю, что настоящий REST побходит только для прямого-CRUD.
                                                                                                +1
                                                                                                REST ничего подобного не требует. REST требует, чтобы повторение идемпотентного запроса приводило к тому же состоянию (не действию), что и один такой запрос. А вот в случае RPC, при повторении запроса, у вас действительно будет попытка второй раз запустить одну и ту же ракету с соответствующими непредсказуемыми последствиями.

                                                                                                Принцип идемпотентности введён не просто так. Иногда так случается, что ответ от сервера не приходит. По самым разным причинам. У клиента, в случае неидемпотентности, есть несколько вариантов стратегий в этом случае:

                                                                                                1. Забить болт. Клиент так и не узнает запустил сервер ракету или нет. Плохое решение.
                                                                                                2. Повторить запрос. Если сервер таки запустил ракету, но просто ответ от него не дошёл до клиента — будет попытка запустить ракету снова с мало предсказуемыми последствиями (взрыв этой ракеты, или запуск ещё одной).
                                                                                                3. Запросить состояние ракеты, проверить, что ракета ещё не запущена и повторить запрос. При этом нужно держать пальцы скрещенными, чтобы никто другой не запустил ракету раньше нас, иначе получится ситуация (2).

                                                                                                В случае идемпотентного запроса — мы его можем смело повторять и не бояться, что другой клиент выполнит его раньше нас. По этой причине нужно стараться все запросы делать идемпотентными (да-да, даже POST запросы).
                                                                                                  0
                                                                                                  > Иначе надо городить еще одну сущность — запуск

                                                                                                  В БД это и будет сущность — запись в таблице запусков, связывающая ракету, цели, пусковую установку, время, приказ, исполнителей и проч. Запуск — это ACID-транзакция, и у этой транзакции есть _идентификатор_, и если она длительная, то ею можно манипулировать (отменить, например), т.е. по всем статьям это самая что ни на есть настоящая информационная сущность.

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

                                                                                                  Нет, не требует. Первая попытка запуска вернёт 201 Created, все последующие попытки пуска с теми же параметрами ничего не поменяют, а просто вернут 200 OK. В стандарте указано, что метода POST небезопасный, но нет запрета сделать его идемпотентным, если требуется.

                                                                                                  > Однако теневое изменение другой сущности тоже нельзя.

                                                                                                  Нет такого запрета. После осуществления запуска у ракеты изменится статус, у пусковой установки изменится число ракет, у цели появится отметка о захвате чтобы другие ракеты туда не пускать и т.п. Делать все эти изменения кучей вызовов REST и пытаться имитировать ими транзакционную БД — ничем не обоснованное и вредное маньячество. Все перечисленные сущности могут поменяться по тысяче других причин, и нет никакого резона предполагать, что твои действия с одним ресурсом имеют сугубо локальный эффект.
                                                                                                    0
                                                                                                    > Что же в WS такого stateful кроме сессии?

                                                                                                    Т.е. наличие сессии и постоянного соединения для вас не является stateful? Может тогда расскажете, когда можно считать что наступил stateful? Ну а REST по умолчанию stateless и не должен иметь «клиентской сессии».

                                                                                                    > HTTP вон идет по TCP который тоже имеет понятие сессии и очень даже stateful.

                                                                                                    WebSocket'ы тоже работают поверх TCP. Не понимаю причем тут TCP вообще.

                                                                                                    > Не понимаю почему все привязались к REST over HTTP при этом, что такое REST забыли.

                                                                                                    REST — это паттерн описывающий архитектуру WWW, а веб строиться на основе HTTP. Фактически HTTP построен по паттерну REST. Ну может еще по этому:

                                                                                                    «The REST architectural style was developed by W3C Technical Architecture Group (TAG) in parallel with HTTP 1.1, based on the existing design of HTTP 1.0.[4] The World Wide Web represents the largest implementation of a system conforming to the REST architectural style.»

                                                                                                    > Сферический REST в вакуме это вообще утопия вносит больше проблем чем пряников. Лучше уж json/msgpack гонять по Web Socket'ам.

                                                                                                    Может вы просто не умеете его готовить? Или не совсем понимаете к каком случае имеет смысл делать REST, а в каком нет? В нашем проекте Умного дома REST API для M2M связей был бы убийством. Поэтому мы используем сокеты (внимание не websocket'ы) и protobuf. Каждому инструменту свое применение. И то, что вы называете «лучше» тоже должно проходит проверку задачей.

                                                                                                      +1
                                                                                                      > Т.е. наличие сессии и постоянного соединения для вас не является stateful? Может тогда расскажете, когда можно считать что наступил stateful? Ну а REST по умолчанию stateless и не должен иметь «клиентской сессии».

                                                                                                      TCP с Keep-Alive вас не смущает? TLS сессия тоже не смущает? А то, что HTTP/2 еще более stateful чем WS вас не смущает тоже? Одная сессия поверх другой сессии, через другую сессию. Но почему-то элементарный WS handshake у вас сразу когнитивный диссонанс вызывает.

                                                                                                      > Может вы просто не умеете его готовить? Или не совсем понимаете к каком случае имеет смысл делать REST, а в каком нет?

                                                                                                      Но сами же сказали, что HTTP это REST. Весь прямо HTTP REST? А то, что HTTP/2 еще более stateful чем WS вас не смущает тоже?

                                                                                                      > В нашем проекте Умного дома REST API для M2M связей был бы убийством.

                                                                                                      Для умного дома уже изобрели CoAP. Который кстати вполне может иметь HTTP REST-мост. Причем тут M2M когда мы говорим о Nuclear Strike as a Service с REST API. Вот допустим S3 вполне логично имет RESTful API, а вот EC2 уже нет. Бложику REST пойдет.

                                                                                                      Пример: restful api для виртуальных машин. Как перезагрузку одной машины сделать? А перевести машину на другой хост?
                                                                                                        0
                                                                                                        HTTP/2 — это дань времени и правильно, что они идут по этому пути, но REST это все же о HTTP 1.1. Более того, сейчас веб все больше и больше отходит от REST и stateless и все больше становится stateful. Хорошо это или плохо не знаю, но веб-сервисы становятся все более динамичными, что не может не радовать. Опять же причем тут REST я не понимаю.

                                                                                                        > Для умного дома уже изобрели CoAP.

                                                                                                        Для IoT уже много чего придумано и не только CoAP, но причем тут REST?

                                                                                                        > Причем тут M2M когда мы говорим о Nuclear Strike as a Service с REST API.

                                                                                                        Это был мой ответ на вопрос уважаемого vintage: «Приведите пример, что ли, когда вместо REST по вашему мнению стоит использовать «другой паттерн».»

                                                                                                        Собственно вы также а него ответили. Посмотрите тред комментариев, может поймете мою мысль.

                                                                                                        > А перевести машину на другой хост?

                                                                                                        PUT /vms/:id
                                                                                                        hostname=:new_host

                                                                                                        > Как перезагрузку одной машины сделать?

                                                                                                        т.к. перезагрузка это не совсем изменение состояния ресурса, а точнее временное изменение состояния ресурса, то можно сделать на чистом REST с помощью 2-х запросов:

                                                                                                        PUT /vms/:id
                                                                                                        status='off'

                                                                                                        PUT /vms/:id
                                                                                                        status='on'

                                                                                                        но мы бы так делать не стали. Как я уже писал где-то выше, мы вполне возволяем себе накручивать RCP поверх чистого REST. При этом REST занимается только хранением и изменением состояния ресурсов и CRUD, а RPC сложными действиями над ресурсом. Поэтому у нас мы это выглядело так:

                                                                                                        PUT /vms/:id/restart/

                                                                                                        Кстати, вы так и не сказали, что по вашему есть stateful?

                                                                                  0
                                                                                  Добрый день[ и с праздником]! Вы могли бы подсказать мне, как лучше и понятнее объяснить студентам разницу между RPC и REST?
                                                                                  Я так понимаю, что Филдинг, один из разработчиков HTTP, ввел термин REST как обобщение практики, уже реализованной в до него изобретенном и работающем вебе. Практики, которую сегодня ярко воплощают PHP-сценарии, т.е. когда новый запрос/реакция серверной части ничего не знает о предыдущем состоянии дел и все начинается с нуля. Это верно?

                                                                                  В то же время приложения, сделанные на RoR, Python, Node включают в себя цикл обработки запросов, фактически постоянно вися в памяти серверного компьютера и включая в себя «веб-сервер» как компонент — и у них есть полная возможность хранить между запросами что угодно. Как это соотносится с REST?

                                                                                  Есть ли какая-либо четкая корреляция между REST/RPC и серверными технологиями? Или REST/RPC — это чисто семантика обращения к удаленному исполнителю совершенно абстрагированная от того, что и как происходит на той стороне?

                                                                                  Извините за вот это все, но я был бы очень благодарен за ответ.
                                                                                    0
                                                                                    Да, это просто семантика.

                                                                                    REST предполагает ограниченный набор стандартных глаголов, которыми можно оперировать с неограниченным числом существительных. Например, ВЗЯТЬ можно хоть ручку, хоть карандаш, хоть чашку, хоть почти любой предмет.

                                                                                    RPC предполагает неограниченный набор глаголов, каждому из которых нужны свои существительные. Например, глагол ПОЛОЖИТЬ_НА требует два существительных: что ложить и на что ложить. А вот глагол ПОМЕСТИТЬ_МЕЖДУ требует уже от 3 существительных: что помещать и не менее двух предметов между которыми есть пространство.
                                                                                      0
                                                                                      Спасибо!
                                                                                      0
                                                                                      REST не вникает в детали реализации сервера, но подразумевает, что сервер работает по модели запрос-ответ, иначе идея REST частично теряет смысл. Ибо кроме всего прочего сервисный подход REST подразумевает наиболее легкую масштабируемость серверов приложений за счёт stateless подхода (достаточно иметь балансер и можно тиражировать сервера с логикой).

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

                                                                                      Для меня отличие между REST и RPC простое: первый оперирует сущностями (ресурсами), второй — действиями.
                                                                                        0
                                                                                        Спасибо!
                                                                                      +1
                                                                                      Мне кажется или автор под видом REST хочет продать то, что называется JSON RPC?
                                                                                        0
                                                                                        Поясните Вашу мысль? Ведь JSON-RPC — протокол, а не методология именований ресурсов и их методов.
                                                                                          0
                                                                                          REST предполагает передачу непосредственно состояния ресурса (это ещё и в названии есть). А автор поста вызывает удалённо метод (собственно RPC), при этом зачем-то пытаясь уже не-рестовскому эндпоинту дать какбы рестовский урл. Сам по себе REST не предполагает никаких других операций кроме CRUD на ресурсах, то есть он очень хорошо подходит для клиент-серверных систем с «толстым» клиентом, который производит сложную логику, а сервер лишь хранит состояние ресурсов.
                                                                                        +1
                                                                                        Насчет JSON-RPC скорее не соглашусь, но вот ваше понимание REST мне близко.

                                                                                        > Сам по себе REST не предполагает никаких других операций кроме CRUD на ресурсах, то есть он очень хорошо подходит для клиент-серверных систем с «толстым» клиентом, который производит сложную логику, а сервер лишь хранит состояние ресурсов.

                                                                                        Как с языка сняли. Все так. К сожалению реализовать чистый REST на практике сложно, либо не удобно.

                                                                                        Only users with full accounts can post comments. Log in, please.