Використання методів PUT vs PATCH у сценаріях реального життя REST API


681

Перш за все, деякі визначення:

PUT визначений у Розділі 9.6 RFC 2616 :

Метод PUT вимагає, щоб вкладений об'єкт зберігався під наданим URI-запитом. Якщо Request-URI посилається на вже існуючий ресурс, додане об'єкт ДОЛЖНЕ розглядатися як модифікована версія тієї, що знаходиться на сервері походження . Якщо Uquest-URI не вказує на існуючий ресурс і URI може бути визначений як новий ресурс запитуючим агентом користувача, сервер-джерело може створити ресурс з цим URI.

PATCH визначено в RFC 5789 :

Метод PATCH просить застосувати набір змін, описаних в об'єкті запиту, до ресурсу, ідентифікованого запитом-URI.

Також згідно з RFC 2616, розділ 9.1.2 PUT є ідентичним, а PATCH - ні.

Тепер давайте розглянемо реальний приклад. Коли я виконую POST /usersз даними {username: 'skwee357', email: 'skwee357@domain.com'}і сервер здатний створити ресурс, він відповість 201 і розташування ресурсу (припустимо /users/1) і будь-який наступний дзвінок у GET /users/1повернеться {id: 1, username: 'skwee357', email: 'skwee357@domain.com'}.

Тепер скажемо, що хочу змінити свою електронну пошту. Модифікація електронної пошти вважається "набором змін", і тому я повинен ПАТЧУВАТИ /users/1" патч-документ ". У моєму випадку це буде документ json : {email: 'skwee357@newdomain.com'}. Потім сервер повертає 200 (якщо дозволити, що це нормально). Це підводить мене до першого питання:

  • ПАТЧ НЕ ідентичний. Це сказано в RFC 2616 та RFC 5789. Однак якщо я видам той самий запит PATCH (з моєї нової електронної пошти), я отримаю той самий стан ресурсу (при цьому мій електронний лист буде змінено на запитуване значення). Чому PATCH не є ідентичним?

PATCH - це порівняно новий дієслово (RFC, представлений у березні 2010 року), і він вирішує проблему "виправлення" або зміни набору полів. До того, як PATCH був введений, всі використовували PUT для оновлення ресурсів. Але після того, як PATCH був введений, він бентежить мене з приводу того, для чого використовується PUT. І це підводить мене до мого другого (і головного) питання:

  • Яка реальна різниця між PUT та PATCH? Я десь читав, що PUT може використовуватися для заміни цілого об'єкта на конкретний ресурс, тому слід надіслати повну сутність (замість набору атрибутів, як у PATCH). Яке реальне практичне використання для такого випадку? Коли ви хочете замінити / перезаписати сутність у конкретному URI ресурсу, і чому така операція не вважається оновленням / виправленням сутності? Єдиним випадком практичного використання, який я бачу для PUT, є видача PUT на колекцію, тобто /usersзаміна всієї колекції. Видавати PUT для конкретної сутності не має сенсу після введення PATCH. Я помиляюся?

1
а) це RFC 2616, а не 2612. б) RFC 2616 застаріла, поточна специфікація PUT знаходиться в greenbytes.de/tech/webdav/rfc7231.html#PUT , в) я не розумію вашого запитання; чи не зовсім очевидно, що PUT можна використовувати для заміни будь-якого ресурсу, не тільки колекції; г) до введення PATCH, люди зазвичай використовували POST, д) нарешті, так, конкретний запит PATCH (залежно від формату патчу) може бути безсильним; це просто, що це взагалі.
Джуліан Решке

якщо це допомагає, я написав статтю про PATCH vs PUT eq8.eu/blogs/36-patch-vs-put-and-the-patch-json-syntax-war
еквівалент8

5
Просто: POST створює елемент у колекції. PUT замінює елемент. PATCH змінює елемент. Під час POSTing URL-адреса нового елемента обчислюється та повертається у відповідь, тоді як PUT та PATCH потребують URL-адреси у запиті. Правильно?
Том Расселл

