Pull to refresh
4
0

Неизвестный агрессивный человек

Send message
На "существительное" можно "указать пальцем". Ему можно что-то отправить (POST). Про него можно нормально говорить и писать человеческим языком без оказий, когда в качестве подлежащего или дополнения в предложении придётся использовать "глагол" (отправить число в купить). "Существительное" описывает то, что выполняет действия.

Это уже пространные философские измышления


Для меня использование глаголов в идентификаторах ресурсов — это натягивание совы, т.к. можно замечательно обойтись без них. И иметь в результате достаточно строгую систему с чёткими правилами. Правила, в которых есть понимание где "сущность" (ресурс), а где "действие" выполняемое с ним (HTTP-метод).

Для меня натягивание бизнес-логики на CRUD-шаблон тоже выглядит как натягивание совы на глобус, особенно там, где такое натягивание ошибочно списывается на требования якобы HTTP или REST.


То, что вы делаете, называется Resource Oriented Architecture (https://google.aip.dev/121), но даже там Google вполне допускает использование т. н. "custom methods" (https://google.aip.dev/136) — действий, адресуемых URI и выполняемых через POST или GET. Такой дизайн API имеет все права на жизнь, но мы должны понимать, что это все еще ориентированный на данные RPC. Его использование никак не вытекает из REST или HTTP. Вдобавок Филдинг говорил, что "ROA is not REST. ROA is supposed to be a kind of design method for RESTful services, apparently, but most folks who use the term are talking about REST without the hypertext constraint. In other words, not RESTful at all."

Всё что там написано, по моему, означает что "ресурс" — это не обязательно что-то "реальное" (типа документа или файла). "Ресурс" может быть эфемерной сущностью, например "удалятор". Но есть важная характеристика — это всё таки "сущность", а не "действие" (глагол).

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


Our use of the terms "identify" and "identifying" refer to this purpose of distinguishing one resource from all other resources, regardless of how that purpose is accomplished (e.g., by name, address, or context). These terms should not be mistaken as an assumption that an identifier defines or embodies the identity of what is referenced, though that may be the case for some identifiers


Familiar examples include an electronic document, an image, a source of information with a consistent purpose (e.g., "today's weather report for Los Angeles"), a service (e.g., an HTTP-to-SMS gateway), and a collection of other resources. [..] Likewise, abstract concepts can be resources, such as the operators and operands of a mathematical equation, the types of a relationship


The target of an HTTP request is called a “resource”. HTTP does not limit the nature of a resource; it merely defines an interface that might be used to interact with resources.


Оператор, функцию или сервис тоже можно адресовать. Ресурсом может быть все что годно, что можно идентифицировать с помощью URI. Я не понимаю, о чем мы спорим.


Есть очень даже полезное использование ETag в условных запросах отличных от GET. Например: "частично изменить ресурс, но только если его состояние не изменилось относительно имеющегося на клиенте".

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


В моём консервативном сознании не получается идентифицировать "thing" с помощью глагола. Если какое-то действие нельзя сделать с одним ресурсом через CRUD, то я просто сделаю другой ресурс, который будет это делать.

У HTTP есть метод POST, семантика которого определяется семантикой целевого ресурса (The POST method requests that the target resource process the representation enclosed in the request according to the resource's own specific semantics). У "существительного" может быть своя собственная семантика?

Да, там есть только понятие resource identificator и пример его реализации для web-а — URL. Можно конечно "сову" натянуть так сильно, что и глаголы считать "идентификаторами" ресурсов, но я предпочитаю более строгий вариант — только существительные.

Не нужно ничего натягивать: This specification does not limit the scope of what might be a resource; rather, the term "resource" is used in a general sense for whatever might be identified by a URI. [...] This specification does not place any limits on the nature of a resource, the reasons why an application might seek to refer to a resource, or the kinds of systems that might use URIs for the sake of identifying resources. [...] Likewise, the "one" resource identified might not be singular in nature (e.g., a resource might be a named set or a mapping that varies over time).
https://greenbytes.de/tech/webdav/rfc3986.html#overview


Если HTTP API не работает в концепции "всё-есть-ресурс", то не понятно что этот ETag будет означать. Например в запросе:
POST /payments/{payment_id}/cancel
какой смысл будет иметь ETag для глагола "cancel", при выполнении условного запроса (If-Match)?

Он не будет иметь никакого смысла для cancel, и я не очень представляю, зачем вообще передавать ETag на такие запросы. ETag в основном используется для кеширования при GET-запросах, и там нейминг значения не имеет.


GET /products.search?brand=cien

HTTP/1.1 200 Ok
ETag: W/"123"

GET /products.search?brand=cien
If-None-Match: W/"123"

HTTP/1.1 304 Not Modified

Будет проблематично это сделать если обработчик HTTP-запроса это просто функция, а не какой-то стандартизированный объект ("ресурс"), у которого есть свойство etag. В таком случае придётся обработку условных запросов запихивать в каждую "функцию".

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


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

Какие именно свойства? Почему "представление" не может представлять результат обращения к ресурсу, или представлять состояние, в которое должен перейти клиент?
С точки зрения HTTP, вполне может: an abstraction is needed to represent ("take the place of") the current or desired state of that thing in our communications. That abstraction is called a representation.


Если действовать в ограничениях "только CRUD" и "URL указывает на один ресурс", то не получиться сделать так как Вы написали. Если использовать DELETE — то можно удалить только один ресурс за раз.

Разве DELETE /users/1 не удалит и корзину пользователя? Почему в вашей модели ресурс не может быть коллекцией? Почему мы не можем применить DELETE к коллекции? Можем, сработает каскадное удаление. В WebDAV, применив DELETE к директории, мы удаляем все поддерево. И не только в WebDAV. Это не противоречит ни HTTP, ни CRUD, ни REST.


In effect, this method is similar to the rm command in UNIX: it expresses a deletion operation on the URI mapping of the origin server rather than an expectation that the previously associated information be deleted.
https://greenbytes.de/tech/webdav/rfc7231.html#DELETE


Resources are not storage items (or, at least, they aren’t always equivalent to some storage item on the back-end). […] Likewise, a single resource can be the equivalent of a database stored procedure, with the power to abstract state changes over any number of storage items.
https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-743


Наверное потому, что большинство языков программирования предоставляют только один удобный способ что-то делать — функции. Вызывать функции в них понятнее и привычнее, чем менять состояние "объекта" как-то по другому. Хотя в языках где есть "property" c возможностью написать для него "setter", можно было бы попробовать изобразить "REST-в-бутылке", но думаю это будет смотреться дико и чужеродно.

ORM так и работают. Нет технических препятствий чтобы это сделать.


И это будет правильное решение, если не считать вариант "пускай клиент удаляет по одному файлу". В моём случае этот решение было найдено благодаря тому, что я стал следовать жестким ограничениям, которые в "интернетах" принято считать свойствами "REST-API".

Выполнение тяжелых задач в отдельном спулере это самоочевидное решение, для этого не обязательно читать мусорные посты о CRUD в интернетах. Когда создается заказ или меняется его статус, мы должны отправить пользователю email с pdf, и этот процесс может занять 3 — 10 секунд зависимо от размера заказа и настроения нашего email-провайдера. Других решений, кроме как создавать джобу в очереди, возникнуть не может.

Чувствую, на ваш комментарий я разрожусь лонгридом.


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

Хватает простого "языка" для замены значений указанных в JSON полей, не трогая тех, которые не указаны. Но насколько знаю где-то есть описание "языка" для PATCH запросов, который позволяет в том числе делать и инкременты.

Может, лучше просто взять GraphQL?


Под правильными, я имел ввиду общие принципы ведения бухгалтерии и работы с деньгами и счетами, а не какой-то конкретный API. В таких системах обычно не бывает простого "списать_деньги()".

https://developer.payoneer.com/docs/OpenAPI/APIDoc/api/payments/withdraw
https://developer.wepay.com/api/api-calls/checkout
https://www.liqpay.ua/documentation/api/p2p_credit/doc


Полностью с Вами согласен по этому поводу. Я писал исключительно про то как это описано в первоисточнике.

Так в первоисточнике нет ни слова о CRUD или нейминге URI, который не имеет никакого значения для архитектурного стиля.


Самое интересное то, что первоисточники строго противоречат вашим словам, вплоть до наоборот.

«There is no such thing as a REST endpoint. There are resources. A countably infinite set of resources bound only by restrictions on URL length. A client can POST to a REST service to create a resource that is a GraphQL query, and then GET that resource with all benefits of REST…»
Roy Fielding


«You won't find a constraint about "nouns" anywhere in my dissertation. It talks about resources, as in resources, because that is what we want from a distributed hypermedia system (the ability to reuse those information sources through the provision of links). Services that are merely end-points are allowed within that model, but they aren't very interesting because they only amount to one resource.»
Roy Fielding (RESTful representation of nouns?)


«A REST API must not define fixed resource names or hierarchies (an obvious coupling of client and server). Servers must have the freedom to control their own namespace. Instead, allow servers to instruct clients on how to construct appropriate URIs, such as is done in HTML forms and URI templates, by defining those instructions within media types and link relations. [Failure here implies that clients are assuming a resource structure due to out-of band information, such as a domain-specific standard, which is the data-oriented equivalent to RPC’s functional coupling]»
Roy Fielding (REST APIs must be hypertext-driven)


«At no time whatsoever do the server or client software need to know or understand the meaning of a URI — they merely act as a conduit through which the creator of a resource (a human naming authority) can associate representations with the semantics identified by the URI.»
Roy Fielding (Evaluation)


«You should not be building clients that are dependent on the resource naming structure. There is simply no need to do so — the hypertext sends the client directly to the desired application state.»
Roy Fielding (REST APIs must be hypertext-driven)


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

Вы не сделали вариативность меньше, вы спрятали вариативность в тело запроса. Интересно, как именно это облегчает мониторинг? Рейты RPS тоже удобнее делать? :)


Я был в своё время с "другой стороны" — писал недо-REST, с глаголами в URL-ах. С уникальными методами, которые делались в соответствии с "предметной областью", а не с тем как это на самом деле реализовано изнутри.

И стали писать недо-REST без глаголов в URL? :) Кстати, почему HTTP API, даже будучи реализованным в терминах CRUD, часто спрятан внутри языкового SDK который вместо CRUD-кишок раскрывает высокоуровневые вызовы?
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/pinpoint.html
https://elasticsearch-py.readthedocs.io/en/v7.13.0/api.html


