REST API gerçek yaşam senaryolarında PUT ve PATCH yöntemlerinin kullanımı


681

Her şeyden önce, bazı tanımlar:

PUT, Bölüm 9.6 RFC 2616'da tanımlanmıştır :

PUT yöntemi, ekteki varlığın sağlanan Request-URI altında saklanmasını ister. İstek URI'si zaten var olan bir kaynağa başvuruyorsa, ekteki varlık , kaynak sunucuda bulunanın değiştirilmiş bir sürümü olarak düşünülmelidir . İstek URI'sı varolan bir kaynağı göstermiyorsa ve bu URI, istekte bulunan kullanıcı aracısı tarafından yeni bir kaynak olarak tanımlanabiliyorsa, kaynak sunucu bu URI ile kaynağı oluşturabilir.

PATCH, RFC 5789'da tanımlanmıştır :

PATCH yöntemi , istek varlığında açıklanan bir dizi değişikliğin Request-URI tarafından tanımlanan kaynağa uygulanmasını ister.

Ayrıca RFC 2616 Bölüm 9.1.2'ye göre, PATCH değilken PUT Idempotent'tir.

Şimdi gerçek bir örneğe bakalım. /usersVeriler ile POST yaptığımda {username: 'skwee357', email: 'skwee357@domain.com'}ve sunucu bir kaynak oluşturma yeteneğine sahip olduğunda, 201 ve kaynak konumu (varsayalım /users/1) ile yanıt verir ve bir sonraki GET çağrısı /users/1geri döner {id: 1, username: 'skwee357', email: 'skwee357@domain.com'}.

Şimdi e-postamı değiştirmek istediğimi varsayalım. E-posta değişikliği "bir dizi değişiklik" olarak kabul edilir ve bu nedenle /users/1" yama belgesi " ile YAMA yapmalıyım . Benim durumumda bu json belge olacaktır: {email: 'skwee357@newdomain.com'}. Sunucu daha sonra 200 değerini döndürür (iznin iyi olduğu varsayılarak). Bu beni ilk soruya getiriyor:

  • PATCH idempotent DEĞİLDİR. Bunu RFC 2616 ve RFC 5789'da söyledi. Ancak aynı PATCH isteğini (yeni e-postamla) yayınlarsam, aynı kaynak durumunu alırım (e-postam istenen değere değiştirilirken). PATCH neden idempotent değil?

PATCH nispeten yeni bir fiildir (RFC, Mart 2010'da tanıtıldı) ve bir dizi alanı "yamalama" ya da değiştirme sorununu çözmeye geliyor. PATCH kullanılmadan önce herkes kaynakları güncellemek için PUT kullandı. Ama PATCH piyasaya sürüldükten sonra, PUT'un ne için kullanıldığına dair beni şaşırttı. Bu da beni ikinci (ve ana) soruma getiriyor:

  • PUT ve PATCH arasındaki gerçek fark nedir? Bir yerde belirli bir kaynak altında tüm varlık yerine kullanılabilecek PUT okudum , bu yüzden bir tam varlık (PATCH gibi öznitelikler kümesi yerine) göndermek gerekir. Böyle bir durum için gerçek pratik kullanım nedir? Belirli bir kaynak URI'daki bir varlığı ne zaman değiştirmek / üzerine yazmak istersiniz ve böyle bir işlem neden varlığı güncellemek / yama yapmak olarak değerlendirilmiyor? PUT için gördüğüm tek pratik kullanım örneği, bir koleksiyona PUT vermek, yani /userstüm koleksiyonu değiştirmek. Belirli bir varlık üzerinde PUT yayınlamak PATCH başlatıldıktan sonra anlamsızdır. Yanlış mıyım?

1
a) 2612 değil RFC 2616'dır. b) RFC 2616 artık kullanılmamıştır, PUT'un şu anki teknik özellikleri greenbytes.de/tech/webdav/rfc7231.html#PUT , c) Sorunuzu almıyorum ; PUT'un sadece bir koleksiyonu değil, herhangi bir kaynağı değiştirmek için kullanılabileceği açık değildir, d) PATCH tanıtılmadan önce, insanlar genellikle POST, e) son olarak, evet, belirli bir PATCH isteği (yama biçimine bağlı olarak) idempotent olabilir ; sadece genel olarak değil.
Julian Reschke