Цей пост може бути корисним: POST vs PUT vs PATCH: mscharhag.com/api-design/http-post-put-patch
micha

Відповіді:


943

ПРИМІТКА . Коли я вперше провів час, читаючи про REST, ідентифікація була заплутаною концепцією, щоб спробувати вийти правильно. У своїй оригінальній відповіді я все ще не зрозумів це правильно, як показали подальші коментарі (і відповідь Джейсона Хотгера ). Деякий час я чинив опір оновленню цієї відповіді, щоб уникнути ефективного плагіату Джейсона, але я зараз його редагую, бо, ну, мене просили (у коментарях).

Прочитавши мою відповідь, пропоную вам також прочитати відмінну відповідь Джейсона Хетгера на це запитання, і я спробую зробити свою відповідь кращою, не просто крадучись з Джейсоном.

Чому PUT ідентичний?

Як ви зазначали у вашому цитаті RFC 2616, PUT вважається безсильним. Коли ви PUT ресурс, ці два припущення відтворюються:

  1. Ви посилаєтесь на сутність, а не на колекцію.

  2. Суб'єкт, який ви постачаєте, є повним ( вся організація).

Давайте розглянемо один із ваших прикладів.

{ "username": "skwee357", "email": "skwee357@domain.com" }

Якщо ви надішліть цей документ /users, як ви пропонуєте, ви можете повернути такий суб'єкт, як

## /users/1

{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}

Якщо ви хочете змінити цю сутність пізніше, ви вибираєте між PUT та PATCH. PUT може виглядати так:

PUT /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // new email address
}

Це можна зробити за допомогою PATCH. Це може виглядати приблизно так:

PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

Ви відразу помітите різницю між цими двома. PUT включав усі параметри цього користувача, але PATCH включав лише той, що був змінений ( email).

Під час використання PUT передбачається, що ви надсилаєте повну сутність, і ця повна сутність заміняється будь-яку існуючу сутність у цьому URI. У наведеному вище прикладі PUT та PATCH досягають однієї мети: вони обидва змінюють електронну адресу цього користувача. Але PUT обробляє його, замінюючи всю сутність, тоді як PATCH оновлює лише ті поля, які були поставлені, залишаючи інші в спокої.

Оскільки запити PUT включають всю сутність, якщо ви надсилаєте один і той же запит неодноразово, він завжди повинен мати однаковий результат (дані, надіслані вами, - це цілі дані сутності). Тому PUT є безсильним.

Неправильне використання PUT

Що станеться, якщо ви використовуєте вищезазначені дані PATCH у запиті PUT?

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}
PUT /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

GET /users/1
{
    "email": "skwee357@gmail.com"      // new email address... and nothing else!
}

(Я припускаю, що для цього питання, що сервер не має певних обов'язкових полів, і дозволив би це статися ... це може бути не так у дійсності.)

Оскільки ми використовували PUT, але тільки постачали email, то це єдине в цій сутності. Це призвело до втрати даних.

Цей приклад наведено для ілюстративних цілей - ніколи насправді цього не роби. Цей запит PUT є технічно бездоганним, але це не означає, що це не страшна, зламана ідея.

Як PATCH може бути безсильним?

У наведеному вище прикладі PATCH виявився безсильним. Ви внесли зміни, але якщо ви знову і знову вносили однакові зміни, це завжди призведе до того ж результату: ви змінили адресу електронної пошти на нове значення.

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}
PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // email address was changed
}
PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // nothing changed since last GET
}

Мій оригінальний приклад, зафіксований на точність

Спочатку у мене були приклади, які, на мою думку, демонструють неідентичність, але вони вводили в оману / неправильно. Я буду тримати приклади, але використовую їх, щоб проілюструвати іншу річ: те, що декілька документів PATCH проти однієї сутності, змінюючи різні атрибути, не роблять PATCHes неідентичними.