И в итоге я пошёл в сторону "REST-просветления" и "ограничения свободы". Стало гораздо лучше.

Я ничего не понял.


Сходу получилось реализовать генератор документации, все API устроены похожим образом и легко добавлять в них массово новые фичи (например поддержку заголовка ETag и условных HTTP-запросов).

ETag в ответах добавляется легко и это никак не зависит от того, делаете вы GET /products.search?q=abc или GET /products?q=abc. Аналогично с генерацией OpenAPI.


В целом наверное да, всё что угодно можно назвать RPC, т.к. фактически всё сводится именно к этому — вызову какой-то функции.

Не совсем, принципиальная разница между RPC и REST состоит в Uniform interface.


Но, согласитесь, под RPC обычно принято понимать систему с одной точкой входа (один URL). А какую "именно" функцию вызвать — передаётся через аргументы. И RPC не предполагает ни каких ограничений (ни стилевых, ни структурных). Называй функции как хочешь, передавай любые аргументы, делай внутри любую дичь.

  1. Я понятия не имею, откуда вы это берёте. RPC может иметь одну точку входа, или несколько, или по одной на каждый вызов. Это не принципиально. В вашей модели GrapрQL это RPC или нет?
  2. RPC не предполагает никаких ограничений, это вы, как программист, их предполагаете. В PATCH с вашим кастомным DML тоже легко засовывается любая дичь, включая операторы с мутациями. И как мы это валидировать-то будем? А если админ может менять статус заказа, а модератор нет?