eğer yardımcı olur ben PUT vs PATCH'in ilgili bir makale yazdı ettik eq8.eu/blogs/36-patch-vs-put-and-the-patch-json-syntax-war
equivalent8

5
Basit: POST, koleksiyonda bir öğe oluşturur. PUT, bir öğenin yerini alır. PATCH bir öğeyi değiştirir. POST yaparken, yeni öğenin URL'si yanıtta hesaplanır ve döndürülürken PUT ve PATCH, istekte bir URL gerektirir. Sağ?
Tom Russell

Bu yazı faydalı olabilir: POST vs PUT vs PATCH: mscharhag.com/api-design/http-post-put-patch
micha

Yanıtlar:


943

NOT : REST hakkında ilk kez okumak için zaman harcadığımda, idempotence doğru olmaya çalışmak için kafa karıştırıcı bir kavramdı. Daha fazla yorumun (ve Jason Hoetger'in cevabının ) gösterdiği gibi, orijinal cevabımda hala tam olarak doğru anlamadım . Bir süredir, Jason'ı etkili bir şekilde intihal etmekten kaçınmak için bu cevabı kapsamlı bir şekilde güncellemeye direndim, ancak şimdi düzenliyorum, çünkü, (yorumlarda) istendi.

Cevabımı okuduktan sonra, Jason Hoetger'in bu soruya mükemmel cevabını da okumanızı öneririm ve Jason'dan çalmadan cevabımı daha iyi hale getirmeye çalışacağım.

PUT neden idempotenttir?

RFC 2616 alıntıda belirttiğiniz gibi, PUT idempotent olarak kabul edilir. Bir kaynağı koyduğunuzda, bu iki varsayım yürürlüktedir:

  1. Bir koleksiyona değil bir objeye başvuruyorsunuz.

  2. Sağladığınız varlık tamamlandı ( tüm varlık).

Örneklerden birine bakalım.

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

Bu belgeyi /usersönerdiğiniz gibi POST yaparsanız , aşağıdaki gibi bir varlığı geri alabilirsiniz:

## /users/1

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

Bu varlığı daha sonra değiştirmek isterseniz PUT ve PATCH arasında seçim yaparsınız. Bir PUT şöyle görünebilir:

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

Aynı işlemi PATCH kullanarak da yapabilirsiniz. Bu şöyle görünebilir:

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

Bu ikisi arasında hemen bir fark göreceksiniz. PUT, bu kullanıcıdaki tüm parametreleri içeriyordu, ancak PATCH yalnızca değiştirileni ( email) içeriyordu .

PUT kullanırken, tüm varlığı gönderdiğiniz ve tam varlığın o URI'de bulunan herhangi bir varlığın yerini aldığı varsayılır . Yukarıdaki örnekte, PUT ve PATCH aynı hedefe ulaşmıştır: her ikisi de bu kullanıcının e-posta adresini değiştirir. Ancak PUT bunu tüm varlığı değiştirerek ele alırken PATCH yalnızca sağlanan alanları güncelleyerek diğerlerini yalnız bırakır.

PUT istekleri tüm varlığı içerdiğinden, aynı isteği tekrar tekrar verirseniz, her zaman aynı sonuca sahip olmalıdır (gönderdiğiniz veriler artık varlığın tüm verileridir). Bu nedenle PUT idempotenttir.

PUT kullanımı yanlış

Yukarıdaki PATCH verilerini bir PUT isteğinde kullanırsanız ne olur?

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!
}

(Bu sorunun amaçları için, sunucunun belirli bir zorunlu alana sahip olmadığını ve bunun olmasına izin vereceğini varsayıyorum ... gerçekte durum böyle olmayabilir.)

PUT kullandığımız için, ancak sadece tedarik ettiğimizden email, şimdi bu varlıktaki tek şey bu. Bu veri kaybına neden oldu.

Bu örnek açıklama amaçlıdır - bunu asla yapma. Bu PUT isteği teknik olarak idempotenttir, ancak bu korkunç, kırık bir fikir olmadığı anlamına gelmez.

PATCH nasıl idempotent olabilir?

Yukarıdaki örnekte, yama olarak İdempotent. Bir değişiklik yaptınız, ancak aynı değişikliği tekrar tekrar yaptıysanız, her zaman aynı sonucu verir: e-posta adresini yeni değere değiştirdiniz.

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
}

Orijinal örneğim, doğruluk için düzeltildi