Скажімо, що в якийсь минулий час було додано користувача. Це стан, з якого ви починаєте.

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@olddomain.com",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Після PATCH у вас є змінена сутність:

PATCH /users/1
{"email": "skwee357@newdomain.com"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",    // the email changed, yay!
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Якщо потім кілька разів застосувати свій PATCH, ви будете отримувати той самий результат: електронний лист змінено на нове значення. А входить, А виходить, тому це безсильно.

Через годину, після того, як ви поїхали попити кави та перепочити, хтось ще приходить разом із власним ПАТЧЕМ. Здається, Поштове відділення внесло деякі зміни.

PATCH /users/1
{"zip": "12345"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",  // still the new email you set
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"                      // and this change as well
}

Оскільки цей ПАТЧ з поштового відділення не стосується електронної пошти, лише поштовий індекс, якщо він повторно застосовується, він також отримає той самий результат: поштовий індекс встановлений на нове значення. А входить, А виходить, отже, це також безсильно.

Наступного дня ви знову вирішите надіслати свій ПАТЧ.

PATCH /users/1
{"email": "skwee357@newdomain.com"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"
}

Ваш патч має той самий ефект, що і вчора: він встановив електронну адресу. A зайшов, A вийшов, отже, і це безсильно.

Що я помилився у своїй оригінальній відповіді

Я хочу зробити важливу відмінність (щось я помилився в своїй оригінальній відповіді). Багато серверів відповідатимуть на ваші запити REST, надсилаючи назад новий стан сутності, з вашими модифікаціями (якщо такі є). Отже, коли ви отримаєте цю відповідь назад, вона відрізняється від тієї, яку ви отримали вчора , тому що поштовий індекс не той, який ви отримали востаннє. Однак ваш запит не стосувався поштового коду, лише електронної пошти. Таким чином, ваш документ PATCH все ще є ідентичним - електронний лист, який ви надіслали в PATCH, тепер є адресою електронної пошти для цієї особи.

Тож коли PATCH не є безсильним?

Для повного звернення до цього питання я знову звертаюсь до відповіді Джейсона Хотгера . Я просто збираюся залишити це на цьому, тому що я, чесно кажучи, не думаю, що зможу відповісти на цю частину краще, ніж він вже є.


2
Це речення не зовсім правильне: "Але це безвідмовно: коли А входить, В завжди виходить". Наприклад, якщо ви мали би GET /users/1до того, як Поштове відділення оновило поштовий індекс, а потім знову зробило те саме GET /users/1запит після оновлення Поштового відділення, ви отримаєте два різних відповіді (різні поштові індекси). Той самий "A" (GET-запит) відбувається, але ви отримуєте різні результати. І все-таки GET залишається безсильним.
Jason Hoetger

@JasonHoetger GET є безпечним (вважається, що він не спричинить жодних змін), але не завжди є безсильним. Є різниця. Див. RFC 2616 сек. 9.1 .
Ден Лоу

1
@DanLowe: GET, безумовно, гарантовано є ідентичним. Точно сказано, що в розділі 9.1.2 RFC 2616 та в оновленій специфікації, RFC 7231, розділ 4.2.2 , "з методів запиту, визначених цією специфікацією, PUT, DELETE та безпечні методи запиту є ідентичними". Idempotence просто не означає "ви отримуєте однакову відповідь кожного разу, коли ви робите один і той же запит". 7231 4.2.2 продовжує говорити: "Повторення запиту матиме такий самий передбачуваний ефект, навіть якщо початковий запит вдався, хоча відповідь може відрізнятися ".
Джейсон Хотгер

1
@JasonHoetger Я визнаю це, але не бачу, що це стосується цієї відповіді, в якій обговорювали PUT і PATCH, і навіть не згадує GET ...
Dan Lowe

1
Ага, коментар від @JasonHoetger прояснив це: лише результуючі стани, а не відповіді, на кілька запитів методу ідентичного потенціалу повинні бути ідентичними.
Том Рассел

329

Хоча чудова відповідь Дена Лоу дуже ґрунтовно відповіла на питання ОП про різницю між PUT та PATCH, його відповідь на питання, чому PATCH не є безсильним, не зовсім коректна.

Щоб показати, чому PATCH не є безсильним, допоможе почати з визначення idempotence (з Вікіпедії ):

Термін idempotent використовується більш повно для опису операції, яка дасть ті самі результати, якщо вона виконується один або кілька разів [...] Функція idempotent - це функція, яка має властивість f (f (x)) = f (x) для будь-яке значення х.

Більш доступною мовою ідентифікаційний PATCH може бути визначений як: Після PATCHing ресурсу з патч-документом всі наступні виклики PATCH на той самий ресурс з тим самим патч-документом не змінять ресурс.

І навпаки, неідентифікуюча операція - це операція, де f (f (x))! = F (x), що для PATCH може бути зазначено як: Після PATCHing ресурсу з патч-документом, подальший PATCH викликає той самий ресурс з той же патч-документ дійсно змінює ресурс.

Щоб проілюструвати неімпотентний PATCH, припустимо, що є ресурс / користувачів, і припустимо, що виклик GET /users повертає список користувачів у даний час:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" }]

Замість того, щоб PATCHing / users / {id}, як у прикладі ОП, припустимо, що сервер дозволяє PATCHing / користувачів. Випустімо цей запит PATCH:

PATCH /users
[{ "op": "add", "username": "newuser", "email": "newuser@example.org" }]

Наш патч-документ дарує серверу додати нового користувача, викликаного newuserдо списку користувачів. Після цього зателефонувавши вперше,GET /users повернеться:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" }]

Тепер, якщо ми видаємо такий самий запит PATCH, як вище, що відбувається? (Для цього прикладу припустимо, що ресурс / users дозволяє дублювати імена користувачів.) "Op" - це "add", тому до списку додається новий користувач, а потімGET /users повернення:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" },
 { "id": 3, "username": "newuser", "email": "newuser@example.org" }]