А когда разработчик ставит себя в жёсткие рамки, ограничивает себя CRUD-ом, работает в концепции "всё есть ресурс".

Все-таки, что такое "ресурс"?


То начинают открываться новые возможности и видение проблем, которые могут случится, но про которые бы не задумался делая RPC в терминах предметной области (если будет интересно, то могу отдельно привести пример из реальной жизни про API "удалить_все_файлы()").

Не существует никакой концептуальной разницы между POST /files.drop, POST /delete_all_files и DELETE /files. На большой выборке этот вызов может заглохнуть не зависимо от того, как вы его назвали — в терминах CRUD или в терминах предметной области.


Я имел ввиду, что без документации просто догадаться, что если есть ресурс "контейнер", в который можно через POST "добавлять" другие ресурсы, то очень вероятно можно так же выполнить листинг этого контейнера, запросить один элемент из него, удалить или поменять этот элемент. Сразу ясно каким образом это может делаться. Для этого дока не нужна. А если есть HATEOAS, то даже ещё проще.

Работа с API в реальности очень далека от игрушечных примеров вроде "получить листинг товаров", но вы это и так должны знать. Документация нужна всегда.

Физически это частичное изменение имеющегося ресурса. Следовательно метод PATCH. А дальше дело за тем, что бы придумать (или найти готовый) формат представления изменений, которые надо применить к ресурсу.
PATCH /counters/{counter_id}
body: {"$inc": {"value": 2}}

