Здравствуйте, gandjustas, Вы писали:
P>>А как ты определяешь, что параметр является именем пользователя, идентификатором, емейлом, суммой для списания со счета? G>По имени параметра. Ты предлагаешь тоже по имени это делать?
Я предлагаю вернуться и посмотреть то самое определение апи, которое ты счел несущественным. Сейчас мы просто пересказываем ровно то, что там проиллюстрировано.
P>>А correlation ты как определяешь? G>Это что?
P>>Ровно то же самое, что и с аргументом любой другой семпантики. G>Нет, ключом идемпотентности в PUT и DELETE является url
Ога. А в url будет id объекта, который ты хочешь удалить или перезаписать. То есть, явный параметр. Ты что, запросы из терминала шлёшь, набивая их руками?
P>>Правильно понимаю, то самое "соглашение высокого уровня" само себя запроектирует, закодит, зафиксит баги, протестирует, продеплоит и всё само себя? G>ни одно соглашение этого не сделает
Ужос! Это что же — ты в курсе как имплементать идемпотентность, но вопрошаешь будто бы не в курсе дел?
G>Более того, даже если сделают какой-то метод в edmx, который будет вызываться с помощью POST и будет идемпотентен, то ты об этом не узнаешь, потому что в EDMX нет этой информации.
В edmx есть аннотации, куда ты можешь класть всё что тебе надо. Главное, что бы твой генеренный код протаскивал все нужные параметры как на клиенте, так и на сервере.
G>Кроме случаев если в параметрах метода явно написан idempotencyKey, но тогда непонятно почему бы не вынести этот ключ в url и не использовать PUT.
Потому, что один клиент хочет транспорт graphql, второй — odata, третий grpc, четвертый обычный openapi.
G>Короче снова пришли к тому, что идемпотентность POST в общем случае не нужна и приседания с её реализацией не имеют смысла.
Мы снова пришли к примеру операции в odata протоколе, или grapql, или grpc. Идемпотентность нужна, а вот Put и даже http может и не быть.
Re[25]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, Pauel, Вы писали:
P>Урл нам надо сначала отрендерить
Эта фраза непонятна. P>Вот PUT в ODATA протоколе, который REST искаропки P>
P>PUT /Warehouse.svc/Orders('an-id')
P>
Всё верно. 'an-id' является частью URL.
P>Вызываем его примерно так: P>
P>svc.orders.update('an-id', modifiedOrder);
P>
P>или так: P>
P>svc.orders.update(modifiedOrder);
P>
P>Т.е. у нас явный параметр, который и будет ключом идемпотентности.
Нет. В первую очередь этот параметр — часть навигации.
В частности, мы можем сделать GET /Warehouse.svc/Orders('an-id') и получить этот ордер обратно.
То, что этот 'an-id' является ключом идемпотентности, следует не из какой-то особенной магии OData, а из спецификации HTTP.
Поэтому мы можем между клиентом и сервером воткнуть прокси, который будет, скажем, задерживать респонс случае получения невнятного ответа от апстрима, и продолжать долбить этот апстрим повторяющимися PUT-ами до получения вменяемого ответа — даже если сам клиент ничего не знает про возможности повторных запросов. P>Для POST мы этот ключ можем положить куда угодно, это непринципиально.
Принципиальна возможность как-то донести до клиента или прокси информацию о том, что изо всего букета POST является ключом идемпотентности.
P>Куда поедет ключ — дело десятое. Есл клиент генерируется по метаданным, а наружу торчит OO-интерфейс, нам это вообще по барабану, где что лежит. Вылезла пробелема с гатевеем, которй не пропускает наш запрос — поменяли атрибут, добавили @Header или @Encode и всё путём — перебилдили, и всё палит. Не надо вообще думать, что там где на самом деле лежит.
Это какая-то утопия. Покажите мне гатевей, который путём перебилдивания осознает, что какой-то там кусок квери стринга является на самом деле ключом идемпотентности, и сможет делать авто-повторы от имени тупых клиентов, как я показал выше.
P>Клиент — может. А на сервере это нужно мейнтейнить руками.
В этом смысле все подходы равноценны — можно вообще свой собственный глагол замутить, типа CREATE (чтобы не путаться с PUT, который то ли "создать", то ли "изменить").
Всё равно "всё это нужно мейнтейнить руками".
P>Например, какой то шутник решит, что раз изменений нету, то можно и ошибку бросить "already done". И тесты надо написать, что бы такой шутник убедился, что его подход невалидный.
Ну, с тем же успехом шутник может просто на всё возвращать 200 OK, не записывая ничего в базу. Какое отношение это имеет к проектированию?
P>Что касается одаты, то я запилил серверный фремворк для TypeScript поверх оdata протокола собственной разработки, так что вобщем одату понимаю. Кое что портироал на жээс из odata-olingo, кое что из дотнета, остальное допилил самостоятельно. Собственно, это мой текущий проект — кучка фремворков на разные случаи жизни. P>Идемпотентность реализуется ровно так же, как и везде.
Из ваших слов я делаю вывод, что официальные рекомендации вы не читали.
Потому как в ODATA она всё же не такая, как везде.
P>Вот например кейс такой, что нам надо проавать апи и юезр будет выбирать протокол и платить за количество фактически вызваных операций. А он возьми и выбери graphql. Разве мы ему скажем, что теперь про идемпотентность он должен забыть?
Мы с вами, похоже, говорим о разных вещах. Идемпотентность сама по себе является практически единственным способом построения нормальных распределённых приложений.
Понятно, что гольным HTTP мир не исчерпывается; можно оказаться в чистом поле какого-нибудь RPC или вообще низкоуровневого протокола типа TCP, для которого никто о таких вещах и не планировал задумываться.
Там, понятное дело, мы будем городить идемпотентность из палок и верёвок. Ровно как и дельта-енкодинг, кондишнл геты, кондишнл апдейты (они же оптимистик локинг) и прочие небезынтересные штуки.
Но в случае наличия HTTP имеет смысл как можно больше пользоваться уже разработанными спецификациями. В частности потому, что к ним более-менее готовы все существующие инструменты.
А когда вы выходите в чистое поле, то вы и биться там будете один. Можно посмотреть на богомерзкий SOAP, где пошли ровно по этому пути. Когда чтение какого-нибудь Order делается тоже через POST, а идентификатор уезжает в теле. Ну и всё — пришлось громоздить чудовищную гору всех этих полунеобязательных и слабосовместимых WS-* спецификаций, чтобы закатить солнце вручную.
Хорошо, что мы от этого ушли в сторону REST.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[26]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, Sinclair, Вы писали:
P>>Урл нам надо сначала отрендерить S>Эта фраза непонятна.
С т.з. разработчика урл составляется из разных кусков, у которых разная семантика, эти куски и есть фактически параметры.
S>Всё верно. 'an-id' является частью URL.
Это и есть явный параметр. Гарантии ничтожные — если нет других реквестов, юзеров, итд, то реквест можно повторять и ждать того же реквеста. Но нам обычно надо побольше.
S>В частности, мы можем сделать GET /Warehouse.svc/Orders('an-id') и получить этот ордер обратно.
А что это меняет? Гарантии то всё равно слабые. Лучше подкинуть хидер prefer и получить результат в выхлопе post.
P>>Для POST мы этот ключ можем положить куда угодно, это непринципиально. S>Принципиальна возможность как-то донести до клиента или прокси информацию о том, что изо всего букета POST является ключом идемпотентности.
Да, для http POST мы не сможем ничего такого сделать, т.к. прокси сверх хттп ничего не понимает. Зато мы можем сделать свой прокси для нашего сервиса, и он сможет обеспечить все, что нужно. Можно и graphql прокси точно так же примастырить, и все будет путём.
S>Это какая-то утопия. Покажите мне гатевей, который путём перебилдивания осознает, что какой-то там кусок квери стринга является на самом деле ключом идемпотентности, и сможет делать авто-повторы от имени тупых клиентов, как я показал выше.
Теоретически это круто, авто-повторы от имени сверх-тонких клиентов, но практически это лучше делать через гатевей, который понимает тот протокол, который поверх хттп. Тогда к нему можно прикрутить кучку дополнительных вещей.
P>>Например, какой то шутник решит, что раз изменений нету, то можно и ошибку бросить "already done". И тесты надо написать, что бы такой шутник убедился, что его подход невалидный. S>Ну, с тем же успехом шутник может просто на всё возвращать 200 OK, не записывая ничего в базу. Какое отношение это имеет к проектированию?
Прямое — идемпотентность нужно изначально закладывать в дизайн и тащить до тестов всех уровней, вне зависимости от того, описано чтото в спецификации хттп или нет.
P>>Идемпотентность реализуется ровно так же, как и везде. S>Из ваших слов я делаю вывод, что официальные рекомендации вы не читали. S>Потому как в ODATA она всё же не такая, как везде.
Да, мы не поддерживаем эту фичу. Собственно, быстро глянул, они предлагают для всех unsafe методов добавлять доп хидеры, один из которых и будет тем самым ключом идемпотентности.
Т.е. внагрузку к урл, будет еще один ключ. Разумная вещь.
P>>Вот например кейс такой, что нам надо проавать апи и юезр будет выбирать протокол и платить за количество фактически вызваных операций. А он возьми и выбери graphql. Разве мы ему скажем, что теперь про идемпотентность он должен забыть? S>Мы с вами, похоже, говорим о разных вещах. Идемпотентность сама по себе является практически единственным способом построения нормальных распределённых приложений.
В том то и дело, и это не зависит от транспорта. Потому имеет смысл втащить ключ идемпотентности в энвелоп в явном виде и сэкономить себе парочку седых волос. Поступай я иначе, была бы уже вся голова седая, а так у меня только клок другой в бороде седой
S>Но в случае наличия HTTP имеет смысл как можно больше пользоваться уже разработанными спецификациями. В частности потому, что к ним более-менее готовы все существующие инструменты.
Я вот шота навскидку не могу представить, какой из сервисов-посредников поддерживает repeatable requests
S>Хорошо, что мы от этого ушли в сторону REST.
Как по мне, так bff/edge-services, graphql и grpc пожрали и рест, и одату целиком. Скажем, на собесах меня перестали спрашивать по рест лет 5 назад. А про одату товарищи только знают, что этото чтото дотнетное, и ни единого вопроса не задают. Подумаешь, мелочовочка — запилить full-blown серверный процессин, фремворк поверх этого, кодогенератор и тд.
Re[26]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, gandjustas, Вы писали:
G>Нет, ключом идемпотентности в PUT и DELETE является url
Не является. Нет в HTTP (и REST) никаких ключей идемпотентности. К одному URL может быть несколько разных запросов. И каждый из этих запросов может выполняться несколько раз. Поэтому идемпотентность (в том смысле, в котором она определена в HTTP) — это свойство именно операции.
Далее. Похоже, в этой теме есть непонимание того, что такое идемпотентность. Прямо по определению (RFC 2616, 9.1.2) идемпотентность говорит, что req(req(state)) == req(state) (и как следствие req(req(req(...(req(state))...))) == req(state). Всё! В общем случае оно даже не гарантирует, что сломавшийся запрос можно безопасно повторять. Причем об этом прямым текстом написано прямо в той же части про идемпотентность. Если у нас есть несколько акторов (субъектов, систем), оперирующих над одним ресурсом, могут наблюдаться различные неприятные эффекты. Например, система А делает PUT /variables/x с содержимым "1". Система Б читает это значение, выполняет какие-то операции, делает PUT /variables/x с содержимым "2". Затем система А повторяет запрос PUT /variables/x со значением "1". В результате система Б может опять обнаружить значение 1 и повторить свои (уже неидемпотентные) действия. И все усложняется тем, что в реальной системе акторов обычно много. Инфраструктура HTTP в виде балансировщиков нагрузки и маршрутизаторов тоже может выступать в качестве акторов. Например, она может приводить к задержкам запросов, что со стороны получателя может выглядеть как изменение порядка запросов от клиента. Поэтому на практике для обеспечения безопасных повторов нужно использовать дополнительные техники:
Во многих случаях можно сделать конечный автомат без циклов. В этом случае перемещение в предыдущие состояния не допускается. Рудиментарная машина с ровно одним переходом (единственная операция — создание ресурса) является частным случаем такого автомата.
В ряде сценариев можно выделить "главную" систему. Например, корзина (basket) в интернет-магазине может уведомлять склад (warehouse), который обновляет остатки. В этом случае корзина — главная система, владеющая информацией. Она всегда знает последнее состояние и уведомляет склад только этим состоянием. В условиях возможности переупорядочивания запросов в состояние/сообщение явно должен входить индикатор порядка (версия/ревизия или время последнего изменения). Конечного автомата здесь нет, но упорядоченность переходов все равно есть.
Наше REST API является главной системой. Например, у нас внутренняя система (backoffice), в которой несколько менеджеров могут одновременно редактировать один заказ или его свойства. В этом случае обычно делается оптимистическая блокировка, которая на уровне HTTP выглядит в виде условных запросов (Conditional Requests). Обычно это If-Match (для существующих ресурсов) и X-If-Not-Exists/CREATE (для создания нового).
В самом HTTP для существующих глаголов есть два типа идемпотентности. Первый наблюдается у безопасных глаголов (GET/HEAD/OPTIONS). У них состояние ресурса не изменяется, т.е. req(state) == state. Второй тип наблюдается у глаголов PUT/DELETE. Для них выполняется семантика перезаписи. Если запрос применим в состояниях s1 и s2, то состояние после выполнения запроса одно и то же: req(s1) == req(s2). На практике для безопасного повторения запросов в системах со многими акторами хотелось бы больших гарантий. Например, req(req1(req2(...(reqN(req(state)))...))) == req1(req2(...(reqN(req(state)))...)) (т.е. если запрос был выполнен ранее, его повторение не приведет к изменению состояний). Готового глагола с такой семантикой нет. Но операции — есть. Например, движение по конечному автомату без циклов. В этом случае большинство запросов имеют семантику req(state) == max(state, newState). В этом случае повторное выполнение в любом состоянии будет безопасно.
Явный ключ идемпотентности (как в POST) в REST моделируется через команды и рудиментарный конечный автомат с ровно одной операцией (PUT с семантикой CREATE), доступной клиенту. Что-то вроде PUT /resources/xxx/operations/<unique-op-id>. Но, опять же, в данном случае требуемые свойства системы достигаются за счет того, как определен автомат. Технически ресурс может поддерживать больше операций. Например, поддерживать отмену запроса в виде глагола DELETE. И в этом случае можно создавать гонки между PUT/DELETE на одном ресурсе (с разным содержимым команд).
Re[27]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, maxkar, Вы писали:
M>Здравствуйте, gandjustas, Вы писали:
G>>Нет, ключом идемпотентности в PUT и DELETE является url M>Не является. Нет в HTTP (и REST) никаких ключей идемпотентности. К одному URL может быть несколько разных запросов. И каждый из этих запросов может выполняться несколько раз. Поэтому идемпотентность (в том смысле, в котором она определена в HTTP) — это свойство именно операции.
Что такое "операция"? Видимо речь о запросе, запрос это Метод+URL+Тело+Заголовки
Предложение оппонента сделать "идемпотентность" по полю тела или заголовка.
M>Далее. Похоже, в этой теме есть непонимание того, что такое идемпотентность. Прямо по определению (RFC 2616, 9.1.2) идемпотентность говорит, что req(req(state)) == req(state) (и как следствие req(req(req(...(req(state))...))) == req(state). Всё! В общем случае оно даже не гарантирует, что сломавшийся запрос можно безопасно повторять.
Как раз гарантирует. В случае неполучения ответа (пропадания связи) или ответа 500 можно повторять идемпотентный запрос пока не получишь другой ответ.
M>Причем об этом прямым текстом написано прямо в той же части про идемпотентность. Если у нас есть несколько акторов (субъектов, систем), оперирующих над одним ресурсом, могут наблюдаться различные неприятные эффекты. Например, система А делает PUT /variables/x с содержимым "1". Система Б читает это значение, выполняет какие-то операции, делает PUT /variables/x с содержимым "2". Затем система А повторяет запрос PUT /variables/x со значением "1". В результате система Б может опять обнаружить значение 1 и повторить свои (уже неидемпотентные) действия.
Это к идемпотентности не имеет отношения. Это состояние гонки и с ним надо бороться другими методами.
Идемпотентность позволяет повторять запросы пока ты не получил ответа совсем (связи нет) или получил 500 (ошибка на стороне сервера). При получении любого другого ответа надо прекратить повторы.
M>И все усложняется тем, что в реальной системе акторов обычно много. Инфраструктура HTTP в виде балансировщиков нагрузки и маршрутизаторов тоже может выступать в качестве акторов. Например, она может приводить к задержкам запросов, что со стороны получателя может выглядеть как изменение порядка запросов от клиента. Поэтому на практике для обеспечения безопасных повторов нужно использовать дополнительные техники: M>
M> Во многих случаях можно сделать конечный автомат без циклов. В этом случае перемещение в предыдущие состояния не допускается. Рудиментарная машина с ровно одним переходом (единственная операция — создание ресурса) является частным случаем такого автомата. M> В ряде сценариев можно выделить "главную" систему. Например, корзина (basket) в интернет-магазине может уведомлять склад (warehouse), который обновляет остатки. В этом случае корзина — главная система, владеющая информацией. Она всегда знает последнее состояние и уведомляет склад только этим состоянием. В условиях возможности переупорядочивания запросов в состояние/сообщение явно должен входить индикатор порядка (версия/ревизия или время последнего изменения). Конечного автомата здесь нет, но упорядоченность переходов все равно есть. M> Наше REST API является главной системой. Например, у нас внутренняя система (backoffice), в которой несколько менеджеров могут одновременно редактировать один заказ или его свойства. В этом случае обычно делается оптимистическая блокировка, которая на уровне HTTP выглядит в виде условных запросов (Conditional Requests). Обычно это If-Match (для существующих ресурсов) и X-If-Not-Exists/CREATE (для создания нового). M>
В общем случае только последнее имеет смысл. Только к идемпотентности не имеет отношения. Так как даже без повторения запросов можно получить эти проблемы. Кстати способ разрешения конфликтов в вебе был описан раньше REST https://www.w3.org/1999/04/Editing/
M>В самом HTTP для существующих глаголов есть два типа идемпотентности. Первый наблюдается у безопасных глаголов (GET/HEAD/OPTIONS). У них состояние ресурса не изменяется, т.е. req(state) == state. Второй тип наблюдается у глаголов PUT/DELETE. Для них выполняется семантика перезаписи.
Это называется SAFE и IDEMPOTENT
M>Явный ключ идемпотентности (как в POST) в REST моделируется через команды и рудиментарный конечный автомат с ровно одной операцией (PUT с семантикой CREATE), доступной клиенту. Что-то вроде PUT /resources/xxx/operations/<unique-op-id>. Но, опять же, в данном случае требуемые свойства системы достигаются за счет того, как определен автомат. Технически ресурс может поддерживать больше операций. Например, поддерживать отмену запроса в виде глагола DELETE. И в этом случае можно создавать гонки между PUT/DELETE на одном ресурсе (с разным содержимым команд).
Суть разговора была в том, что оппонент предлагает использовать POST и <unique-op-id> в теле или заголовке
Re[28]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, gandjustas, Вы писали:
M>>Явный ключ идемпотентности (как в POST) в REST моделируется через команды и рудиментарный конечный автомат с ровно одной операцией (PUT с семантикой CREATE), доступной клиенту. Что-то вроде PUT /resources/xxx/operations/<unique-op-id>. Но, опять же, в данном случае требуемые свойства системы достигаются за счет того, как определен автомат. Технически ресурс может поддерживать больше операций. Например, поддерживать отмену запроса в виде глагола DELETE. И в этом случае можно создавать гонки между PUT/DELETE на одном ресурсе (с разным содержимым команд). G>Суть разговора была в том, что оппонент предлагает использовать POST и <unique-op-id> в теле или заголовке
Тут Синклер скинул ссылку на спеку Repeatable Request, вобщем, это оно и есть. Просто Shmj зашел слишком издалека.
Re[27]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, Pauel, Вы писали: P>Это и есть явный параметр. Гарантии ничтожные — если нет других реквестов, юзеров, итд, то реквест можно повторять и ждать того же реквеста. Но нам обычно надо побольше.
Эти гарантии замечательны тем, что хорошо известны всем клиентам — независимо от степени их обученности нашей самоизобретённой супер-технологии. P>А что это меняет? Гарантии то всё равно слабые. Лучше подкинуть хидер prefer и получить результат в выхлопе post.
Речь не о том, что мы хотим сделать GET-после-PUT, а в некоторых мета-соглашениях об API.
Вот, к примеру, когда я вижу, что для создания нового экземпляра мне предлагается сделать POST на некий URL, у меня нет никакой информации о том, где я потом буду эту сущность запрашивать.
Ну да, обычно я могу увидеть список этих сущностей, выполнив GET на тот же URL. И тем не менее — не всегда понятно, чем именно адресуется конкретная сущность.
Никакие prefer тут не помогут.
P>Да, для http POST мы не сможем ничего такого сделать, т.к. прокси сверх хттп ничего не понимает. Зато мы можем сделать свой прокси для нашего сервиса, и он сможет обеспечить все, что нужно. Можно и graphql прокси точно так же примастырить, и все будет путём.
Вы рассуждаете в рамках модели, где и сервер и клиент пишутся вами.
Я же рассматриваю более широкий случай — когда мы пишем что-то одно. То есть либо мы пишем клиента к существующему сервису, либо пишем сервис для неопределённого круга клиентов.
У нас нет вот этой роскоши "поменять аннотацию и перекомпилировать гатевей", потому что мы вообще не контролируем вторую сторону.
Если мы вдруг решим перенести ключ идемпотентности из тела в хидер на стороне сервера, то это просто означает, что сколько-то тысяч клиентов упадут после деплоймента.
А когда мы пишем клиента, у нас возникает вопрос "а как узнать, что и как себя ведёт". Про требования идемпотентности PUT мы знаем из публичных RFC, поэтому пишем наш код соответствующим образом.
Для POST мы должны глазами читать спецификацию на естественном языке. Которая очень редко бывает исчерпывающей, кстати.
Обратите внимание, как замудрёно прикрутили идемпотентность POST к OData — там ещё и предусмотрен способ ограничить продолжительность обязанности сервера по поддержке идемпотентности.
Это придумали практики. Очень может так оказаться, что сервис, который анонсировал идемпотентность в спецификации, на самом деле способен её придерживаться не бесконечно.
P>Теоретически это круто, авто-повторы от имени сверх-тонких клиентов, но практически это лучше делать через гатевей, который понимает тот протокол, который поверх хттп. Тогда к нему можно прикрутить кучку дополнительных вещей.
Осталось понять, где мы его возьмём.
P>Прямое — идемпотентность нужно изначально закладывать в дизайн и тащить до тестов всех уровней, вне зависимости от того, описано чтото в спецификации хттп или нет.
Всё верно. Объём работы на серверной стороне никак не зависит от того, каким именно способом сделана идемпотентность. Более того — он будет таким же и для RPC-based подходов.
Потому что чудес не бывает. Если я изобрету способ делать идемпотентность без помощи прикладного программиста, то быстро заработаю многоденег
P>Да, мы не поддерживаем эту фичу. Собственно, быстро глянул, они предлагают для всех unsafe методов добавлять доп хидеры, один из которых и будет тем самым ключом идемпотентности. P>Т.е. внагрузку к урл, будет еще один ключ. Разумная вещь.
Эта часть — она как у всех. Невозможно сделать POST идемпотентным без ключа идемпотентности.
P>В том то и дело, и это не зависит от транспорта. Потому имеет смысл втащить ключ идемпотентности в энвелоп в явном виде и сэкономить себе парочку седых волос. Поступай я иначе, была бы уже вся голова седая, а так у меня только клок другой в бороде седой
Что такое "в енвелоп"?
Давайте с другой стороны зайдём — у вас же в API авторизация есть? Указываете ли вы "bearerToken" в виде явного параметра у метода BL?
P>Я вот шота навскидку не могу представить, какой из сервисов-посредников поддерживает repeatable requests
Навскидку — никакой. К сожалению.
В итоге, авторы АПИ прикручивают авто-повторы к своим клиентским библиотекам.
Понятно, что в таком подходе можно выбирать любой вариант реализации идемпотентности: https://ably.com/topic/idempotency
Обратите внимание — с т.з. клиента никакого ключа идемпотентности у операции publish нету:
S>>Хорошо, что мы от этого ушли в сторону REST.
P>Как по мне, так bff/edge-services, graphql и grpc пожрали и рест, и одату целиком. Скажем, на собесах меня перестали спрашивать по рест лет 5 назад. А про одату товарищи только знают, что этото чтото дотнетное, и ни единого вопроса не задают. Подумаешь, мелочовочка — запилить full-blown серверный процессин, фремворк поверх этого, кодогенератор и тд.
Ну, для меня REST — это прежде всего подход, а уже во вторую очередь — какой-то конкретный фреймворк и стандарт обмена данными.
Тот же graphQL не противоречит REST-у. C grpc сложнее — он эдакий кадавр, который может реализовать и RESTful, и RPC-style сервис.
Про bff/edge-services ничего не знаю.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[28]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, Sinclair, Вы писали:
P>>А что это меняет? Гарантии то всё равно слабые. Лучше подкинуть хидер prefer и получить результат в выхлопе post. S>Речь не о том, что мы хотим сделать GET-после-PUT, а в некоторых мета-соглашениях об API. S>Вот, к примеру, когда я вижу, что для создания нового экземпляра мне предлагается сделать POST на некий URL, у меня нет никакой информации о том, где я потом буду эту сущность запрашивать. S>Ну да, обычно я могу увидеть список этих сущностей, выполнив GET на тот же URL. И тем не менее — не всегда понятно, чем именно адресуется конкретная сущность. S>Никакие prefer тут не помогут.
POST /Orders вернет тебе хидер Location + опционально саму сущность, для которой, например, в одата, будет установлена метадата, типа @odata.editUrl или @odata.readUrl.
То есть, ажно два способа получить ровно то, что тебе надо.
Только при чем здесь идемпотентность?
S>У нас нет вот этой роскоши "поменять аннотацию и перекомпилировать гатевей", потому что мы вообще не контролируем вторую сторону. S>Если мы вдруг решим перенести ключ идемпотентности из тела в хидер на стороне сервера, то это просто означает, что сколько-то тысяч клиентов упадут после деплоймента.
Упадут. Это я мальца погорячился.
S>А когда мы пишем клиента, у нас возникает вопрос "а как узнать, что и как себя ведёт". Про требования идемпотентности PUT мы знаем из публичных RFC, поэтому пишем наш код соответствующим образом. S>Для POST мы должны глазами читать спецификацию на естественном языке. Которая очень редко бывает исчерпывающей, кстати.
Если нужны внятные гарантии повторяемости запроса, читать придется и там, и там. Другого варианта нет.
S>Обратите внимание, как замудрёно прикрутили идемпотентность POST к OData — там ещё и предусмотрен способ ограничить продолжительность обязанности сервера по поддержке идемпотентности.
Не только POST,а PUT, PATCH и еще DELETE. Т.е. речь о том, что встроеной в хттп идемпотентности мало на что хватает.
P>>Теоретически это круто, авто-повторы от имени сверх-тонких клиентов, но практически это лучше делать через гатевей, который понимает тот протокол, который поверх хттп. Тогда к нему можно прикрутить кучку дополнительных вещей. S>Осталось понять, где мы его возьмём.
Напишем, разумеется. Его всё равно придется писать, в том или ином виде, если только не закладываться полностью на фичи клауда.
S>Потому что чудес не бывает. Если я изобрету способ делать идемпотентность без помощи прикладного программиста, то быстро заработаю многоденег
P>>В том то и дело, и это не зависит от транспорта. Потому имеет смысл втащить ключ идемпотентности в энвелоп в явном виде и сэкономить себе парочку седых волос. Поступай я иначе, была бы уже вся голова седая, а так у меня только клок другой в бороде седой S>Что такое "в енвелоп"? S>Давайте с другой стороны зайдём — у вас же в API авторизация есть? Указываете ли вы "bearerToken" в виде явного параметра у метода BL?
Нету. Её берет на себя гатевей, который физически reverse proxy. Реализация АПИ просто получает этот токен через context.authorization.
S>В итоге, авторы АПИ прикручивают авто-повторы к своим клиентским библиотекам.
А потому что автоповторы они разные, для какогото запроса хватит одного повтора, для какого то — серии, для других — долбить до посинения, пока 200 не вылезет. И это может повлечь изменение архитектуры, например, добавится message queue
S>Про bff/edge-services ничего не знаю.
Это способ логически вытащить часть фронтенда на сервер. Здесь нам вообще может хватить на всё одного POST, все остальные исключительно ради оптимизации под конкретные сценарии, и то не всегда.
Фактически, edge-service или bff это ручная реализация api гатевея, который умеет много всего — подклеивать авторизацию, трансформировать запрос, дергать кучу сервисов, повторять запросы и тд и тд. Буквально бизнес-логику такой сервис не выполняет, ну разве что валидацию парметров. Получается чтото среднее между http rest и graphql.
Со стороны страны все выглядит как вызов ровно одного метода типа documentPage.getFullData() и так вне зависимости от того, где делаем запрос — в браузере, в server side rendering, или изнутри другого сервиса.
Самое главное для этого бфф, это склейка клиента и сервера, что бы трансформировать апи можно было влёгкую и не надо было переписывать все тесты, если перетащил параметр из хидера в квери или наоборот.
Соответственно rest ушел в api фасад какого ресурса, который из внешней сети недоступен.
Re[28]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, gandjustas, Вы писали:
G>Что такое "операция"? Видимо речь о запросе, запрос это Метод+URL+Тело+Заголовки
Да, вот это вот всё.
G>Предложение оппонента сделать "идемпотентность" по полю тела или заголовка.
Нет. У оппонента предложение сделать именно "сильную" идемпотентность (не RFC-шную) по какому-либо ключу. Чтобы даже если есть другие (конкурирующие) операции между последовательными запросами, то операция не повторялась. Вряд ли мы говорим о том, что если вдруг сервер получает два разных запроса с одним и тем же idempotency key, то он должен выполнить новый запрос (это именно RFC-семантика). Ну и если уж быть совсем педантом, для идемпотентности на уровне RFC не нужно вообще никаких idempotency key. Сервер (инфраструктура) может просто запоминать последний запрос к ресурсу и просто выдавать сохраненный ответ, если запрос повторяется. Нет же, оппонент хочет какие-то idempotency key. Значит, мы все же о чем-то другом говорим. И вот обычно под idempotency key имеется в виду, что запрос может быть безопасно повторен при наличии конкурентных запросов.
M>>Далее. Похоже, в этой теме есть непонимание того, что такое идемпотентность. Прямо по определению (RFC 2616, 9.1.2) идемпотентность говорит, что req(req(state)) == req(state) (и как следствие req(req(req(...(req(state))...))) == req(state). Всё! В общем случае оно даже не гарантирует, что сломавшийся запрос можно безопасно повторять. G>Как раз гарантирует. В случае неполучения ответа (пропадания связи) или ответа 500 можно повторять идемпотентный запрос пока не получишь другой ответ.
Гарантирует только при условии остутствия гонок (параллельных небезопасных запросов к тому же ресурсу). А если есть гонки, идемпотентность ничего не гарантирует.
G>Это к идемпотентности не имеет отношения. Это состояние гонки и с ним надо бороться другими методами.
Это имеет самое прямое отношение. В некоторых случаях методы борьбы с гонками настолько суровые, что идемпотентность нам больше не нужна. Допустим, мы решили, что все запросы от клиентов должны быть условные (conditional). С этого момента нам идемпотентность ничем не поможет. Потому что либо запрос проходит (и тогда мы знаем, что состояние не изменилось). Либо не проходит с диагностикой о том, что условие не выполняется и нужно разбираться с тем, какое же новое состояние имеет объект. Идемпотентность — один из способов обеспечения надежности, когда условия позволяют ее использовать.
M>>В самом HTTP для существующих глаголов есть два типа идемпотентности. Первый наблюдается у безопасных глаголов (GET/HEAD/OPTIONS). У них состояние ресурса не изменяется, т.е. req(state) == state. Второй тип наблюдается у глаголов PUT/DELETE. Для них выполняется семантика перезаписи. G>Это называется SAFE и IDEMPOTENT
Первые — полностью согласен. Со вторыми сложнее. Класс (небезопасных) идемпотентных операций шире, чем представленный PUT и DELETE. Т.е. PUT и DELETE — идемпотентные, да. Но могут быть и идемпотентные глаголы с другой семантикой.
G>Суть разговора была в том, что оппонент предлагает использовать POST и <unique-op-id> в теле или заголовке
К сожалению, оппонент при этом не дает определения идемпотентности операции, которое он хочет достичь. И мне почему-то кажется, что здесь используется не RFC-шное определение.
Re[14]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, Sinclair, Вы писали:
S>Чтобы идемпотентностью пользоваться, нужен какой-то код обвязки вызовов на клиенте. S>То есть, мы не просто делаем где-то в коде клиента httpClient.PostAsync(orderServiceUrl, orderUpdateContent). Нам надо S>1. Записать в локальную базу инфу о том, что "worflow #xxxxxxx is going to update the order #yyyyyyy, idempotence key #kkkkk, update params $pppppppp". S>2. Попытаться выполнить тот самый POST, или PUT, или PATCH S>3. В зависимости от результата либо перейти по успешной ветке, либо по ветке неудачи, либо вернуться на шаг 2.
Спасибо! Это очень существенный момент. Обычно для идемпотентного PUT таких приседаний не нужно. В хорошем API можно просто посылать желаемое (т.е. с точки зрения клиента — текущее) состояние и в случае конфликтов разбираться, где что-то пошло не так.
С явными ключами идемпотентности нет проблем только в браузере. Если браузер был закрыт, запрос можно не повторять. А вот в API, к которому может обращаться сервер, явные ключи идемпотентности могут быть очень неудобны как раз потому, что их нужно генерировать и сохранять. Хотя бы на случай, когда данный инстанс будет остановлен и операцию докатывать будет уже другой экземпляр сервера.
Re[15]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, maxkar, Вы писали:
M>Спасибо! Это очень существенный момент. Обычно для идемпотентного PUT таких приседаний не нужно. В хорошем API можно просто посылать желаемое (т.е. с точки зрения клиента — текущее) состояние и в случае конфликтов разбираться, где что-то пошло не так.
Я не очень понял, что вы имеете в виду под выделенным. Ну, можно просто игнорировать идемпотентность в интерактивных приложениях, оставляя пользователя наедине с request timed out.
Но если хочется хоть как-то пользоваться идемпотентностью, то нужна собственно логика повторов. Опять же, в интерактивном приложении мы можем выполнять шаг 1 не в персистентное хранилище, а просто держать всё в памяти.
Тогда идемпотентность будет ограничена временем жизни конкретного экземпляра приложения (а то и конкретного окна).
M>С явными ключами идемпотентности нет проблем только в браузере. Если браузер был закрыт, запрос можно не повторять. А вот в API, к которому может обращаться сервер, явные ключи идемпотентности могут быть очень неудобны как раз потому, что их нужно генерировать и сохранять. Хотя бы на случай, когда данный инстанс будет остановлен и операцию докатывать будет уже другой экземпляр сервера.
Совершенно верно. Сервер должен быть всегда готов к собственному сбою. По-другому никак нельзя.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[16]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, Sinclair, Вы писали:
M>>Обычно для идемпотентного PUT таких приседаний не нужно. В хорошем API можно просто посылать желаемое (т.е. с точки зрения клиента — текущее) состояние и в случае конфликтов разбираться, где что-то пошло не так. S>Я не очень понял, что вы имеете в виду под выделенным.
У меня в основном серверные приложения. Типичный сценарий подразумевает, что в какой-то момент объект получает состояние "sending to provider" (без детализации но с разными таймаутами для повторов). В этом состоянии сервер посылает запрос. При успехе — обновляет состояние в базе. При ошибке — ничего не делает. Еще есть универсальный докат транзакций. Он выполняет "select * from entities where state == 'sending to provider'" и далее делает обычный PUT. Все это — относительно просто и универсально. Состояния явно соовтетствуют тому, что происходит на сервере.
Добавление ключей идемпотентности усложняет схему. В состояние добавляется новое поле "ключ идемпотентности". В ряде случаев это не очень сложно. В других — сложно. Например, может быть распределенная архитектура с микроядром (distributed microkernel architecture). Ядро обеспечивает общее изменение состояний. Интеграции с провайдерами обычно получают запрос от ядра, конвертируют его в формат провайдера и отправляют дальше. Во многих случаях эти интеграции вообще без состояния. Т.е. просто взяли запрос, преобразовали его (переместили поля, изменили названия и т.п.), отправили дальше. Потом разобрали/поменяли ответ и передали ядру. Добавление ключей идемпотентности запросто превращается в приключение. Нет никакой гарантии того, что у разных провайдеров будет одинаковый формат ключа. Поэтому приходится делать какую-то кастомизацию. Либо отдельный вызов для генерации таких ключей, либо генерация ключей идемпотентности в модуле интеграции. Во втором случае модуль получает свою отдельную базу данных. Это не всегда плохо, но и не всегда хорошо.
В общем, явные ключи идемпотентности добавляют лишние приседания (на сервере, да), которые не имеют естественного отображения в доменную модель. И добавляет совершенно синтетические атрибуты. А вот PUT (с состоянием "мы посылаем") — вполне естественное состояние в доменной модели. С браузером или клиентом проблем как раз гораздо меньше.
Re[29]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, maxkar, Вы писали:
M>Здравствуйте, gandjustas, Вы писали:
G>>Что такое "операция"? Видимо речь о запросе, запрос это Метод+URL+Тело+Заголовки M>Да, вот это вот всё.
G>>Предложение оппонента сделать "идемпотентность" по полю тела или заголовка. M>Нет. У оппонента предложение сделать именно "сильную" идемпотентность (не RFC-шную) по какому-либо ключу. Чтобы даже если есть другие (конкурирующие) операции между последовательными запросами, то операция не повторялась. Вряд ли мы говорим о том, что если вдруг сервер получает два разных запроса с одним и тем же idempotency key, то он должен выполнить новый запрос (это именно RFC-семантика). Ну и если уж быть совсем педантом, для идемпотентности на уровне RFC не нужно вообще никаких idempotency key. Сервер (инфраструктура) может просто запоминать последний запрос к ресурсу и просто выдавать сохраненный ответ, если запрос повторяется. Нет же, оппонент хочет какие-то idempotency key. Значит, мы все же о чем-то другом говорим. И вот обычно под idempotency key имеется в виду, что запрос может быть безопасно повторен при наличии конкурентных запросов.
Очень, очень верное замечание.
Есть ли ссылки на готовые материалы почитать по данной теме?
Я не припомню, чтобы я встречал где-то рассуждения о слабой/сильной идемпотентностях; хотя, очевидно, желанной является вторая.
Вот у нас есть три клиента — A, B, С,
1. A создаёт объект X.
2. B находит объект X, и модифицирует его (назовём новое состояние X').
3. С находит объект X' и удаляет его.
Теперь представим, что у всех троих нестабильная связь. Очевидно, что A должен иметь возможность повторять попытки создать X и после момента 2, и после момента 3.
При этом мы ожидаем, что он таки получит свой 201 — c его точки зрения, это первая успешная попытка создания.
Чего мы не ожидаем:
— что после 2 он получит 4хх — это бы выглядело так, как будто он сделал что-то нехорошее (пытается создать объект-дубликат)
— что после 2 он получит 200/201 и X' вернётся обратно в X (это бы нарушило ожидания B)
— что после 3 он восстановит X из мёртвых — это бы нарушило ожидания C
B, в свою очередь, должен иметь возможность продолжать повторять попытки изменения X.
По тем же причинам мы ожидаем, что даже после 3 он получит 200/202, а не 410 Gone и не восстановит X' из мёртвых.
При этом, очевидно, семантика RFC для этого совершенно недостаточна.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[30]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, Sinclair, Вы писали:
S>При этом, очевидно, семантика RFC для этого совершенно недостаточна.
Какой RFC имеется ввиду?
Все что необходимо для решения проблемы конкурентного обновления есть в https://httpwg.org/specs/rfc9110.html
Даже два механизма:
— заголовок ответа Last-Modified и изголовки запросов If-Modified-Since, If-Unmodifieed-Since
— заголовок ответа Etag и изголовки запросов If-Match, If-None-Match
Предполагается что в случае невыполнения условия сервер должен вернуть 412 Precondition Failed
S>Я не припомню, чтобы я встречал где-то рассуждения о слабой/сильной идемпотентностях; хотя, очевидно, желанной является вторая.
Потому что оно так никогда не называлось.
Идемпотентность по той же ссылке определяется вот так:
Idempotent methods are distinguished because the request can be repeated automatically if a communication failure occurs before the client is able to read the server's response
Нет никаких рассуждений о "сильной" или "слабой"
Re[30]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, maxkar, Вы писали:
M>>Нет. У оппонента предложение сделать именно "сильную" идемпотентность (не RFC-шную) по какому-либо ключу. Чтобы даже если есть другие (конкурирующие) операции между последовательными запросами, то операция не повторялась. Вряд ли мы говорим о том, что если вдруг сервер получает два разных запроса с одним и тем же idempotency key, то он должен выполнить новый запрос (это именно RFC-семантика). Ну и если уж быть совсем педантом, для идемпотентности на уровне RFC не нужно вообще никаких idempotency key. Сервер (инфраструктура) может просто запоминать последний запрос к ресурсу и просто выдавать сохраненный ответ, если запрос повторяется. Нет же, оппонент хочет какие-то idempotency key. Значит, мы все же о чем-то другом говорим. И вот обычно под idempotency key имеется в виду, что запрос может быть безопасно повторен при наличии конкурентных запросов.
S>Очень, очень верное замечание. S>Есть ли ссылки на готовые материалы почитать по данной теме? S>Я не припомню, чтобы я встречал где-то рассуждения о слабой/сильной идемпотентностях; хотя, очевидно, желанной является вторая.
S>Вот у нас есть три клиента — A, B, С,
S>1. A создаёт объект X. S>2. B находит объект X, и модифицирует его (назовём новое состояние X'). S>3. С находит объект X' и удаляет его.
У нас есть понятие идемпотентности метода, но не группы методов, связанных некой замысловатой семантикой. Т.е. мы можем говорить об идемпотентности PUT отдельно от идемпотентности DELETE, и у нас нет идемпотентности пары PUT+DELETE.
В этом наборе операций для A/B/C у нас ведь есть еще и GET, который вызвается в момент "находит"? Или это кондишнл PUT/DELETE? Если есть GET и параллельные операции создания, то уже одного PUT и GET достаточно что бы получить состояние гонки даже с одним клиентом. Методы PUT и GET должны вызываться взаимоисключающим образом и только так. Пока не получен ответ на PUT, все ответы GET будут скомпрометированы и наоборот. Это даже вне кондишнл и идемпотентности.
В ситуации с несколькими клиентами мы не в силах контролировать время отправки запроса и получения ответа на другом клиенте, т.е. все полученные с сервера ответы, даже без намеков на проблемы транспорта, оптимистические в лучшем случае.
Мы можем лишь придумать непротиворечивое поведение, решающее гонки в некоторых случаях, но это будет всего лишь наше решение, не имеющее ничего общего с идемпотентностью "группы методов со связанной семантикой".
S>Теперь представим, что у всех троих нестабильная связь. Очевидно, что A должен иметь возможность повторять попытки создать X и после момента 2, и после момента 3.
А дело же не только в связи. Пользователю приспичило в туалет, он захлопнул крышку ноута, ноут сделал кибернейт, а после туалета оказалось, что рабочее время вышло и запрос на удаление найденного в пятницу X вылетит лишь в понедельник. А может и не вылететь, если у ноута села батарейка. S>При этом мы ожидаем, что он таки получит свой 201 — c его точки зрения, это первая успешная попытка создания. S>Чего мы не ожидаем: S>- что после 2 он получит 4хх — это бы выглядело так, как будто он сделал что-то нехорошее (пытается создать объект-дубликат) S>- что после 2 он получит 200/201 и X' вернётся обратно в X (это бы нарушило ожидания B) S>- что после 3 он восстановит X из мёртвых — это бы нарушило ожидания C S>B, в свою очередь, должен иметь возможность продолжать повторять попытки изменения X. S>По тем же причинам мы ожидаем, что даже после 3 он получит 200/202, а не 410 Gone и не восстановит X' из мёртвых. S>При этом, очевидно, семантика RFC для этого совершенно недостаточна.
За данную задачу не скажу, не вижу, как ее решить правильно, требования не доконца ясны. Но в проекте корпоративной облачной файлопомойки, в котором я работаю, одноэтапное создание ресурса приводило к неизбежным гонкам, когда ресурс, созданный клиентом А, удалялся клиентом Б до момента получения А ответа о создании. Т.е. А продолжал его создавать при потере ответа сервера о создании, что было неприемлемо. Но в задаче A/B/C это вроде штатный сценарий.
Максимально стабильное решение получилось собрать с двухэтапным созданием ресурсов. Create POST-ом получает сессию с идентификатором нового ресурса, а CompleteCreate завершает создание ресурса на сервере, фактически публикуя созданный ранее скрытый ресурс. Состояние создания ресурса для клиента персистентно, он закончит его создание даже после ребута.
Своим примером я хочу показать, что задача примерно та же, отличается небольшой деталью, но рабочего решения за счет идемпотенетности или кондишнл не видно.
Тут нет задачи проявлять изобретательность в каждом конкретном случае. Можно же задачу обновления ресурса/коллекции ресурсов строить на общих подходах, например CASUpdate, где ключом будет версия/хэш/ETag ресурса. Аки в GIT. Уже упоминал где-то в начале темы.
Немного неизящно, т.к. мы не сможем ничего создать, не получив последнее состояние/версию ресурса. Не сгодится для высоконагруженных систем без троттлинга со стороны клиентов. А в целлом будет как-то работать.
Re[31]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, Sinclair, Вы писали:
S>>При этом, очевидно, семантика RFC для этого совершенно недостаточна. G>Какой RFC имеется ввиду?
2616 и 9110.
G>Все что необходимо для решения проблемы конкурентного обновления есть в https://httpwg.org/specs/rfc9110.html G>Даже два механизма: G>- заголовок ответа Last-Modified и изголовки запросов If-Modified-Since, If-Unmodifieed-Since G>- заголовок ответа Etag и изголовки запросов If-Match, If-None-Match G>Предполагается что в случае невыполнения условия сервер должен вернуть 412 Precondition Failed
Это нарушает ожидания от "сильной идемпотентности". И даже с этими хидерами у нас нет никакой гарантии соблюдения идемпотентности в случае взаимодействия с сервером нескольких клиентов.
Попробуйте "починить" приведённый мной пример при помощи добавления этих заголовков в запросы и ответы.
Парочку из аномалий вы предотвратите, но не все.
S>>Я не припомню, чтобы я встречал где-то рассуждения о слабой/сильной идемпотентностях; хотя, очевидно, желанной является вторая. G>Потому что оно так никогда не называлось.
G>Идемпотентность по той же ссылке определяется вот так: G>
G>Idempotent methods are distinguished because the request can be repeated automatically if a communication failure occurs before the client is able to read the server's response
G>Нет никаких рассуждений о "сильной" или "слабой"
В том-то и дело, что в спеке неявно подразумевается, что клиент — единственный, кто взаимодействует с заданным ресурсом на сервере.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[31]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, samius, Вы писали:
S>У нас есть понятие идемпотентности метода, но не группы методов, связанных некой замысловатой семантикой. Т.е. мы можем говорить об идемпотентности PUT отдельно от идемпотентности DELETE, и у нас нет идемпотентности пары PUT+DELETE.
Нет никакой пары. Каждый клиент "видит" только себя и сервер, и пытается реализовать наилучшую, на его взгляд, стратегию работы.
S>В этом наборе операций для A/B/C у нас ведь есть еще и GET, который вызвается в момент "находит"?
Да, конечно. Клиенты B и C используют GET (причём, скорее всего, на уровень выше в иерархии — типа "дай мне список X подходящих под условие отбора")ю
S>Или это кондишнл PUT/DELETE? Если есть GET и параллельные операции создания, то уже одного PUT и GET достаточно что бы получить состояние гонки даже с одним клиентом. Методы PUT и GET должны вызываться взаимоисключающим образом и только так. Пока не получен ответ на PUT, все ответы GET будут скомпрометированы и наоборот. Это даже вне кондишнл и идемпотентности.
С чего бы это вдруг? С точки зрения сервера, первый же PUT был успешным. У него нет никаких причин полагать, что A не получил ответ. Стало быть, он считает X вполне себе валидным ресурсом, который доступен для произвольных манипуляций B и C. S>В ситуации с несколькими клиентами мы не в силах контролировать время отправки запроса и получения ответа на другом клиенте, т.е. все полученные с сервера ответы, даже без намеков на проблемы транспорта, оптимистические в лучшем случае.
Это всего лишь влияет на то, что B получает в своей выдаче. Относительность времени между A и B означает, что B мог не увидеть X, если сервер трактует его GET как прибывший раньше PUT.
Но если B "нашёл" X, то сервер уже не может "притвориться", что X нету.
Ок, опытный B, конечно же, примет меры для предотвращения lost update — в частности, в своём PUT он подложит If-Match, чтобы нечаянно не сломать потенциальных конкурентов, которые могли успеть сделать из X X" в промежутке между GET и PUT. Но смотрите, в чём беда — при повторах своего update B рискует дождаться момента после 3:DELETE, пришедшего от C. Что делать серверу? Формально, If-Match требует отдать 4хх, т.к. ожидаемого X там больше нет.
Если это и будет первым успешно доставленным респонсом на запросы B, то он будет введён в заблуждение. B будет считать, что он так и не успел обновить X перед удалением. А это не так, и это может быть важно из-за семантики приложения.
S>>Теперь представим, что у всех троих нестабильная связь. Очевидно, что A должен иметь возможность повторять попытки создать X и после момента 2, и после момента 3. S>А дело же не только в связи. Пользователю приспичило в туалет, он захлопнул крышку ноута, ноут сделал кибернейт, а после туалета оказалось, что рабочее время вышло и запрос на удаление найденного в пятницу X вылетит лишь в понедельник. А может и не вылететь, если у ноута села батарейка.
Всё верно. Но если запрос на удаление таки поедет в понедельник, то никакого противоречия не будет — что бы там ни ответил сервер, это будет корректно. Например, вмешался D, который изменил Х' на X", и теперь его нельзя удалять без проверки. Отлично. Но если удаление таки прошло в пятницу, а в понедельник сервер делает вид, что ничего такого не было — это уже ай-яй-яй.
S>За данную задачу не скажу, не вижу, как ее решить правильно, требования не доконца ясны. Но в проекте корпоративной облачной файлопомойки, в котором я работаю, одноэтапное создание ресурса приводило к неизбежным гонкам, когда ресурс, созданный клиентом А, удалялся клиентом Б до момента получения А ответа о создании. Т.е. А продолжал его создавать при потере ответа сервера о создании, что было неприемлемо. Но в задаче A/B/C это вроде штатный сценарий.
Нет, это как раз вырожденный вариант, в котором участвуют A и C. В хорошей реализации, независимо от проблем связи, ресурс X должен на сервере проходить жизненный цикл "появился-умер" однократно.
А не так, что он будет "мерцать", пока A и C борются с недоставкой ответов, и результат зависит только от того, у кого их них первым восстановится двусторонняя связь.
S>Максимально стабильное решение получилось собрать с двухэтапным созданием ресурсов. Create POST-ом получает сессию с идентификатором нового ресурса, а CompleteCreate завершает создание ресурса на сервере, фактически публикуя созданный ранее скрытый ресурс. Состояние создания ресурса для клиента персистентно, он закончит его создание даже после ребута.
Звучит как реализация той самой "сильной" идемпотентности вручную.
S>Своим примером я хочу показать, что задача примерно та же, отличается небольшой деталью, но рабочего решения за счет идемпотенетности или кондишнл не видно.
Ну вот есть подозрение, что спека на repeatable requests OData позволяет решить эту проблему. Надо бы поковырять это повнимательнее.
S>Немного неизящно, т.к. мы не сможем ничего создать, не получив последнее состояние/версию ресурса. Не сгодится для высоконагруженных систем без троттлинга со стороны клиентов. А в целлом будет как-то работать.
Да, тут надо повнимательнее посмотреть на вопрос с таймстампами. Если удастся их верно назначить на стороне сервера, то будет работать корректное упорядочивание запросов, и можно будет всем раздавать повторяемые ответы.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[32]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, samius, Вы писали:
S>>У нас есть понятие идемпотентности метода, но не группы методов, связанных некой замысловатой семантикой. Т.е. мы можем говорить об идемпотентности PUT отдельно от идемпотентности DELETE, и у нас нет идемпотентности пары PUT+DELETE. S>Нет никакой пары. Каждый клиент "видит" только себя и сервер, и пытается реализовать наилучшую, на его взгляд, стратегию работы.
Я о паре/совокупности методов, а не про пару клиентов.
S>>Или это кондишнл PUT/DELETE? Если есть GET и параллельные операции создания, то уже одного PUT и GET достаточно что бы получить состояние гонки даже с одним клиентом. Методы PUT и GET должны вызываться взаимоисключающим образом и только так. Пока не получен ответ на PUT, все ответы GET будут скомпрометированы и наоборот. Это даже вне кондишнл и идемпотентности. S>С чего бы это вдруг? С точки зрения сервера, первый же PUT был успешным. У него нет никаких причин полагать, что A не получил ответ. Стало быть, он считает X вполне себе валидным ресурсом, который доступен для произвольных манипуляций B и C.
С точки зрения клиента. Если он отправил GET и PUT, не дождавшись ответа GET, или наоборот, то клиент не сможет узнать по ответу GET, мог ли быть выполнен отправленный им PUT и чей-то DELETE, если в ответе GET нет X.
S>>В ситуации с несколькими клиентами мы не в силах контролировать время отправки запроса и получения ответа на другом клиенте, т.е. все полученные с сервера ответы, даже без намеков на проблемы транспорта, оптимистические в лучшем случае. S>Это всего лишь влияет на то, что B получает в своей выдаче. Относительность времени между A и B означает, что B мог не увидеть X, если сервер трактует его GET как прибывший раньше PUT.
Исхожу из того, что сервер вообще не знает, кто когда отправил запрос, отвечает моментально в момент получения запроса и не думает о том, кому что показать, показывая свое текущее состояние. Пусть, например, он хранит свое состояние в критической секции, или делает вид что происходит именно это. S>Но если B "нашёл" X, то сервер уже не может "притвориться", что X нету.
Может, если допускается B2, который уже поменял X->X2 S>Ок, опытный B, конечно же, примет меры для предотвращения lost update — в частности, в своём PUT он подложит If-Match, чтобы нечаянно не сломать потенциальных конкурентов, которые могли успеть сделать из X X" в промежутке между GET и PUT. Но смотрите, в чём беда — при повторах своего update B рискует дождаться момента после 3:DELETE, пришедшего от C. Что делать серверу? Формально, If-Match требует отдать 4хх, т.к. ожидаемого X там больше нет. S>Если это и будет первым успешно доставленным респонсом на запросы B, то он будет введён в заблуждение. B будет считать, что он так и не успел обновить X перед удалением. А это не так, и это может быть важно из-за семантики приложения.
Понимаю. И идемпотентность не снимает проблем гонок с разделяемым состоянием. Она не для этого. Это проблема разработчика — обеспечить корректную работу в соответствии с семантикой приложения, не забыв об идемпотентности, которая обеспечит ему возможность повтора вызовов и только лишь.
S>Всё верно. Но если запрос на удаление таки поедет в понедельник, то никакого противоречия не будет — что бы там ни ответил сервер, это будет корректно. Например, вмешался D, который изменил Х' на X", и теперь его нельзя удалять без проверки. Отлично. Но если удаление таки прошло в пятницу, а в понедельник сервер делает вид, что ничего такого не было — это уже ай-яй-яй.
Я хотел продемонстрировать (для других, уверен, что мы с вами понимаем), что проблема не в одной лишь связи, которая может быть сколь угодно надежна. Уже были примеры с метро. А это в плюс пример с офисом, езернетом и резервным питанием всего, что можно.
Но да, не там сделал акцент. Пусть ситуация будет такая, что запрос отправлен, а ответ пришел уже после ухода в кибернейт. Приложение просыпается в понедельник — ответа нет. В контексте исполняющегося потока кода для приложения довольно сложно определить, что предыдущая строчка с вызовом метода выполнялась на прошлой неделе, а текущая — уже на новой неделе. Ответа сервера нет и не будет.
S>>За данную задачу не скажу, не вижу, как ее решить правильно, требования не доконца ясны. Но в проекте корпоративной облачной файлопомойки, в котором я работаю, одноэтапное создание ресурса приводило к неизбежным гонкам, когда ресурс, созданный клиентом А, удалялся клиентом Б до момента получения А ответа о создании. Т.е. А продолжал его создавать при потере ответа сервера о создании, что было неприемлемо. Но в задаче A/B/C это вроде штатный сценарий. S>Нет, это как раз вырожденный вариант, в котором участвуют A и C. В хорошей реализации, независимо от проблем связи, ресурс X должен на сервере проходить жизненный цикл "появился-умер" однократно. S>А не так, что он будет "мерцать", пока A и C борются с недоставкой ответов, и результат зависит только от того, у кого их них первым восстановится двусторонняя связь.
Это зависит от задачи. Вполне допускаю такую задачу, где ресрус именно должен мерцать. Причем, даже без GET. Один лупит PUT X, другой — DELETE X в цикле. Например, синтетическая задача по замеру среднего времени жизни ресурса в таких условиях при разном количестве создающих и удаляющих клиентов.
Не настаиваю на рассмотрении именно такой задачи.
S>>Максимально стабильное решение получилось собрать с двухэтапным созданием ресурсов. Create POST-ом получает сессию с идентификатором нового ресурса, а CompleteCreate завершает создание ресурса на сервере, фактически публикуя созданный ранее скрытый ресурс. Состояние создания ресурса для клиента персистентно, он закончит его создание даже после ребута. S>Звучит как реализация той самой "сильной" идемпотентности вручную.
Возможно, так и есть, но я не думал об этом в терминах "сильной" идемпотентности. обычная книжная идемпотентность в рамках этого решения решает лишь свою маленькую задачку.
S>>Своим примером я хочу показать, что задача примерно та же, отличается небольшой деталью, но рабочего решения за счет идемпотенетности или кондишнл не видно. S>Ну вот есть подозрение, что спека на repeatable requests OData позволяет решить эту проблему. Надо бы поковырять это повнимательнее.
На сколько я понял — нет, не решает. Там предлагают использовать заголовки для того, что бы отличать один PUT X со своими повторами от второго, логически с первым не связанным PUT X со своими повторами, причем, помечать для сервера так же первую попытку вызова от повторных.
так повторный PATCH с добавлением элемента коллекции на сервере можно будет отличить от намеренного добавления дубля ранее добавленного элемента.
S>>Немного неизящно, т.к. мы не сможем ничего создать, не получив последнее состояние/версию ресурса. Не сгодится для высоконагруженных систем без троттлинга со стороны клиентов. А в целлом будет как-то работать. S>Да, тут надо повнимательнее посмотреть на вопрос с таймстампами. Если удастся их верно назначить на стороне сервера, то будет работать корректное упорядочивание запросов, и можно будет всем раздавать повторяемые ответы.
Чистые таймстампы будут препятствовать масштабированию сервера. Та еще мина.
Re[32]: Идемпотентность POST - хорошая ли практика?
Здравствуйте, Sinclair, Вы писали:
S>Это всего лишь влияет на то, что B получает в своей выдаче. Относительность времени между A и B означает, что B мог не увидеть X, если сервер трактует его GET как прибывший раньше PUT.
Как я понимаю, идемпотентность позоволяет добавить между клиентом и сервером кеширующий прокси. Как мне кажется, это упрощает понимание сценариев, если предположить, что есть такой прокси, который "абсолютно надёжен" и кеширует все ответы сервера в соответствии с rfc (по урлу как ключу, по глаголам, с обработкой соответсвующих заголовков типа if-match и т.п.). В таком случае сервер видит запрос ровно один раз и даёт ровно один ответ. Клиент, если не смог получить ответ в первый раз, всегда получит его от прокси, в том же виде.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[33]: Идемпотентность POST - хорошая ли практика?
Важно не наличие прокси, а сама возможность восстановить когерентность состояния после сбоя.
Вот у вас есть микросервис Х, который выставляет наружу некоторую операцию A.
Для выполнения этой операции нужно выполнить две операции — A1 и A2, в микросервисах Y и Z.
Для конкретики: А1 — это списание денег, А2 — резервирование авиабилета.
Z ничего не знает про деньги; его работа — чисто резервирование билетов от имени агента.
Y ничего не знает про билеты; его работа — чисто обработка платежей.
Теперь учтём, к примеру, то, что X может внезапно упасть в любой момент:
— до первого обращения к Y
— после обращения к Y, но до того, как ответ от Y сохранён в локальную базу
— после обращения к Y, но перед обращением к Z
— после обращения к Z, но до того, как ответ от Z сохранён в локальную базу.
Плюс к этому, даже если сам X вполне себе функционален, возможен сбой коммуникации между ним и Y/Z. Возможен сбой самих Y и Z — причём в произвольные моменты:
— до получения запроса
— после получения запроса, но до внесения изменений в локальную базу
— после внесения изменений в базу, но до отправки ответа X
Единственный способ избежать безумия и дорогостоящих рукопашных процедур восстановления когерентности — это идемпотентность.
X полагается на то, что у него есть безопасный способ повтора операции, если он "не расслышал" или "забыл" ответ на предыдущий запрос.
Тогда у него есть штатный способ, не зависящий от ручного вмешательства инженеров, довести свою работу "оформление заказа на авиабилет" до конца — успешного там или неуспешного.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.