Ресурс / користувачів знову змінився , навіть якщо ми видали такий самий PATCH проти тієї самої кінцевої точки. Якщо наш PATCH є f (x), f (f (x)) не є таким же, як f (x), і, отже, цей конкретний PATCH не є ідентичним .

Хоча PATCH - ні гарантовано є ідентичним, проте в специфікації PATCH немає нічого, що не заважало б вам робити всі операції PATCH на вашому конкретному сервері idempotent. RFC 5789 навіть передбачає переваги від безвідмовних запитів PATCH:

Запит PATCH може бути виданий таким чином, щоб він був ідентичним, що також допомагає запобігти поганим результатам від зіткнень двох запитів PATCH на одному ресурсі за аналогічні часові рамки.

На прикладі Дена, його операція PATCH насправді є безсильною. У цьому прикладі суб'єкт / users / 1 змінився між нашими запитами PATCH, але не через наші запити PATCH; Фактично, інший патч-документ Поштового відомства викликав зміну поштового коду. Інший ПАТЧ поштового відділення - це інша операція; якщо наш PATCH дорівнює f (x), то PATCH поштового відділення дорівнює g (x). Idempotence стверджує, що f(f(f(x))) = f(x), але не дає жодних гарантій f(g(f(x))).


11
Якщо припустити, що сервер також дозволяє видавати PUT на /users, це також зробить PUT неідентичним. Все це зводиться до того, як сервер призначений для обробки запитів.
Узаїр Садід

13
Отже, ми могли створити API лише за допомогою операцій PATCH. Тоді, що стає принципом REST використання http VERBS для здійснення дій CRUD щодо ресурсів? Хіба ми, панове, тут не надто ускладнюємо кордони PATCH?
bohr