Başlangıçta, idempotency olmadığını düşündüğüm örnekler vardı, ama yanıltıcı / yanlış. Örnekleri saklayacağım, ancak bunları farklı bir şeyi göstermek için kullanacağım: aynı varlığa karşı birden fazla PATCH belgesi, farklı nitelikleri değiştirerek, PATCH'leri idempotent yapmaz.

Diyelim ki geçmiş bir zamanda bir kullanıcı eklendi. Bu sizin başladığınız durumdur.

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

Bir PATCH sonrasında, değiştirilmiş bir varlığınız var:

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"
}

Daha sonra PATCH'inizi tekrar tekrar uygularsanız, aynı sonucu almaya devam edersiniz: e-posta yeni değere değiştirildi. A giriyor, A çıkıyor, bu yüzden bu idempotent.

Bir saat sonra, biraz kahve yapmaya ve ara vermeye gittikten sonra, başka biri kendi YAMA ile birlikte gelir. Postane bazı değişiklikler yapıyor gibi görünüyor.

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
}

Postaneden gelen bu PATCH, e-posta ile ilgilenmediğinden, yalnızca posta kodu, tekrar tekrar uygulanırsa, aynı sonucu alır: posta kodu yeni değere ayarlanır. A girer, A çıkar, bu yüzden bu da idempotenttir.

Ertesi gün, PATCH'inizi tekrar göndermeye karar veriyorsunuz.

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"
}

Düzeltme ekiniz dünle aynı etkiye sahip: e-posta adresini ayarladı. A girdi, A çıktı, bu yüzden bu da idempotent.

Orijinal cevabımda neyi yanlış yaptım

Önemli bir ayrım yapmak istiyorum (orijinal cevabımda yanlış yaptığım bir şey). Birçok sunucu, değişikliklerinizle (varsa) yeni varlık durumunu geri göndererek REST isteklerinize yanıt verir. Yani, bu yanıtı geri aldığınızda, dün aldığınız yanıttan farklıdır , çünkü posta kodu son kez aldığınız yanıt değildir. Ancak, isteğiniz posta kodu ile değil, yalnızca e-posta ile ilgiliydi. Dolayısıyla, PATCH belgeniz hala idempotent - PATCH'de gönderdiğiniz e-posta artık varlığın e-posta adresidir.

Peki PATCH ne zaman idempotent değil?

Bu sorunun tam bir tedavisi için sizi tekrar Jason Hoetger'in cevabına yönlendiriyorum . Sadece bu konuda bırakacağım, çünkü dürüstçe bu kısma zaten sahip olduğundan daha iyi cevap verebileceğimi sanmıyorum.


2
Bu cümle pek doğru değil: "Ama bu ikna edici: A ne zaman girerse, B her zaman çıkar". Örneğin GET /users/1, Postane posta kodunu güncellemeden önce olsaydınız ve Postane güncellemesinden sonra aynı GET /users/1isteği tekrar yaparsanız, iki farklı yanıt (farklı posta kodları) alırsınız. Aynı "A" (GET isteği) giriyor, ancak farklı sonuçlar alıyorsunuz. Yine de GET hala idempotent.
Jason Hoetger

@JasonHoetger GET güvenlidir (hiçbir değişikliğe neden olmadığı varsayılır), ancak her zaman idempotent değildir. Bir fark var. Bkz. RFC 2616 sn. 9.1 .
Dan Lowe

1
@DanLowe: Kesinlikle idempotent olması garanti. Tam olarak RFC 2616'nın 9.1.2 Bölümünde ve güncellenmiş spesifikasyonda RFC 7231 bölüm 4.2.2'de "Bu spesifikasyon tarafından tanımlanan talep yöntemlerinden PUT, DELETE ve güvenli talep yöntemlerinin idempotent olduğunu" belirtir. İdempotence sadece "aynı talebi her yaptığınızda aynı cevabı alırsınız" anlamına gelmez. 7231 4.2.2 şöyle devam ediyor: "Özgün istek başarılı olsa bile, isteğin
yinelenmesi

1
@JasonHoetger Bunu kabul edeceğim, ancak PUT ve PATCH'ı tartışan ve asla GET'ten bahsetmeyen bu cevapla ne yapması gerektiğini görmüyorum ...
Dan Lowe

1
Ah, @ JasonHoetger'den gelen yorum bu açıklığa kavuştu: çoklu idempotent yöntem isteklerinin yanıtlar yerine yalnızca sonuçlanan durumlarının aynı olması gerekir.
Tom Russell

