DİNLENME - kimlikleri vücuda koyun veya koymayın?


107

Diyelim ki, müşteriler için, müşterinin kimlik atayabileceği bir RESTful kaynağa sahip olmak istiyorum.

Bir kişi şuna benzer: {"id": <UUID>, "name": "Jimmy"}

Şimdi, müşteri bunu nasıl kaydetmeli (veya "PUT")?

  1. PUT /person/UUID {"id": <UUID>, "name": "Jimmy"} - şimdi her zaman doğrulamamız gereken bu çirkin kopyaya sahibiz: Vücuttaki kimlik yoldakiyle eşleşiyor mu?
  2. Asimetrik gösterim:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID İadeler {"id": <UUID>, "name": "Jimmy"}
  3. Gövdede kimlik yok - yalnızca konumda kimlik:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID İadeler {"name": "Jimmy"}
  4. POSTKimlik müşteri tarafından oluşturulduğu için hiçbir şey iyi bir fikir gibi görünmüyor.

Bunu çözmenin yaygın kalıpları ve yolları nelerdir? Yalnızca konumdaki kimlikler dogmatik olarak en doğru yol gibi görünse de, pratik uygulamayı da zorlaştırır.

Yanıtlar:


68

Farklı okuma / yazma modellerine sahip olmanın yanlış bir tarafı yoktur: İstemci, bir kaynak gösterimini yazabilir, burada sunucu, eklenen / hesaplanan öğelerle başka bir gösterimi (veya hatta tamamen farklı bir temsili) geri döndürebilir (hatta tamamen farklı bir temsil - buna karşı herhangi bir özellik yoktur) , tek gereksinim, PUT'un kaynağı oluşturması veya değiştirmesidir).

Bu yüzden (2) 'deki asimetrik çözümü seçerim ve şunları yazarken sunucu tarafındaki "kötü çoğaltma kontrolünden" kaçınırdım:

PUT /person/UUID {"name": "Jimmy"}

GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

2
Ve yazarak (statik veya dinamik) uygularsanız, kimliği olmayan modellere zahmetsizce sahip olamazsınız ... Bu nedenle, PUT istekleri için kimliği URL'den kaldırmak çok daha kolaydır. "Huzurlu" olmayacak ama doğru olacak.
Ivan Kleshnin

2
idKimlik ve varlık ve ek dönüştürücüler ile birlikte TO olmadan ve programcılar için çok büyük ek yükler olmadan ek TO tutun .
Grigory Kislin

Kimliği BODY'den alırsam ne olur örn .: PUT / kişi {"id": 1, "ad": "Jimmy"}. Bu kötü bir uygulama olur mu?
Bruno Santos

Kimliği bedene koymak iyi olur. Tam sayı yerine kimlik için bir GUID kullanın - aksi takdirde yinelenen kimlik riskiyle karşılaşırsınız.
Jørn Wildt

Bu yanlış. Cevabımı gör. PUT, tüm kaynağı içermelidir. Kimliği hariç tutmak ve kaydın yalnızca bazı kısımlarını güncellemek istiyorsanız PATCH kullanın.
CompEng88

27

Halka açık bir API ise, yanıt verirken muhafazakar olmalısınız, ancak serbestçe kabul etmelisiniz.

Bununla demek istediğim, hem 1 hem de 2'yi desteklemelisin. 3'ün mantıklı olmadığına katılıyorum.

Hem 1 hem de 2'yi desteklemenin yolu, istek gövdesinde hiçbiri sağlanmamışsa url'den kimliği almak ve istek gövdesindeyse url'deki kimlik ile eşleştiğini doğrulamaktır. İkisi eşleşmezse, 400 Kötü İstek yanıtı döndürün.

Bir kişi kaynağını döndürürken muhafazakar olun ve koymada isteğe bağlı olsa bile, kimliği her zaman json'a ekleyin.