6
Якщо PUT реалізований у колекції (наприклад /users), будь-який запит PUT повинен замінити вміст цієї колекції. Таким чином, PUT /usersповинен очікувати колекції користувачів та видалити всіх інших. Це безсильно. Мабуть, ви б робили таке в кінцевій точці / користувачів. Але щось подібне /users/1/emailsможе бути колекцією, і це може бути цілком справедливо, щоб дозволити замінити всю колекцію новою.
Vectorjohn

5
Хоча ця відповідь є чудовим прикладом самопочуття, я вважаю, що це може замутити води в типових сценаріях REST. У цьому випадку у вас є запит PATCH з додатковою opдією, яка запускає певну логіку на сервері. Для цього потрібно, щоб сервер і клієнт мали знати конкретні значення для передачі opполя для запуску робочих процесів на стороні сервера. У більш простих сценаріях REST такий тип opфункціонування є поганою практикою, і, швидше за все, їм слід керуватися безпосередньо через HTTP дієслова.
ivandov

7
Я б ніколи не розглядав можливість видання PATCH, лише POST і DELETE, проти колекції. Це справді колись робиться? Чи може PATCH вважатися ідентичним для всіх практичних цілей?
Том Расселл

72

Мені було цікаво і про це, і я знайшов кілька цікавих статей. Я не можу відповісти на ваше питання повною мірою, але це принаймні надає ще трохи інформації.

http://restful-api-design.readthedocs.org/en/latest/methods.html

HTTP RFC вказує, що PUT повинен приймати повне нове представлення ресурсів як об'єкт запиту. Це означає, що якщо, наприклад, надаються лише певні атрибути, їх слід видалити (тобто встановити на null).

Враховуючи це, тоді PUT повинен відправити весь об'єкт. Наприклад,

/users/1
PUT {id: 1, username: 'skwee357', email: 'newemail@domain.com'}

Це ефективно оновить електронний лист. Причина, що PUT може бути не надто ефективною, полягає в тому, що ваше єдине дійсне зміна одного поля, включаючи ім'я користувача, є марним. Наступний приклад показує різницю.

/users/1
PUT {id: 1, email: 'newemail@domain.com'}

Тепер, якщо PUT був розроблений відповідно до специфікації, PUT встановив би ім'я користувача на нуль, і ви отримаєте наступне назад.

{id: 1, username: null, email: 'newemail@domain.com'}

Використовуючи PATCH, ви оновлюєте лише вказане поле, а решта залишаєте в спокої, як у вашому прикладі.

Наступний прийом на PATCH трохи інший, ніж я ніколи не бачив.

http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/

Різниця між запитами PUT та PATCH відображається в тому, як сервер обробляє додану сутність для зміни ресурсу, визначеного Request-URI. У запиті PUT вкладений об'єкт вважається модифікованою версією ресурсу, що зберігається на початковому сервері, і клієнт вимагає замінити збережену версію. Однак із PATCH додане об'єднання містить набір інструкцій, що описують, як ресурс, що перебуває в даний час на початковому сервері, повинен бути модифікований для створення нової версії. Метод PATCH впливає на ресурс, визначений Request-URI, а також МОЖЕ мати побічні ефекти на інші ресурси; тобто нові ресурси можуть бути створені або модифіковані існуючі за допомогою застосування PATCH.

PATCH /users/123

[
    { "op": "replace", "path": "/email", "value": "new.email@example.org" }
]

Ви більш-менш трактуєте PATCH як спосіб оновлення поля. Тож замість того, щоб надсилати частковий об’єкт, ви надсилаєте операцію. тобто Замініть електронну пошту значенням.

На цьому стаття закінчується.

Варто зазначити, що PATCH насправді не розроблений для справді REST API, оскільки дисертація Філдінга не визначає жодного способу часткової модифікації ресурсів. Але сам Рой Філдінг сказав, що PATCH був чимось [він] створеним для початкової пропозиції HTTP / 1.1, оскільки частковий PUT ніколи не є RESTful. Впевнені, що ви не передаєте повне представництво, але REST не вимагає, щоб представлення було повним.