329

Dan Lowe'un mükemmel cevabı OP'nin PUT ve PATCH arasındaki fark hakkındaki sorusunu çok iyi cevaplamış olsa da, PATCH'ın neden idempotent olmadığı sorusuna cevabı oldukça doğru değil.

PATCH'ın neden idempotent olmadığını göstermek için idempotence tanımıyla ( Wikipedia'dan ) başlamaya yardımcı olur :

İdempotent terimi, bir veya birkaç kez yürütüldüğünde aynı sonuçları üretecek bir işlemi tanımlamak için daha kapsamlı bir şekilde kullanılır [...] Bir idempotent işlevi, f (f (x)) = f (x) özelliğine sahip bir işlevdir. herhangi bir değer x.

Daha erişilebilir bir dilde, bir idempotent PATCH şu şekilde tanımlanabilir: Bir düzeltme eki belgesine sahip bir kaynak PATCH işleminden sonra, aynı düzeltme eki belgesiyle aynı kaynağa yapılan tüm PATCH çağrıları kaynağı değiştirmez.

Tersine, idempotent olmayan bir işlem, f (f (x))! = F (x) 'in olduğu bir işlemdir; aynı yama belge yapmak kaynağı değiştirin.

İdempotent olmayan bir PATCH'i göstermek için bir / users kaynağı olduğunu ve çağrının GET /usersşu anda bir kullanıcı listesi döndürdüğünü varsayalım :

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

OP örneğinde olduğu gibi PATCHing / users / {id} yerine sunucunun PATCHing / kullanıcılara izin verdiğini varsayalım. Bu PATCH isteğini yayınlayalım:

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

Düzeltme eki belgemiz, sunucuya kullanıcı newuserlistesine yeni bir kullanıcı eklemesini bildirir. Bunu ilk kez aradıktan sonra GET /usersgeri dönecekti:

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

Şimdi, yukarıdaki ile aynı PATCH isteğini verirsek , ne olur? (Bu örnek uğruna, / users kaynağının yinelenen kullanıcı adlarına izin verdiğini varsayalım.) "Op", "add" dir, bu nedenle listeye yeni bir kullanıcı eklenir ve sonraki GET /usersdöndürür:

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

/ Kullanıcıların Kaynak değişti yine biz yayınlanan rağmen aynı karşı PATCH'i aynı son nokta. PATCH'imiz f (x) ise, f (f (x)) f (x) ile aynı değildir ve bu nedenle, bu özel PATCH idempotent değildir .

PATCH'nin idempotent olduğu garanti edilemese de, PATCH belirtiminde, sunucunuzdaki tüm PATCH işlemlerini yapmanıza engel olacak hiçbir şey yoktur. RFC 5789, idempotent PATCH isteklerinden de avantajlar bekliyor:

PATCH isteği, aynı kaynak üzerinde benzer bir zaman çerçevesinde iki PATCH isteği arasındaki çarpışmalardan kaynaklanan kötü sonuçların önlenmesine yardımcı olacak şekilde idempotent olacak şekilde verilebilir.

Dan'ın örneğinde, PATCH operasyonu aslında idempotenttir. Bu örnekte, / users / 1 varlığı PATCH isteklerimiz arasında değişti, ancak PATCH isteklerimiz nedeniyle değişmedi ; aslında Postanenin posta kodunun değişmesine neden olan farklı yama belgesiydi. Postanenin farklı YAMA farklı bir işlemdir; PATCH'imiz f (x) ise, Postanenin PATCH'ı g (x) olur. İdempotence bunu belirtir f(f(f(x))) = f(x), ancak hiçbir garanti vermez f(g(f(x))).


11
Sunucunun aynı zamanda PUT verilmesine izin verdiğini varsayarsak /users, bu da PUT'u idempotent yapmaz. Tüm bunlar, sunucunun istekleri karşılamak için nasıl tasarlandığıdır.
Uzair Sajid

13
Böylece, sadece PATCH işlemleriyle bir API oluşturabiliriz. Peki, Kaynaklar üzerinde CRUD eylemleri yapmak için http VERBS kullanma REST ilkesi ne olur? Burada PATCH beyefendisi beylerini fazla karmaşıklaştırmıyor muyuz?
bohr