3
Kabul edilen çözüm bu olmalıdır. Bir API her zaman kullanıcı dostu olmalıdır. Vücutta isteğe bağlı olmalıdır. Bir POST'tan bir ID almamalı ve ardından bunu bir PUT'ta tanımsız hale getirmem gerekiyor. Ayrıca, yapılan 400 yanıt noktası tam da doğru.
Michael

Yaklaşık 400 kod ayrıca softwareengineering.stackexchange.com/questions/329229/… tartışmasına da bakın . Kısacası, 400 kodu uygunsuz değildir, daha az spesifiktir, 422 ile karşılaştırın.
Grigory Kislin

8

Bu sorunun bir çözümü, biraz kafa karıştırıcı olan "Hypertext As The Engine of Application State" veya "HATEOAS" kavramını içeriyor. Bu, bir REST yanıtının mevcut kaynakları veya köprüler olarak gerçekleştirilecek eylemleri içerdiği anlamına gelir. Orijinal REST anlayışının bir parçası olan bu yöntemi kullanarak, kaynakların benzersiz tanımlayıcıları / kimliklerinin kendileri birer köprüdür. Yani, örneğin, aşağıdaki gibi bir şeye sahip olabilirsiniz:

GET /person/<UUID> {"person": {"location": "/person/<UUID>", "data": { "name": "Jimmy"}}}

Ardından, bu kaynağı güncellemek istiyorsanız şunları yapabilirsiniz (sözde kod):

updatedPerson = person.data
updatedPerson.name = "Timmy"
PUT(URI: response.resource, data: updatedPerson)

Bunun bir avantajı, istemcinin sunucunun Kullanıcı Kimliklerinin dahili temsili hakkında herhangi bir fikre sahip olmamasıdır. İstemcinin bunları keşfetme yolu olduğu sürece kimlikler değişebilir ve hatta URL'lerin kendisi de değişebilir. Örneğin, bir insan topluluğu alırken, şöyle bir yanıt döndürebilirsiniz:

GET /people
{ "people": [
    "/person/1",
    "/person/2"
  ]
}

(Tabii ki, başvurunun ihtiyaçlarına bağlı olarak her kişi için tam kişi nesnesini de iade edebilirsiniz).

Bu yöntemle nesnelerinizi daha çok kaynaklar ve konumlar açısından, daha az kimlik açısından düşünürsünüz. Benzersiz tanımlayıcının dahili temsili böylece istemci mantığınızdan ayrıştırılır. Bu, REST'in arkasındaki ilk itici güçtü: HTTP özelliklerini kullanarak, daha önce var olan RPC sistemlerinden daha gevşek bir şekilde bağlanmış istemci-sunucu mimarileri oluşturmak. HATEOAS hakkında daha fazla bilgi için Wikipedia makalesine ve bu kısa makaleye bakın .


4

Bir ekte, URL'ye kimliği eklemeniz gerekmez. Bu şekilde, bir PUT'ta bir kimlik gönderirseniz, birincil anahtarı değiştirmek için GÜNCELLEME olarak yorumlayabilirsiniz.

  1. EKLEYİN:

    PUT /persons/ 
      {"id": 1, "name": "Jimmy"}
    HTTP/1.1 201 Created     
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}
    
    GET /persons/1
    
    HTTP/1.1 200 OK
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}  
    
  2. GÜNCELLEME

    PUT /persons/1 
         {"id": "2", "name": "Jimmy Jr"} - 
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="filled_by_server"}
    
    GET /persons/2 
    
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="updated_by_server"}
    

JSON API bu standardı ve çözer yeni nesneye bir bağlantı ile eklenen veya güncellenen nesneyi dönen bazı sorunlar kullanır. Bazı güncellemeler veya ekler, ek alanları değiştirecek bazı iş mantığı içerebilir

Ekleme ve güncellemeden sonra alma işleminden kurtulabileceğinizi de göreceksiniz.


3

Bu daha önce sorulmuştu - tartışma bir göz atmaya değer:

RESTful GET yanıtı bir kaynağın kimliğini döndürmeli mi?