Тепер я не знаю, чи я особливо згоден із цією статтею, як відзначають багато коментаторів. Надіслати часткове представлення може бути легко описом змін.

Для мене я змішаний з використанням PATCH. Здебільшого я буду сприймати PUT як PATCH, оскільки єдиною реальною різницею, яку я помітив до цього часу, є те, що PUT "повинен" встановити пропущені значення до нуля. Це може бути не самий правильний спосіб зробити це, але удача кодування ідеально.


7
Можливо, варто додати: у статті Вільяма Дюранда (та rfc 6902) є приклади, де "op" - "add". Це, очевидно, не бездумно.
Йоханнес Бродволл

2
Або ви можете полегшити і замість цього використовувати RFC 7396 Merge Patch та уникнути створення патчу JSON.
Пьотр Кула

для таблиць nosql важливі відмінності між патчем та put, тому що у nosql немає стовпців
stackdave

18

Різниця між PUT та PATCH полягає в тому, що:

  1. PUT потрібно мати ідентичну силу. Для того, щоб досягти цього, ви повинні розмістити весь повний ресурс в тілі запиту.
  2. PATCH може бути неідентичним. Що означає, що це може бути імовірним у деяких випадках, наприклад, описаних вами випадках.

PATCH вимагає певної "мови виправлення", щоб повідомити серверу, як змінити ресурс. Абонент і сервер повинні визначити деякі "операції", такі як "додати", "замінити", "видалити". Наприклад:

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@olddomain.com",
  "state": "NY",
  "zip": "10001"
}

PATCH /contacts/1
{
 [{"operation": "add", "field": "address", "value": "123 main street"},
  {"operation": "replace", "field": "email", "value": "abc@myemail.com"},
  {"operation": "delete", "field": "zip"}]
}

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "abc@myemail.com",
  "state": "NY",
  "address": "123 main street",
}

Замість використання явних полів "операція" мова виправлення може зробити це неявним шляхом визначення умов, таких як:

в органі запиту PATCH:

  1. Існування поля означає «замінити» або «додати» це поле.
  2. Якщо значення поля є нульовим, це означає видалити це поле.

З вищенаведеною умовою, PATCH у прикладі може приймати таку форму:

PATCH /contacts/1
{
  "address": "123 main street",
  "email": "abc@myemail.com",
  "zip":
}

Що виглядає більш лаконічно і зручно. Але користувачі повинні знати про цю основу.

З операціями, про які я згадував вище, PATCH залишається безсильним. Але якщо ви визначите такі операції, як: "приріст" або "додати", ви можете легко побачити, що це більше не буде ідентичним.


7

TLDR - Версія, що скидається

PUT => Встановити всі нові атрибути для існуючого ресурсу.

PATCH => Частково оновити існуючий ресурс (не всі атрибути потрібні).


3

Дозвольте мені процитувати та детальніше прокоментувати RFC 7231, розділ 4.2.2 , вже цитований у попередніх коментарях:

Метод запиту вважається "idempotent", якщо передбачуваний ефект на сервері декількох однакових запитів із цим методом збігається з ефектом для одного такого запиту. З методів запиту, визначених цією специфікацією, PUT, DELETE та безпечні методи запиту є ідентичними.

(...)

Методи ідентичного потенціалу відрізняються тим, що запит може бути повторений автоматично, якщо помилка зв'язку трапляється до того, як клієнт зможе прочитати відповідь сервера. Наприклад, якщо клієнт надсилає запит PUT і базове з'єднання закривається до отримання будь-якої відповіді, то клієнт може встановити нове з'єднання і повторити повторно ідентифікаційний запит. Він знає, що повторення запиту матиме той самий передбачуваний ефект, навіть якщо початковий запит вдався, хоча відповідь може відрізнятися.