6
PUT bir koleksiyona (örn. /users) Uygulanırsa, herhangi bir PUT isteği bu koleksiyonun içeriğinin yerini almalıdır. Yani bir PUT /usersbir kullanıcı koleksiyonu beklemek ve diğerlerini silmek gerekir. Bu idempotent. / Users uç noktasında böyle bir şey yapmanız muhtemel değildir. Ancak böyle bir şey /users/1/emailsbir koleksiyon olabilir ve tüm koleksiyonun yenisiyle değiştirilmesine izin vermek tamamen geçerli olabilir.
Vectorjohn

5
Bu cevap harika bir idempotence örneği sunsa da, bunun tipik REST senaryolarındaki suları çamurlayabileceğine inanıyorum. Bu durumda op, belirli sunucu tarafı mantığını tetikleyen ek eylem içeren bir PATCH isteğiniz vardır . Bu, sunucunun ve istemcinin opalanın sunucu tarafı iş akışlarını tetiklemesi için iletilecek belirli değerlerden haberdar olmasını gerektirir . Daha basit REST senaryolarında, bu tür opişlevsellik kötü bir uygulamadır ve büyük olasılıkla doğrudan HTTP fiilleri aracılığıyla ele alınmalıdır.
ivandov

7
Bir koleksiyona karşı bir YAMA, sadece POST ve DELETE vermeyi düşünmezdim. Bu gerçekten hiç yapıldı mı? Bu nedenle PATCH tüm pratik amaçlar için idempotent kabul edilebilir mi?
Tom Russell

72

Bunu da merak ettim ve birkaç ilginç makale buldum. Sorunuza tam olarak cevap vermeyebilirim, ancak bu en azından biraz daha bilgi sağlar.

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

HTTP RFC, PUT'un istek varlığı olarak tamamen yeni bir kaynak temsili alması gerektiğini belirtir. Bu, örneğin yalnızca belirli özellikler sağlanırsa, bunların kaldırılması (yani null olarak ayarlanması) gerektiği anlamına gelir.

Bu göz önüne alındığında, bir PUT tüm nesneyi göndermelidir. Örneğin,

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

Bu, e-postayı etkin bir şekilde güncelleyecektir. PUT'un çok etkili olmamasının nedeni, tek bir alanı gerçekten değiştirdiğiniz ve kullanıcı adını dahil etmenin bir tür faydasız olmasıdır. Bir sonraki örnek farkı gösterir.

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

Şimdi, PUT, spesifikasyona göre tasarlanmışsa, PUT kullanıcı adını null olarak ayarlar ve aşağıdakileri geri alırsınız.

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

PATCH kullandığınızda, yalnızca belirttiğiniz alanı günceller ve kalanını örneğinizde olduğu gibi yalnız bırakırsınız.

Aşağıdaki YAMA, daha önce hiç görmediğimden biraz farklı.

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

PUT ve PATCH istekleri arasındaki fark, sunucunun ekteki varlığı İstek-URI tarafından tanımlanan kaynağı değiştirmek için işleme biçiminde yansıtılır. Bir PUT isteğinde, ekteki varlık kaynak sunucuda depolanan kaynağın değiştirilmiş bir sürümü olarak kabul edilir ve istemci depolanan sürümün değiştirilmesini ister. Ancak PATCH ile ekteki varlık, kaynak sunucuda bulunan bir kaynağın yeni bir sürüm üretmek için nasıl değiştirilmesi gerektiğini açıklayan bir dizi talimat içerir. PATCH yöntemi, İstek URI'sı tarafından tanımlanan kaynağı etkiler ve ayrıca diğer kaynaklar üzerinde yan etkileri olabilir; başka bir deyişle, bir PATCH uygulamasıyla yeni kaynaklar oluşturulabilir veya mevcut kaynaklar değiştirilebilir.

PATCH /users/123

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

PATCH'ı az çok bir alanı güncellemenin bir yolu olarak görüyorsunuz. Kısmi nesnenin üzerinden göndermek yerine işlemi gönderirsiniz. ie E-postayı değerle değiştirin.

Makale bununla bitiyor.

Fielding'in tezi kaynakları kısmen değiştirmek için herhangi bir yol tanımlamadığından PATCH'ın gerçekten REST API'leri için gerçekten tasarlanmadığını belirtmek gerekir. Ancak, Roy Fielding, PATCH'nin ilk HTTP / 1.1 teklifi için yarattığı bir şey olduğunu söyledi, çünkü kısmi PUT asla RESTful değil. Tam bir temsili aktarmadığınızdan emin olun, ancak REST yine de temsillerin tamamlanmasını gerektirmez.