Я же говорил, будете выкручиваться :) Ради того, чтобы не написать increment, мы вводим "служебное поле", которое не поле, а оператор, не являющийся частью обновляемых данных. Т. е. мы изобретаем микроязык запросов. Чтобы что?


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

Это примеры правильных систем или нет?
https://developer.twitter.com/en/docs/api-reference-index.html
https://api.slack.com/methods
https://www.flickr.com/services/api/
https://vk.com/dev/methods
https://core.telegram.org/methods
https://developers.google.com/youtube/v3/docs/
https://www.dropbox.com/developers/documentation/http/documentation


Да, у некоторых HTTP-методов более широкий смысл нежели у их эквивалента в абривиатуре CRUD.

Из семи методов только два имеют семантический CRUD эквивалент. Создать ресурс может POST, PUT и PATCH. Получить может GET и POST (привет, ElasticSearch). Обновить может POST, PUT и PATCH. Удалить может POST и DELETE.


Я не согласен с таким определением. "Базворд" REST — это не пустой звук, под ним понимаются вполне конкретные архитектурыне принципы построения распределённых клиент-серверных приложений.

Какие именно архитектурные принципы и какое к ним отношение имеет CRUD? Приставка REST в большинстве постов про API это модный пустой звук. Её используют просто ради красного словца, пересказывая выдумки в интернете по принципу "сломанного телефона". Сейчас к почти любому HTTP API приписываeтся приставка REST/RESTful даже если автор понятия не имеет об обязательности гимермедиа в REST или идемпотентности PUT. Об правилах нейминга URI и CRUD — никогда не существовавших ни в REST, ни в HTTP — в мусорных туториалах пишут в таких огромных количествах, потому что об этом легко писать.


CRUD "нарисовался", скорее всего, из методов HTTP, которые можно узко использовать именно для этого.

Я думаю что CRUD нарисовался из его ошибочного сопоставления с HTTP методами и ошибочной интерпретации термина "resource" как документа. Это породило другой миф о глаголах в URI и анти-POST кампанию в своё время. Но если копнуть чуть глубже, окажется, что ни URI, ни HTTP никак не ограничивают сферу того, что можно отнести к "ресурсам", а REST определяет ресурс как семантику, а не на значение, которое соответствует этой семантике. Собственно, на определении ресурса из REST базируются определения ресурса в URI и HTTP.
https://www.ics.uci.edu/~fielding/pubs/dissertation/evaluation.htm#sec_6_2_1
https://tools.ietf.org/html/rfc3986#section-1.1
https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-743


Ну и как я уже упоминал — любую логику можно реализовать через CRUD, и часто такое решение будет удобнее и предоставлять больше возможностей, нежели "лёгкий" путь RPC ("что вижу о том и пою").

Выше я привёл реализацию withdraw, которая через CRUD сразу же даёт нам типовой "REST API" для просмотра истории "транзакций".

  1. Почему низкоуровневый CRUD должен быть более удобным, чем API, который работает в терминах предметной области?
  2. Вы противопоставляете RPC с RPC. Пока на сервере есть набор конечных точек с собственной логикой, на которую жестко завязан клиент (например, ожидает, что по конкретной ссылке всегда возвращается объект товара), это будет взаимодействие в RPC-стиле. Не важно, CRUD или нет, это RPC. Здесь нет концептуальной разницы. Главне ограничение REST — HATEOAS — призвано разорвать эту связь, чтобы сервер сам переводил клиента в нужное состояние. На этом принципе работает весь гипертекстовый веб. Знаете, что означает REpresentaional State Transfer?

О том как работать с таким API, может без документации догадаться любой, кто имел дело с нормальным "REST API".

На примере $inc я увидел как "без документации" работать с таким API.

В так называемом "REST API" любую бизнес-логику можно выразить в терминах CRUD, и нет необходимости лепить дополнительные "глаголы". А если вы не претендуете на реализацию "REST API", и делаете вольную интерпретацию RPC — делайте везде глаголы, и вообще всегда через POST, вы и так уже выбрали свой особый путь, нечего лицемерить и пытаться следовать принципам, которые вас не устраивают.

Обычно так называемый "REST API" это и есть RPC API ориентированный на CRUD. Термин REST тут применяется просто как вирусный баззворд. Кстати, я посмотрю как мы будем выкручиваться с CRUD для вызовов вроде login, withdraw, increment, close :) Особенно с учетом того, что сами HTTP методы не соответствуют набору CRUD операций.