Отже, що повинно бути "те саме" після повторного запиту ідентичного способу? Не стан сервера, ні відповідь сервера, а намічений ефект . Зокрема, метод повинен бути ідентичним "з точки зору клієнта". Тепер я думаю, що ця точка зору показує, що останній приклад у відповіді Дена Лоу , який я не хочу тут плагіатувати, дійсно показує, що запит PATCH може бути неідентичним (більш природним чином, ніж приклад у Відповідь Джейсона Хетгера ).

Дійсно, давайте зробимо приклад трохи точнішим, зробивши чіткий один можливий намір для першого клієнта. Скажімо, цей клієнт проходить список користувачів із проектом, щоб перевірити їх електронні листи та поштові індекси. Він починається з користувача 1, помічає, що поштовий індекс правильний, але електронна пошта неправильна. Він вирішує виправити це за допомогою PATCH-запиту, який є повністю законним, і надсилає лише

PATCH /users/1
{"email": "skwee357@newdomain.com"}

оскільки це єдина корекція. Тепер запит не працює через певну проблему з мережею і автоматично надсилається через пару годин. Тим часом, інший клієнт (помилково) змінив zip користувача 1. Потім, відправлення того ж запиту PATCH вдруге не досягає наміченого ефекту клієнта, оскільки ми закінчуємо невірним ZIP. Отже, метод не є безсильним у розумінні RFC.

Якщо замість цього клієнт використовує запит PUT для виправлення електронної пошти, надсилаючи на сервер усі властивості користувача 1 разом з електронною поштою, його намічений ефект буде досягнутий, навіть якщо запит потрібно буде повторно надіслати пізніше, а користувач 1 був змінений тим часом --- оскільки другий запит PUT замінить усі зміни з першого запиту.


2

На мою скромну думку, ідентифікація означає:

  • PUT:

Я надсилаю визначення конкурентного ресурсу, отже, - результат, що випливає, точно такий, як визначено параметрами PUT. Кожен раз, коли я оновлюю ресурс однаковими параметрами PUT - отриманий стан точно такий же.

  • ПАТЧ:

Я надіслав лише частину визначення ресурсу, тому може трапитися, що інші користувачі тим часом оновлюють ДРУГІ параметри цього ресурсу. Отже, послідовні виправлення з однаковими параметрами та їх значення можуть призвести до різного стану ресурсів. Наприклад:

Припустимо, що об'єкт визначається наступним чином:

ЗКД: - колір: чорний, - тип: седан, - місць: 5

Я скріплюю це:

{color: 'red'}

Отриманим об'єктом є:

ЗКД: - колір: червоний, - тип: седан, - місць: 5

Потім деякі інші користувачі виправляють цей автомобіль:

{type: 'хетчбек'}

Отже, отриманим об'єктом є:

ЗКД: - колір: червоний, - тип: хетчбек, - місця: 5

Тепер, якщо я знову промацую цей об'єкт за допомогою:

{color: 'red'}

Отриманим об'єктом є:

ЗКД: - колір: червоний, - тип: хетчбек, - місця: 5

Який РІЗНИЙ у тому, що я раніше мав!

Ось чому PATCH не є ідентичним, тоді як PUT є безсильним.


1

На завершення дискусії про ідентифікацію я повинен зазначити, що можна визначити ідемпотенцію в контексті REST двома способами. Давайте спочатку формалізуємо кілька речей:

Ресурс є функцією з його кообласть є класом рядків. Іншими словами, ресурс - це підмножина String × Any, де всі клавіші унікальні. Назвемо клас ресурсів Res.

Операція REST на ресурсах - це функція f(x: Res, y: Res): Res. Два приклади операцій REST:

  • PUT(x: Res, y: Res): Res = x, і
  • PATCH(x: Res, y: Res): Res, який працює як PATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}.