Şimdi, birçok yorumcunun belirttiği gibi, özellikle makaleye katılıp katılmadığımı bilmiyorum. Kısmi bir sunum üzerinden göndermek, değişikliklerin açıklaması olabilir.

Benim için PATCH kullanıyorum. Şimdiye kadar fark ettiğim tek gerçek fark, PUT'un eksik değerleri null olarak ayarlaması gerektiğinden, çoğunlukla PUT'u PATCH olarak ele alacağım. Bunu yapmanın 'en doğru' yolu değil, iyi şans kodlaması mükemmel olabilir.


7
Eklemeye değer olabilir: William Durand'ın makalesinde (ve rfc 6902) "op" un "add" olduğu örnekler vardır. Bu açıkçası idempotent değil.
Johannes Brodwall

2
Veya daha kolay hale getirebilir ve bunun yerine RFC 7396 Birleştirme Yamasını kullanabilir ve JSON yamasını oluşturmaktan kaçınabilirsiniz.
Piotr Kula

nosql tabloları için yama ve put arasındaki farklar önemlidir, çünkü nosql'de sütun yoktur
stackdave

18

PUT ve PATCH arasındaki fark şudur:

  1. PUT'un idempotent olması gerekir. Bunu başarmak için, kaynağın tamamını istek gövdesine koymanız gerekir.
  2. PATCH, idempotent olmayabilir. Bu, tarif ettiğiniz durumlar gibi bazı durumlarda da idempotent olabileceğini ima eder.

PATCH, sunucuya kaynağı nasıl değiştireceğini bildirmek için bazı "yama dili" gerektirir. Arayanın ve sunucunun "ekle", "değiştir", "sil" gibi bazı "işlemleri" tanımlaması gerekir. Örneğin:

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",
}

Açık "işlem" alanları kullanmak yerine, yama dili aşağıdaki gibi kuralları tanımlayarak onu örtük hale getirebilir:

PATCH istek gövdesinde:

  1. Bir alanın varlığı, o alanı "değiştir" veya "ekle" anlamına gelir.
  2. Bir alanın değeri null ise, o alanı silmek anlamına gelir.

Yukarıdaki kural ile, örnekteki PATCH aşağıdaki formu alabilir:

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

Hangi daha özlü ve kullanıcı dostu görünüyor. Ancak kullanıcıların temel sözleşmenin farkında olması gerekir.

Yukarıda bahsettiğim operasyonlarla, PATCH hala idempotent. Ancak, "artış" veya "ekle" gibi işlemleri tanımlarsanız, artık idempotent olmayacağını kolayca görebilirsiniz.


7

TLDR - Salak Versiyon

PUT => Varolan bir kaynak için tüm yeni öznitelikleri ayarlayın.

PATCH => Varolan bir kaynağı kısmen güncelle (tüm öznitelikler gerekli değildir).


3

Daha önceki yorumlarda belirtilmiş olan RFC 7231 bölüm 4.2.2'yi daha yakından alıntılayıp yorumlayayım :

Bu yöntemle birden çok özdeş isteğin sunucu üzerindeki amaçlanan etkisi, bu tür tek bir isteğin etkisi ile aynı ise, bir istek yöntemi "idempotent" olarak kabul edilir. Bu spesifikasyon ile tanımlanan talep yöntemlerinden PUT, DELETE ve güvenli talep yöntemleri idempotenttir.

(...)

İstemci sunucunun yanıtını okuyabilmeden önce bir iletişim hatası oluşursa istek otomatik olarak yinelenebildiğinden, idempotent yöntemler ayırt edilir. Örneğin, bir istemci bir PUT isteği gönderirse ve herhangi bir yanıt alınmadan temel bağlantı kapatılırsa, istemci yeni bir bağlantı kurabilir ve idempotent isteği yeniden deneyebilir. Orijinal istek başarılı olsa bile, isteğin yinelenmesinin aynı amaçlanan etkiye sahip olacağını bilir, ancak yanıt farklı olabilir.