Bu, neyin "RESTful" olup olmadığına dair tartışmaya girmenin kolay olduğu sorulardan biridir .

Ne olursa olsun, tutarlı kaynaklar açısından düşünmeye çalışıyorum ve bunların tasarımını yöntemler arasında değiştirmemeye çalışıyorum. Bununla birlikte, IMHO, kullanılabilirlik açısından en önemli şey, tüm API'de tutarlı olmanızdır!


2

Farklı operasyonlar için farklı temsillere sahip olmak normal olsa da, PUT için genel bir öneri , BÜTÜN yükü içermektir . Bu, onun idda orada olması gerektiği anlamına gelir . Aksi takdirde, PATCH kullanmalısınız.

Bunu söyledikten sonra, PUT'un çoğunlukla güncellemeler için kullanılması idgerektiğini ve her zaman URL'de de iletilmesi gerektiğini düşünüyorum. Bunun sonucunda, kaynak tanımlayıcısını güncellemek için PUT kullanmak kötü bir fikirdir. idURL’de olandan farklı olabileceği zaman bizi istenmeyen bir durumda bırakır .id vücutta olandan .

Öyleyse, böyle bir çatışmayı nasıl çözeriz? Temelde 2 seçeneğimiz var:

  • 4XX istisnası at
  • bir Warning( X-API-Warnvb.) başlık ekleyin .

Bu soruyu cevaplamaya olabildiğince yakın çünkü konu genel olarak bir fikir meselesidir.


1

Farklı yaklaşımlar kullanmanın kötü bir yanı yoktur. ama bence en iyi yol 2. ile çözüm .

 PUT /person/UUID {"name": "Jimmy"}

 GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

çoğunlukla bu şekilde kullanılır , varlık çerçevesi bile bu tekniği , varlık dbContext'e eklendiğinde, üretilen ID'siz sınıf, Entity Framework referans tarafından oluşturulan ID'dir.


1

Buna JSON-LD / Anlamsal Web açısından bakıyorum çünkü bu slaytlarda ana hatlarıyla belirttiğim gibi gerçek REST uyumluluğunu elde etmenin iyi bir yolu . Bu perspektiften bakıldığında, bir Web kaynağının ID'si (IRI) her zaman kaynağa bakmak / referansını kaldırmak için kullanabileceğim URL'ye eşit olması gerektiğinden, seçenek (1) için gidecek bir soru yoktur. Doğrulamanın uygulanmasının gerçekten zor olmadığını ve sayısal olarak yoğun olmadığını düşünüyorum; bu yüzden bunu seçenek (2) ile devam etmek için geçerli bir neden olarak görmüyorum. POST (yeni oluştur), PUT (güncelle / değiştir) 'den farklı bir semantiğe sahip olduğundan, seçenek (3.) gerçekten bir seçenek değildir.


1

Bilginize, buradaki cevaplar yanlış.

Görmek:

https://restfulapi.net/rest-api-design-tutorial-with-example/

https://restfulapi.net/rest-put-vs-post/

https://restfulapi.net/http-methods/#patch

KOYMAK

Öncelikle mevcut kaynağı güncellemek için PUT API'lerini kullanın (kaynak yoksa, API yeni bir kaynak oluşturmaya veya oluşturmamaya karar verebilir). PUT API tarafından yeni bir kaynak yaratılmışsa, kaynak sunucunun kullanıcı aracısını HTTP yanıt kodu 201 (Oluşturuldu) yanıtı ile bilgilendirmesi ZORUNLUdur ve mevcut bir kaynak değiştirilirse, 200 (Tamam) veya 204 (İçerik Yok) yanıt kodlarının, talebin başarıyla tamamlandığını belirtmek için gönderilmesi GEREKİR.

İstek bir önbellekten geçerse ve İstek-URI'si şu anda önbelleğe alınmış bir veya daha fazla varlığı tanımlarsa, bu girişler eski olarak işlem görmelidir. Bu yönteme verilen yanıtlar önbelleğe alınamaz.

Zaten kaynak koleksiyonunun bir parçası olan tekil bir kaynağı değiştirmek istediğinizde PUT kullanın. PUT, kaynağın tamamını değiştirir. İstek, kaynağın bir bölümünü güncellerse PATCH kullanın.

YAMA

HTTP PATCH istekleri, bir kaynakta kısmi güncelleme yapmak içindir. PUT isteklerinin de bir kaynak varlığını değiştirdiğini görürseniz, daha net hale getirmek için - PATCH yöntemi, mevcut bir kaynağı kısmen güncellemek için doğru seçimdir ve PUT yalnızca bir kaynağı tamamen değiştiriyorsanız kullanılmalıdır.

Yani onu şu şekilde kullanmalısınız:

POST    /device-management/devices      : Create a new device
PUT     /device-management/devices/{id} : Update the device information identified by "id"
PATCH   /device-management/devices/{id} : Partial-update the device information identified by "id"

RESTful uygulamaları, / {id} konumuna ne koyduğunuza dikkat etmelisiniz - kaydın içeriği, yük tarafından sağlanan içeriğe güncellenmelidir - ancak GET / {id} yine de aynı kaynağa bağlanmalıdır.

Başka bir deyişle, PUT / 3, yük kimliği 4 olarak güncellenebilir, ancak GET / 3 yine de aynı yüke bağlanmalıdır (ve kimliği 4 olarak ayarlanmış olanı döndürmelidir).

API'nizin URI ve yükte aynı tanımlayıcıyı gerektirdiğine karar verirseniz, eşleştiğinden emin olmak sizin işinizdir, ancak tümüyle orada olması gereken yükteki kimliği hariç tutuyorsanız kesinlikle PUT yerine PATCH kullanın. . Kabul edilen yanıtın yanlış olduğu yer burasıdır. PUT, tüm kaynağın yerini almalıdır, çünkü yama kısmi olabilir.


Paragrafınızda: "Başka bir deyişle, PUT / 3, yük kimliği 4 olarak güncellenebilir, ancak GET / 3 yine de aynı yüke bağlanmalıdır (ve kimliği 4 olarak ayarlanmış olanı döndürmelidir )." - son id'de 4 yerine 3 mü demek istediniz ?
mlst

Re: "kimliği hariç tutuyorsanız PUT yerine PATCH kullanın" . Bu yanlış bilgilendirilmiş. 2014'ün RFC 7231'i (1999'un RFC 2616'sını geçersiz kılan RFC'lerden biri) burada sunucunun sahip olabileceğinden bahsediyor transformation applied to the body. İstemci için bir mekanizma bile var allows a user agent to know when the representation body it has in memory remains current, o da: sunucu MUST NOT send ... an ETag or Last-Modified ... unless the request's representation was saved without any transformation.
Slawomir Brzezinski

Ayrıca , diğer kullanıcıların PUT kullanmanın neden uygun olduğunu gösteren bu SO sorusuna + yanıtına bakın . Bu güzel özetlemektedir "Önemli olan, müşteri isteği niyet ne anlamaktır. İstemci tamamen geçti değerlerle kaynağın içeriğini yerini alması düşünülen".
Slawomir Brzezinski

0

PATCH / PUT istek türlerine bakmanız gerekebilir.

PATCH istekleri bir kaynağı kısmen güncellemek için kullanılırken, PUT isteklerinde, tüm kaynağı sunucuda geçersiz kılınacağı yere göndermeniz gerekir.

Url'de bir kimlik olması söz konusu olduğunda, bir kaynağı tanımlamak için standart bir uygulama olduğu için her zaman sahip olmanız gerektiğini düşünüyorum. Stripe API bile bu şekilde çalışır.

Sunucudaki bir kaynağı kimliğini tanımlamak için güncellemek için bir PATCH isteği kullanabilirsiniz, ancak gerçek kimliği güncellemeyin.

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.