(Це визначення розроблено спеціально для того, щоб сперечатися PUTі POST, наприклад, не має великого сенсу GETі POST, оскільки воно не дбає про наполегливість).

Тепер, виправляючи x: Res(інформаційно кажучи, використовуючи currying), PUT(x: Res)і PATCH(x: Res)виконують універсальні функції типу Res → Res.

  1. Функція g: Res → Resназивається глобально ідемпотентна , коли g ○ g == g, тобто для будь-якого y: Res, g(g(y)) = g(y).

  2. Нехай x: Resресурс, і k = x.keys. Функція g = f(x)називається лівою ідентифікацією , коли для кожного y: Resми маємо g(g(y))|ₖ == g(y)|ₖ. Це в основному означає, що результат повинен бути однаковим, якщо ми подивимось на застосовані клавіші.

Отже, PATCH(x)не є глобально ідентичним, але залишається безсильним. І тут важлива річ зліва idempotency: якщо ми виправляємо декілька клавіш ресурсу, ми хочемо, щоб ці ключі були однаковими, якщо ми знову виправляємо її, і нам не байдуже решта ресурсу.

І коли RFC говорить про те, що PATCH не є ідентичною, це говорить про глобальну ідентифікацію. Що ж, добре, що це не глобально ідентично, інакше це була б зламана операція.


Тепер відповідь Джейсона Хотгера намагається продемонструвати, що PATCH навіть не залишається безсильним, але він порушує занадто багато речей, щоб зробити це:

  • Перш за все, PATCH використовується на множині, хоча PATCH визначений для роботи над картами / словниками / об'єктами key-value.
  • Якщо хтось дійсно хоче застосувати PATCH до наборів, то існує природний переклад, який слід використовувати:, t: Set<T> → Map<T, Boolean>визначений за допомогою x in A iff t(A)(x) == True. Використовуючи це визначення, виправлення залишається ідентичним.
  • У прикладі цей переклад не використовувався, натомість PATCH працює як POST. Перш за все, чому для об’єкта створюється ідентифікатор? А коли воно генерується? Якщо об'єкт спочатку порівнюється з елементами набору, і якщо не знайдено відповідного об'єкта, тоді генерується ідентифікатор, потім знову програма повинна працювати по-іншому ( {id: 1, email: "me@site.com"}повинна відповідати {email: "me@site.com"}, інакше програма завжди порушена і PATCH не може пластир). Якщо ідентифікатор генерується перед тим, як перевірити набір, програма знову порушується.

Можна зробити приклади того, що PUT є невластивим, порушуючи половину речей, які розбиті в цьому прикладі:

  • Прикладом створених додаткових функцій може бути версія. Можна записувати кількість змін на одному об’єкті. У цьому випадку PUT не є ідентичним: PUT /user/12 {email: "me@site.com"}результати {email: "...", version: 1}вперше, а {email: "...", version: 2}вдруге.
  • Повозившись з ідентифікаторами, кожен раз може генерувати новий ідентифікатор кожного разу, коли об’єкт оновлюється, внаслідок чого неідентифікується PUT.

Усі вищенаведені приклади - природні приклади, з якими можна стикатися.


Моє остаточне питання полягає в тому, що PATCH не повинен бути глобально ідентичним , інакше не дасть бажаного ефекту. Ви хочете змінити адресу електронної пошти свого користувача, не торкаючись іншої інформації, і не хочете перезаписувати зміни іншої сторони, що отримує доступ до того ж ресурсу.


-1

Ще однією інформацією, яку я лише хочу додати, є те, що запит PATCH використовує меншу пропускну здатність порівняно з запитом PUT, оскільки лише частина даних надсилається не всій сутності. Тому просто використовуйте запит PATCH для оновлення конкретних записів, таких як (1-3 записи), а PUT-запит - для оновлення більшої кількості даних. Це так, не думайте занадто багато і не переживайте про це занадто.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.