Как стать автором
Обновить

Комментарии 25

Архитектурно можно выделить три основных подхода: RPC, REST и протоколы синхронизации

Нельзя фронтендерам заниматься самолюбованием. Они не понимают, что изобретение велосипедов без серьёзной базы вызывает только смех.

Смешно вот что - автор серьёзно считате, что он сумел "выделить три основных подхода". Вместо одного RPC, он всё тот же RPC не увидел в своих двух оставшихся "основных подходах". То есть одно и то же, но с мелкими вариациями, автор выдаёт за кардинально различающиеся подходы.

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

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

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

Просветите нас пожалуйста насчет

действительно основных подходов

Желательно, чтобы эти подходы через HTTP работали, мы же про "клиент-серверный" API в WEB среде говорим?

MarkedText — стройный легковесный язык разметки текста (убийца MarkDown).

Tree — структурированный формат представления данных (убийца JSON и XML

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

Не удивлюсь, если однажды появится статья про более компактный язык программирования (убийцу PERL).

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

Это интересно :)

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


Критика REST несостоятельная:


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

Определяется задачей.
Вы просто заточили решение под задачу, где все объекты можно глобально идентифицировать.
В другой задаче /organizations/bifit/employees/dkozlyuk и /organizations/mpei/employees/dkozlyuk —
это разные ресурсы, даже если они об одном человеке.
А его глобально он идентифицируется как /people/dkozlyuk,
причем через Link можно передать эту ссылку.


Создание ресурса не является идемпотентным.

Это проблема выражения Create через POST, а не проблема REST.


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

Где representational state transfer, и где ссылочная челостность в хранилище данные (а где еще помечать как удаленные?).
Почему это сделано заботой клиентов?
Что мешает в классическом REST отдавать Gone / Moved Permanently + Link: rel=archive...?


Таким образом для нашего протокола хватит лишь двух HTTP-методов:
  • GET для чтения.
  • PATCH для обновления.

RFC 5789: PATCH is neither safe nor idempotent as defined by [RFC 2616], Section 9.1.
Из примеров видно, что вы бывший PUT имитируете через PATCH к другому ресурсу, видимо, подразумевая идемпотентность.


Идея пустить все через websocket — полный ужас:


  • Занимаемся не своим прикладного уровня делом.
  • Если канал один, теряем асинхронность, а если их несколько, зачем в одном канале двусторонний стриминг.

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

В другой задаче /organizations/bifit/employees/dkozlyuk и /organizations/mpei/employees/dkozlyuk —это разные ресурсы, даже если они об одном человеке.

Эти ресурсы не о людях, а о работниках:

_query
    \employee[org;person;salary]
        reply
            \employee=123
            \employee=456
    \person[name;employee]
employee
    \123
        org \org=ACME
        person \person=dkozlyuk
        salary 9000
    \456
        org \org=ECMA
        person \person=dkozlyuk
        salary 100500
person
    \person=dkozlyuk
        name \Дмитрий Козлюк
        employee
            \employee=123
            \employee=456

Где representational state transfer, и где ссылочная челостность в хранилище

Речь про ссылочную целостность всей сети, а не отдельного хранилища.

RFC 5789: PATCH is neither safe nor idempotent as defined by [RFC 2616], Section 9.1.

A PATCH request can be issued in such a way as to be idempotent, which also helps prevent bad outcomes from collisions between two PATCH requests on the same resource in a similar time frame.

Из примеров видно, что вы бывший PUT имитируете через PATCH к другому ресурсу, видимо, подразумевая идемпотентность.

PUT потребовал бы передачу всего графа. Тут же передаётся именно diff.

Если канал один, теряем асинхронность

Не теряем. Запросы посылаются независимо от прихода ответов и уведомлений.

Эти ресурсы не о людях, а о работниках

О том и речь. Альтернативные пути не зарождаются сами. Либо автор API решил, что бывает удобно так и этак, тогда выбирать не нужно + есть Link: rel=canonical. Либо были объективные причины продублировать, например, все ресурсы организации требовалось поместить под одним префиксом для какого-нибудь внешнего проксирования или контроля доступа.


Речь про ссылочную целостность всей сети, а не отдельного хранилища.

Весь подход основан на том, что у нас не ресурсы, определяемые адресами, а объекты, которые можно найти. Значит, про ссылочную целостность мы вообще не говорим, с точки зрения модели данных никаких внешних ссылок нет, есть только объекты и поисковые запросы. Прекрасно! Зачем гвоздями прибивать soft delete, который есть деталь реализации? Какие запросы будут и не будут находить объект, который я пометил удаленным?


"A PATCH request can be issued in such a way as to be idempotent" не позволяет понять про кокнертный запрос, будет ли он таковым. У вас PATCH / является идемпотентным по соглашению? Как серверу отличить повторный запрос на создание от конфликта? Это по меньшей мере пробел в спецификации. Моделировать создание как применение diff'а к пустому объекту заманчиво, но семантически спорно, даже если это можно так изобразить (как git diff).


Не теряем [асинхронность]. Запросы посылаются независимо от прихода ответов и уведомлений.

Отлично, то есть канал не один, раз запросы идет асинхронно. Тогда зачем WATCH и FORGET, если можно сделать GET с потенциально бесконечным ответом и PATCH к источнику уведомлений, чтобы их включать и выключать?

Отлично, то есть канал не один, раз запросы идет асинхронно.

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

Альтернативные пути не зарождаются сами. Либо автор API решил, что бывает удобно так и этак

Это 3 разных ресурса, доступных по 3 разным URI. Нет никакого смысла иметь несколько URI для одного ресурса.

были объективные причины продублировать, например, все ресурсы организации требовалось поместить под одним префиксом для какого-нибудь внешнего проксирования или контроля доступа