Аналогично начинал своё программирование на Symbian 9.2 на Nokia E51. Моим первым хелловордом в жизни был генератор красивых диапазонов юникода, упакованный в полноценное приложение через py2sis. Славные были времена)

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

Спецификация RFC 8259 не говорит, что числа — всегда числа с плавающей точкой двойной точности. Она говорит, что "This specification allows implementations to set limits on the range and precision of numbers accepted" и что диапазон IEEE754 может использоваться для лучшей кроссплатформенности. Я не встречал реализаций, где 123 парсится в 123.0, а не в целое число.
https://greenbytes.de/tech/webdav/rfc8259.html#numbers

Есть один прикол с глиняными хатами — со временем в их стенах начинают заводиться мыши, которых очень трудно вывести. Без нормального фундамента, помимо этого, грунт даёт усадку и появляются трещины в стенах.

С помощью Numba, NumPy, Cython возможно малой кровью поднять скорость вычислений до заоблачных высот. Никто не использует голый CPython для тяжелых вычислений.

RESTful — вид реализации архитектуры API, которая наилучшим образом позволяет использовать протокол HTTP. С REST нам нужно думать о приложении с точки зрения ресурсов. Определить, какие ресурсы мы хотим открыть для внешнего мира (например, tasks, customers, etc.). Используем глаголы, определенные протоколом HTTP, для выполнения CRUD операций с этими ресурсами, т.к. GET, POST, PUT, DELETE.

Я узнаю эту копипасту из блогов и статей, которые перепечатывают одно и то же друг у друга (часто — одинаковыми предложениями) не приводя ни ссылок, ни обоснований ;)

Не всё так просто.
Historically, the term has also been used as a geographical, cultural, and later religious identifier for people living in the Indian subcontinent.
https://en.wikipedia.org/wiki/Hindus

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

Мы говорим про кофе вообще или про кофе с сахаром или молоком? Недавно читал противоположную информацию, что чёрный кофе, наоборот, защищает зубы от кариеса. Уровень pH кофе примерно как у бананов.
https://naked-science.ru/article/sci/strong-coffee-cleans-teeth

Это ускорение достигли за счет расхода памяти?

В REST всегда path-часть URL это адрес какого-то ресурса (сущности).

Ресурс адресуется по всему URI (кроме fragment), включая host, path и query, а не только по его path-части. Т.е. /users/42?v=1 и /users/42?v=2могут адресовать два разных ресурса.
https://greenbytes.de/tech/webdav/rfc3986.html#query

400 — это общая ошибка бизнес-логики

400 означает синтаксически битый запрос, грубую ошибку клиента. Например, прислали XML вместо JSON или вообще сломали заголовки. Или тут что-то другое имелось ввиду?

Если почитать апологетов REST, то для кодов ошибок надо использовать HTTP статусы, а текст ошибки отдавать в теле или в специальном заголовке.

Не нужно читать апологетов, нужно читать первоисточники. REST ничего не говорит о том, каким образом использовать HTTP-коды (вообще, редкие упоминания HTTP в REST используются только в качестве примеров).
Если говорить об API, то я полностью согласен с автором. Из общих соображений ясно, что стандартный (и даже расширенный по WebDAV) набор HTTP-кодов никогда не покроет ошибки предметной области. Но вместо того, чтобы косплеить HTTP с его числовыми кодами, я бы предпочитал использовать уникальное имя ошибки. Такой подход удобно маппится на классы исключений в Python:


{
    "error": "SMSGatewayError",
    "message": "СМС-шлюз не отвечает, попробуйте позже"
}
Это понятно, но если вы не работаете с питоном постоянно, то для этого надо запустить репл и проверить, что False or 10 вернёт 10, а не True.

Такая аргументация работает в обе стороны. Если я не работаю постоянно с хаскелем (который для этого требует слома мозга), я понятия не имею что означает Bool -> Bool -> Bool.


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

Выбор невелик.

Зато получится сделать True + 10.

И то, потому что bool является подклассом int — этот компромисс сложился исторически. Хотя я не встречал ни одной ошибки, которая бы следовала из этого.


Неочевидно, чему равен результат x or y при x ~ False — y или bool(y). Не уверен, что это можно вывести из первых принципов, поэтому это надо просто запомнить.

Равен y, оператор or возвращает операнд по его истинности. К слову, о каких принципах речь?


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

Шаблоны C++ имеют чудовищную теоретическую выразительность, с их помощью можно усложнять статику до бесконечности, но вряд ли кто-то любит их читать или отлаживать.


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

Какой из них и почему?

Information

Rating
Does not participate
Registered
Activity