Peki, idempotent bir yöntemin tekrar tekrar talep edilmesinden sonra "aynı" ne olmalıdır? Sunucu durumu veya sunucu yanıtı değil , amaçlanan etki . Özellikle, yöntem "müşterinin bakış açısından" idempotent olmalıdır. Şimdi, bu bakış açısının Dan Lowe'un burada intihal etmek istemediğim cevabındaki son örneğinin gerçekten bir PATCH isteğinin (örnekteki örnekten daha doğal bir şekilde) Jason Hoetger'in cevabı ).

Gerçekten de, ilk müşteri için açık bir olası niyet oluşturarak örneği biraz daha kesin hale getirelim . Diyelim ki bu müşteri, e-postalarını ve posta kodlarını kontrol etmek için projeye sahip kullanıcıların listesini inceliyor. Kullanıcı 1 ile başlar, zip'in doğru olduğunu ancak e-postanın yanlış olduğunu fark eder. Bunu tamamen meşru olan bir PATCH isteğiyle düzeltmeye karar verir ve yalnızca gönderir

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

çünkü bu tek düzeltme. Şimdi, bazı ağ sorunları nedeniyle istek başarısız oluyor ve birkaç saat sonra otomatik olarak yeniden gönderiliyor. Bu arada, başka bir istemci (yanlışlıkla) kullanıcı 1'in zipini değiştirdi. Daha sonra, aynı PATCH isteğini ikinci kez göndermek amaçlanan etkiyi elde etmiyor yanlış bir zip ile sonuçlandığımız için istemcinin . Bu nedenle yöntem, RFC açısından idempotent değildir.

Müşteri bunun yerine e-postayı düzeltmek için bir PUT isteği kullanırsa, sunucuya e-posta ile birlikte 1. kullanıcının tüm özelliklerini gönderirse, isteğinin daha sonra yeniden gönderilmesi ve 1. kullanıcı değiştirilse bile amaçlanan etkisi elde edilir. Bu arada --- ikinci PUT isteği ilk istekden sonra tüm değişikliklerin üzerine yazacağından.


2

Benim düşünceme göre, idempotence:

  • KOYMAK:

Bir rekabet kaynağı tanımı gönderiyorum, bu yüzden - ortaya çıkan kaynak durumu tam olarak PUT parametreleri tarafından tanımlandığı gibidir. Her seferinde kaynağı aynı PUT parametreleriyle güncellediğimde ortaya çıkan durum tamamen aynıdır.

  • YAMA:

Kaynak tanımının yalnızca bir bölümünü gönderdim, bu nedenle diğer kullanıcılar bu kaynağın DİĞER parametrelerini bir arada güncelliyor olabilir. Sonuç olarak - aynı parametrelere ve değerlerine sahip ardışık yamalar farklı kaynak durumuyla sonuçlanabilir. Örneğin:

Aşağıdaki gibi tanımlanan bir nesneyi varsayalım:

ARAÇ: - renk: siyah, - tip: sedan, - koltuklar: 5

Ben ile yama:

{kırmızı renk'}

Ortaya çıkan nesne:

ARAÇ: - renk: kırmızı, - tip: sedan, - koltuklar: 5

Ardından, diğer bazı kullanıcılar bu arabayı yamalarıyla:

{type: 'hatchback'}

yani, ortaya çıkan nesne:

ARAÇ: - renk: kırmızı, - tip: hatchback, - koltuklar: 5

Şimdi, ben bu nesneyi tekrar ile yama:

{kırmızı renk'}

ortaya çıkan nesne:

ARAÇ: - renk: kırmızı, - tip: hatchback, - koltuklar: 5

Daha önce aldığımdan FARKLI olan nedir!

Bu yüzden PUT idempotent iken PATCH idempotent değildir.


1

İdempotency ile ilgili tartışmayı sonuçlandırmak için, REST bağlamında idempotency'yi iki şekilde tanımlayabileceğine dikkat etmeliyim. Önce birkaç şeyi resmileştirelim:

Bir kaynak olarak değer kümesi dizeleri sınıf olmak bir işlevdir. Başka bir deyişle, kaynak String × Anytüm anahtarların benzersiz olduğu bir alt kümesidir . Kaynakların sınıfını arayalım Res.

Kaynaklar üzerinde bir REST işlemi, bir işlevdir f(x: Res, y: Res): Res. REST işlemlerine iki örnek:

  • PUT(x: Res, y: Res): Res = x, ve
  • PATCH(x: Res, y: Res): Resgibi çalışır PATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}.

(Bu tanım özellikle hakkında tartışmak için tasarlanmıştır PUTve POSTve örneğin çok mantıklı değil GETve POSTo sebat umursamayan olarak).

Şimdi, sabitleyerek x: Res(gayri resmi olarak konuşarak, köri kullanarak) PUT(x: Res)ve PATCH(x: Res)türün tek değişkenli işlevleridir Res → Res.

  1. Bir fonksiyon g: Res → Resolarak isimlendirilir küresel İdempotent zaman, g ○ g == gherhangi bir deyişle, y: Res, g(g(y)) = g(y).

  2. x: ResBir kaynak olsun ve k = x.keys. Bir fonksiyona sol idempotentg = f(x) denir , her biri için elimizde . Uygulanan tuşlara bakarsak, temelde sonucun aynı olması gerektiği anlamına gelir.y: Resg(g(y))|ₖ == g(y)|ₖ

Yani, PATCH(x)küresel olarak idempotent değil, idempotent bırakılır. Ve sol idempotency burada önemli olan şeydir: kaynağın birkaç anahtarını yama yaparsak, tekrar yama yaparsak bu anahtarların aynı olmasını isteriz ve kaynağın geri kalanını umursamayız.

Ve RFC PATCH'nin idempotent olmamasından bahsederken, küresel idempotency'den bahsediyor. Küresel olarak idempotent olmaması iyidir, aksi takdirde kırık bir operasyon olurdu.


Şimdi, Jason Hoetger'in cevabı PATCH'ın idempotent bile kalmadığını göstermeye çalışıyor, ancak bunu yapmak için çok fazla şey kırıyor:

  • Her şeyden önce, PATCH bir kümede kullanılır, ancak PATCH haritalar / sözlükler / anahtar / değer nesneleri üzerinde çalışacak şekilde tanımlanır.
  • Birisi gerçekten setlere PATCH uygulamak istiyorsa, kullanılması gereken doğal bir çeviri vardır: t: Set<T> → Map<T, Boolean>ile tanımlanır x in A iff t(A)(x) == True. Bu tanımı kullanarak, yama yapmak idempotent kalır.
  • Örnekte, bu çeviri kullanılmamıştır, bunun yerine PATCH bir POST gibi çalışır. Her şeyden önce, nesne için neden bir kimlik oluşturulur? Ve ne zaman üretilir? Nesne ilk olarak kümenin öğeleriyle karşılaştırılırsa ve eşleşen bir nesne bulunmazsa, kimlik oluşturulursa, program tekrar farklı çalışmalıdır ( {id: 1, email: "me@site.com"}eşleşmelidir {email: "me@site.com"}, aksi takdirde program her zaman bozulur ve PATCH muhtemelen yama). Sete karşı kontrol etmeden önce ID üretilirse, yine program bozulur.

Bu örnekte kırılan şeylerin yarısını kırmakla PUT'un idempotent olmamasının örnekleri olabilir:

  • Oluşturulan ek özelliklere sahip bir örnek sürüm oluşturmadır. Tek bir nesne üzerindeki değişikliklerin sayısı kaydedilebilir. Bu durumda, PUT idempotent değildir: ilk kez PUT /user/12 {email: "me@site.com"}sonuç verir {email: "...", version: 1}ve{email: "...", version: 2} ikinci kez .
  • Kimliklerle uğraşmak, nesne her güncellendiğinde yeni bir kimlik üreterek idempotent olmayan bir PUT ile sonuçlanabilir.

Yukarıdaki örneklerin tümü karşılaşabileceğiniz doğal örneklerdir.


Son olarak, PATCH'ın küresel olarak idempotent olmaması gerekir , aksi takdirde size istenen etkiyi vermez. Bilgilerin geri kalanına dokunmadan, kullanıcının e-posta adresini değiştirmek istiyorsunuz ve aynı kaynağa erişen başka bir tarafın değişikliklerinin üzerine yazmak istemiyorsunuz.


-1

Ekleyeceğim bir ek bilgi, bir PATCH isteğinin bir PUT isteğine kıyasla daha az bant genişliği kullanmasıdır, çünkü verilerin yalnızca bir kısmı tüm varlık değil gönderilir. Bu nedenle (1-3 kayıt) gibi belirli kayıtların güncellenmesi için bir PATCH isteği kullanın, daha fazla miktarda veriyi güncellemek için PUT isteği. Bu kadar, çok fazla düşünme ya da çok fazla endişelenme.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.