Это реализуется через разные endpoint. Впрочем, при чём тут контроль доступа или проксирование не очень понял.

У вас PATCH / является идемпотентным по соглашению?

Конечно.

Как серверу отличить повторный запрос на создание от конфликта?

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

Отлично, то есть канал не один, раз запросы идет асинхронно.

WebSocket канал один. Послал один WATCH и сидишь, спокойно получаешь данные как на текущий момент, так и при каждом их обновлении. FORGET нужен, чтобы перестать их получать.

Это 3 разных ресурса, доступных по 3 разным URI.

Да, неудачные примеры: лайки в статье очевидно один и тот же ресурс, эти очевидно разные. Возьмем удаленное сообщение: /chat/1/message/999 не существует с момента удаления, /user/2/message/999 доступно в архиве. А что я должен получить по запросу message=999?


Нет никакого смысла иметь несколько URI для одного ресурса.

/version/1.2, /version/latest


Это реализуется через разные endpoint.

Разные endpoint — это не несколько URI для одного ресурса?


Впрочем, при чём тут контроль доступа или проксирование не очень понял.

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


Все возможные сущности виртуально существуют и могут быть изменены.

На самом деле, нет, потому что для идемпотентного PATCH / вы должны особым образом обрабатывать начальное пустое состояние. Иначе замена пустоты на значение не отличается от замены значения на значение. По факту это PUT, только почему-то для него вы пожадничали оставить метод, а для подписок нет. Между тем, промежуточные узлы от PUT выиграли бы, так как о его идемпотентности они знают.


Про websocket'ы я не в курсе, под каналом понимаю синхронный канал. Вопрос был в необходимости иметь специальные методы. Впрочем, так как нет URI, а только запросы, то специального URI для управления подписками нет, и методы вполне годятся.

А что я должен получить по запросу message=999?

_query
    \message=999[chat[message];author[message]]
        reply \message=999
message
    \999
        chat \chat=1
        author \user=2
chat
    \1
        message
user
    \2
        message \message=999

Разные endpoint — это не несколько URI для одного ресурса?

В HARP сущности идентифицируются относительно endpoint.

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

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

Иначе замена пустоты на значение не отличается от замены значения на значение.

Они в модели HARP и не отличаются.

Как сервер понял, что надо формировать ответ для архива, а не для чата (тогда ответ был бы "ничего не найдено")? Ресурсы существуют независимо от ссылок, но в основе REST лежит принцип, что какой ресурс будет по ссылке, определяется при обращении (late binding). HARP это решительно отвергает.


Иначе замена пустоты на значение не отличается от замены значения на значение.

Они в модели HARP и не отличаются.

Отличаются для PATCH /, чтобы он был идемпотентным. В норме два запроса, заменяющих пустые поля объекта заполенными, применить нельзя, потому что второй обнаружит, что ожидаемых пустых полей уже нет (это ожидание может быть выражено как в diff, так и If-None-Match). Замечу, что запросы "установить значения части полей, что бы там ни было раньше" — это RPC, а не state transfer, потому что какой получится state, мы не знаем.

Как сервер понял, что надо формировать ответ для архива, а не для чата (тогда ответ был бы "ничего не найдено")?

Так тут и нет никакого архива. Чат пустой. Сообщение лежит по своему URI. При желании можно добавить в него поле deleted, но тут хватило и простого убирания из чата.

REST лежит принцип, что какой ресурс будет по ссылке, определяется при обращении (late binding). HARP это решительно отвергает.

Без понятия о чём вы, но в REST ничего похожего нет.

Отличаются для PATCH /, чтобы он был идемпотентным. 

А ничего, что по определению идемпотентности они отличаться не могут в принципе?

Замечу, что запросы "установить значения части полей, что бы там ни было раньше" — это RPC, а не state transfer, потому что какой получится state, мы не знаем.

Нет, это Last Write Wins стратегия разрешения конфликтов. И какой получится стейт мы как раз знаем. Если вам так будет проще, то это можно считать мультиплексированным PUT, гранулированный до отдельных полей. Но лучше всё же принять, что PATCH - это не "так как в гите".

Мне нравится. Насчет ключей -- я бы наоборот предпочел натуральный ключ там, где он есть. Например, country=ru, airport=svo , book=9780735619678. И по отношению RPC/REST -- RPC предполагает, что мы обращаемся к объектам с уникальным поведением, а REST -- что мы работаем с хранилищем или что есть только один тип объекта «данные» с разными полями, и поведение у него одно -- создать, изменить, удалить. Идея объектов включает в себя идею данных, так что это не разные вещи, а одно -- подмножество другого. Если делать API к системе, которая не просто хранит данные, а как-то себя ведет (атомный реактор), то без концепции вызова методов не обойтись.

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

Это да, метод в любом случае вызывается с помощью какого-то сообщения, и такое сообщение -- это тоже данные, которые мы добавляем, по сути, в журнал сообщений. Но тогда ресурсы уже не должны поддерживать PATCH, потому что данные их будут меняться не любым залетным запросом, а специально обученным методом. GET остается без ограничений. Хотя для методов можно приспособитьPOST -- посылать сообщения непосредственно ресурсу.

Если вам нужно изменить данные - просто меняйте эти данные. Отдельная задача вам тут не нужна. Исключение - когда изменения должны поддерживать аудит. Тогда на каждое изменение создаётся новая "проводка".

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

Валидировать данные надо в любом случае. И лучше делать это в одно месте, а не в десятках мутационных методов.

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

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

"Tree — структурированный формат представления данных (убийца JSON и XML"

О, как! Глупый мир не догадывался....И когда же ждать этого убивца?

"Они экранируются при использовании encodURIComponent" -> encodeURIComponent


Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории