RESTful hizmetinde kısmi güncellemeler için en iyi uygulama


208

Bir müşteri yönetim sistemi için RESTful hizmeti yazıyorum ve kayıtları kısmen güncellemek için en iyi uygulamayı bulmaya çalışıyorum. Örneğin, arayanın bir GET isteği ile tam kaydı okuyabilmesini istiyorum. Ancak güncelleme için, durumu ETKİN'den DEVRE DIŞI olarak değiştirmek gibi yalnızca kayıttaki belirli işlemlere izin verilir. (Bundan daha karmaşık senaryolarım var)

Arayan kişinin tüm kayıtları güvenlik nedeniyle sadece güncellenmiş alanla göndermesini istemiyorum (ayrıca aşırıya kaçma gibi geliyor).

URI'leri oluşturmanın önerilen bir yolu var mı? REST kitaplarını okurken RPC tarzı çağrılar hoşnutsuz görünüyor.

Aşağıdaki çağrı, 123 numaralı kimliğe sahip müşteri için tam müşteri kaydını döndürürse

GET /customer/123
<customer>
    {lots of attributes}
    <status>ENABLED</status>
    {even more attributes}
</customer>

durumu nasıl güncellemeliyim?

POST /customer/123/status
<status>DISABLED</status>

POST /customer/123/changeStatus
DISABLED

...

Güncelleme : Soruyu arttırmak için. Kişi 'iş mantığı çağrılarını' bir REST API'sine nasıl dahil eder? Bunu yapmanın kabul edilmiş bir yolu var mı? Tüm yöntemler doğası gereği CRUD değildir. Bazıları ' sendEmailToCustomer (123) ', ' mergeCustomers (123, 456) ', ' countCustomers () ' gibi daha karmaşıktır

POST /customer/123?cmd=sendEmail

POST /cmd/sendEmail?customerId=123

GET /customer/count 

3
"İş mantığı çağrıları" ile ilgili sorunuzu cevaplamak için POSTRoy Fielding'in kendisinden bir yazı : roy.gbiv.com/untangled/2009/it-is-okay-to-use-post temel fikir nerede: varsa Operasyon kullanımınıza en uygun yöntem ( GETveya PUT) değildir POST.
rojoca

Sonuçta yaptığım şey bu. GET, PUT, DELETE kullanarak bilinen kaynakları almak ve güncellemek için REST çağrıları yapın. Yeni kaynaklar eklemek için POST ve iş mantığı çağrıları için açıklayıcı URL içeren POST.
magiconair

Kararınız ne olursa olsun, bu işlem GET yanıtının bir parçası değilse, RESTful hizmetiniz yoktur. Bunu burada
görmüyorum

Yanıtlar:


69

Temel olarak iki seçeneğiniz vardır:

  1. Kullanın PATCH(ancak tam olarak ne olacağını belirten kendi ortam türünüzü tanımlamanız gerektiğini unutmayın)

  2. Kullanım POSTbir alt kaynağına ve Konum başlığı ana kaynağa bakacak şekilde 303 Bkz Diğer dönün. 303'ün amacı, istemciye "POST'nizi gerçekleştirdim ve sonuç, başka bir kaynağın güncellenmiş olmasıydı. Hangi kaynağın bulunduğu Konum başlığına bakın." POST / 303, bazı ana kaynakların durumunu oluşturmak için bir kaynağa yinelemeli eklemeler için tasarlanmıştır ve kısmi güncellemeler için mükemmel bir seçimdir.


Tamam, POST / 303 bana mantıklı geliyor. PATCH ve MERGE Geçerli HTTP fiilleri listesinde bulamadım, bu yüzden daha fazla test gerektiriyordu. Sistemin müşteri 123'e e-posta göndermesini istersem nasıl bir URI oluşturabilirim? Nesnenin durumunu hiç değiştirmeyen saf RPC yöntem çağrısı gibi bir şey. Bunu yapmanın RESTful yolu nedir?
magiconair

E-posta URI sorusunu anlamıyorum. Bir e-posta göndermesi için POST yapabileceğiniz bir ağ geçidi uygulamak istiyor musunuz yoksa mailto: customer.123@service.org mu arıyorsunuz?
Jan Algermissen

15
Ne REST ne de HTTP'nin HTTP yöntemlerini CRUD ile eşitleyen bazı kişiler dışında CRUD ile ilgisi yoktur. REST, gösterimleri aktararak kaynak durumunu değiştirmekle ilgilidir. Ne elde etmek istiyorsanız, bir temsili uygun semantiğe sahip bir kaynağa aktararak bunu yapın. 'HTTP aktarım içindir' anlamına geldiğinden 'saf yöntem çağrıları' veya 'iş mantığı' terimlerine dikkat edin. Bir e-posta göndermeniz gerekiyorsa, bir ağ geçidi kaynağına POST, hesaplarla birleştirmeniz gerekiyorsa, yeni bir tane oluşturun ve diğer ikisinin POST temsillerini oluşturun, vb.
Jan Algermissen

9
Ayrıca, Google'ın bunu nasıl yaptığını öğrenin: googlecode.blogspot.com/2010/03/…
Marius

4
williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot PATCH [{"op": "test", "yol": "/ a / b / c", "değer" : "foo"}, {"op": "kaldır", "yol": "/ a / b / c"}, {"op": "ekle", "yol": "/ a / b / c" , "değer": ["foo", "çubuk"]}, {"op": "değiştir", "yol": "/ a / b / c", "değer": 42}, {"op": "taşıma", "": "/ a / b / c", "yol": "/ a / b / d"}, {"op": "kopya", "": "/ a / b / d "," yol ":" / a / b / e "}]
15'te

48

Kısmi güncellemeler için POST kullanmalısınız.

Müşteri 123 alanlarını güncellemek için / customer / 123 adresine bir POST yapın.

Yalnızca durumu güncellemek isterseniz, / customer / 123 / status konumuna da koyabilirsiniz.

Genellikle, GET isteklerinin herhangi bir yan etkisi olmamalıdır ve PUT, kaynağın tamamını yazmak / değiştirmek içindir.

Bu, burada görüldüğü gibi doğrudan HTTP'den gelir: http://en.wikipedia.org/wiki/HTTP_PUT#Request_methods


1
@John Saunders POST mutlaka bir URI'den
wsorenson

10
@wsorensen: Yeni bir URL ile sonuçlanması gerekmediğini biliyorum, ancak yine de bir POST'un /customer/123123 müşterisi altında mantıklı olan bariz şeyi yaratması gerektiğini düşündüm. Belki bir sipariş? PUT /customer/123/statusdaha mantıklı görünüyor, POST'un /customersdolaylı olarak bir status(ve yasal REST olduğunu) varsayalım.
John Saunders

1
@John Saunders: Pratik olarak konuşursak, belirli bir URI'da bulunan bir kaynak üzerindeki bir alanı güncellemek istiyorsak, POST PUT'tan daha mantıklı ve bir UPDATE'den yoksun, çoğu zaman REST hizmetlerinde kullanıldığına inanıyorum. POST / müşterileri yeni bir müşteri oluşturabilir ve / customer / 123 / status için bir PUT, belirtimin sözcüğü ile daha iyi hizalanabilir, ancak en iyi uygulamalarda, / müşteri / 123 bir alanı güncellemek için - özlü, mantıklı ve şartnamedeki hiçbir şeye kesinlikle karşı gelmez.
wsorenson

8
POST istekleri idempotent olmamalı mı? Kesinlikle bir girişi güncellemek idempotent ve bu nedenle bunun yerine bir PUT olmalı?
Martin Andersson

1
@MartinAndersson POST-requests yok gerek olmayan İdempotent olmak. Ve belirtildiği gibi, PUTbir kaynağın tamamını değiştirmelidir.
Halle Knast

10

Kısmi güncellemeler için PATCH'i kullanmalısınız - json yama belgeleri kullanarak (bkz. Http://tools.ietf.org/html/draft-ietf-appsawg-json-patch-08 veya http://www.mnot.net/ blog / 2012/09/05 / patch ) veya XML yama çerçevesi (bkz. http://tools.ietf.org/html/rfc5261 ). Benim düşünceme göre, json-patch iş verileriniz için en uygun olanıdır.

JSON / XML yama belgelerine sahip PATCH, kısmi güncellemeler için çok ileriye dönük anlambilime sahiptir. POST'u orijinal belgenin değiştirilmiş kopyalarıyla kullanmaya başlarsanız, kısmi güncellemeler için yakında eksik değerlerin (veya daha doğrusu null değerlerin) "bu özelliği yoksay" veya "bu özelliği" boş değer "- ve sonuçta kendi yama biçiminizle sonuçlanacak olan saldırıya uğramış çözümlerin tavşan deliğini açar.

Burada daha ayrıntılı bir cevap bulabilirsiniz: http://soabits.blogspot.dk/2013/01/http-put-patch-or-post-partial-updates.html .


Bu arada json yaması ve xml yaması için RFC'lerin sonlandırıldığını lütfen unutmayın .
botchniaque

8

Benzer bir sorunla karşılaşıyorum. Yalnızca tek bir alanı güncellemek istediğinizde bir alt kaynaktaki PUT çalışıyor gibi görünüyor. Ancak, bazen bir sürü şeyi güncellemek istersiniz: Bazı girişleri değiştirme seçeneği ile kaynağı temsil eden bir web formu düşünün. Kullanıcının form göndermesi birden fazla PUT ile sonuçlanmamalıdır.

İşte aklıma gelen iki çözüm:

  1. tüm kaynak ile bir PUT yapın. Sunucu tarafında, kaynağın tamamını içeren bir PUT'un değişmeyen tüm değerleri yok saydığı semantiği tanımlayın.

  2. kısmi bir kaynağa sahip bir PUT yapın. Sunucu tarafında, bunun anlambilimini birleştirme olarak tanımlayın.

2 sadece 1 bant genişliği optimizasyonudur. Bazen kaynak bazı alanların gerekli alanlar olduğunu düşünüyorsa 1 tek seçenek olabilir (proto tamponları düşünün).

Her iki yaklaşımın da sorunu bir alanın nasıl temizleneceğidir. Alanın temizlenmesine neden olacak özel bir boş değer tanımlamanız gerekir (özellikle proto arabellekler için boş değerler tanımlanmadığından proto arabellekler için).

Yorumlar?


2
Ayrı bir soru olarak gönderilirse bu daha yararlı olacaktır.
intotecho

6

Durumu değiştirmek için RESTful yaklaşımın, kaynakların durumunu açıklayan mantıksal bir alt kaynak kullanmak olduğunu düşünüyorum. Bu IMO, düşük sayıda durumunuz olduğunda oldukça yararlı ve temizdir. Müşteri kaynağınız için mevcut işlemleri zorlamadan API'nızı daha etkileyici hale getirir.

Misal:

POST /customer/active  <-- Providing entity in the body a new customer
{
  ...  // attributes here except status
}

POST hizmeti, yeni oluşturulan müşteriyi şu kimliğiyle döndürmelidir:

{
    id:123,
    ...  // the other fields here
}

Oluşturulan kaynak için GET, kaynak konumunu kullanır:

GET /customer/123/active

Bir GET / müşteri / 123 / etkin değil 404 döndürmelidir

PUT işlemi için, bir Json varlığı sağlamadan durumu günceller

PUT /customer/123/inactive  <-- Deactivating an existing customer

Bir kuruluşun sağlanması, müşterinin içeriğini güncellemenize ve aynı anda durumu güncellemenize olanak tanır.

PUT /customer/123/inactive
{
    ...  // entity fields here except id and status
}

Müşteri kaynağınız için kavramsal bir alt kaynak oluşturuyorsunuz. Aynı zamanda Roy Fielding'in bir kaynak tanımıyla da tutarlıdır: "... Bir kaynak, belirli bir zamanda haritalamaya karşılık gelen varlık değil, bir takım varlıklarla kavramsal bir eşleme ..." Bu durumda, kavramsal eşleme, etkin = müşteri ile status = ACTIVE olan müşteridir.

Okuma işlemi:

GET /customer/123/active 
GET /customer/123/inactive

Bu aramaları, diğerinin 404 durumunu döndürmesi gerektiğinden hemen sonra yaparsanız, başarılı çıkış durumu örtük olduğu için durumu içermeyebilir. Elbette müşteri kaynağını doğrudan sorgulamak için GET / customer / 123? Status = ACTIVE | INACTIVE'ı kullanabilirsiniz.

Semantik kafa karıştırıcı olabileceğinden, DELETE işlemi ilginçtir. Ancak, bu işlemi bu kavramsal kaynak için yayınlamama veya iş mantığınıza uygun olarak kullanma seçeneğiniz vardır.

DELETE /customer/123/active

Bu, müşterinizi SİLİNDİ / DEVRE DIŞI duruma veya ters duruma (AKTİF / AKTİF DEĞİL) alabilir.


Alt kaynağa nasıl ulaşırsınız?
MStodd

Ben daha açık hale getirmek için çalışırken cevap refactored
raspacorp

5

Artırılmış sorunuza eklenecek şeyler. Bence genellikle daha karmaşık iş eylemlerini mükemmel bir şekilde tasarlayabilirsiniz. Ancak, yöntem / prosedür düşünme tarzını vermek ve kaynaklarda ve fiillerde daha fazla düşünmek zorundasınız.

posta gönderimleri


POST /customers/123/mails

payload:
{from: x@x.com, subject: "foo", to: y@y.com}

Bu kaynağın + POST uygulamasının uygulanması postayı gönderir. gerekirse, / customer / 123 / outbox gibi bir şey önerebilir ve / customer / mails / {mailId} için kaynak bağlantıları sunabilirsiniz.

müşteri sayısı

Bunu bir arama kaynağı gibi ele alabilirsiniz (sayfalama içeren arama meta verileri ve size müşteri sayısı sağlayan num-information bilgisi dahil).


GET /customers

response payload:
{numFound: 1234, paging: {self:..., next:..., previous:...} customer: { ...} ....}


POST alt kaynaklarındaki alanların mantıksal gruplandırılmasını seviyorum.
gertas

3

Eksik / kısmi kaynağı güncellemek için PUT kullanın.

JObject öğesini parametre olarak kabul edebilir ve kaynağı güncellemek için değerini ayrıştırabilirsiniz.

Referans olarak kullanabileceğiniz fonksiyon aşağıdadır:

public IHttpActionResult Put(int id, JObject partialObject)
{
    Dictionary<string, string> dictionaryObject = new Dictionary<string, string>();

    foreach (JProperty property in json.Properties())
    {
        dictionaryObject.Add(property.Name.ToString(), property.Value.ToString());
    }

    int id = Convert.ToInt32(dictionaryObject["id"]);
    DateTime startTime = Convert.ToDateTime(orderInsert["AppointmentDateTime"]);            
    Boolean isGroup = Convert.ToBoolean(dictionaryObject["IsGroup"]);

    //Call function to update resource
    update(id, startTime, isGroup);

    return Ok(appointmentModelList);
}

2

Güncellemeniz Hakkında.

CRUD kavramı, API tasarımı konusunda bazı karışıklıklara neden olduğuna inanıyorum. CRUD, veriler üzerinde gerçekleştirilecek temel işlemler için genel bir düşük seviyeli kavramdır ve HTTP fiilleri sadece istek yöntemleridir ( 21 yıl önce yaratılmıştır) bir CRUD işlemiyle eşleşebilen veya eşleşmeyebilen ). Aslında, HTTP 1.0 / 1.1 belirtiminde CRUD kısaltmasının varlığını bulmaya çalışın.

Pragmatik bir kural uygulayan çok iyi açıklanmış bir kılavuz Google bulut platformu API dokümanlarında bulunabilir . İşlemler üzerinde büyük miktarda kaynağı vurgulayan ve açıkladığınız kullanım örneklerini içeren, kaynak tabanlı bir API oluşturmanın arkasındaki kavramları açıklar. Ürünleri için sadece bir kongre tasarımı olmasına rağmen, bence çok mantıklı.

Buradaki temel kavram (ve çok fazla karışıklık yaratan kavram) "yöntemler" ve HTTP fiilleri arasındaki eşlemedir. Bir şey, API'nizin hangi kaynak türleri (örneğin, bir müşteri listesi almak veya bir e-posta göndermek) üzerinde ne tür "işlemler" (yöntemler) yapacağını tanımlamak ve diğeri HTTP fiilleridir. Hem kullanmayı planladığınız yöntemlerin ve fiillerin hem de aralarında bir eşlemenin tanımı olmalıdır .

Bir işlem, standart bir yöntemle tam olarak eşleşmiyor zaman da (ki diyor List, Get, Create, Update, Deletebu durumda), bir benzeri, "Özel yöntemleri" kullanabilir BatchGetbirkaç nesne kimliği girişe göre birkaç nesne alır veya hangi SendEmail.


2

RFC 7396 : JSON Birleştirme Yaması (soru gönderildikten dört yıl sonra yayınlanır), biçim ve işleme kuralları açısından bir PATCH için en iyi uygulamaları açıklar.

Özetle, application / merge-patch + json MIME ortam türü ve yalnızca değiştirilmesini / eklenmesini / kaldırılmasını istediğiniz parçaları temsil eden bir gövdeyi içeren bir hedef kaynağa bir HTTP PATCH gönderir ve ardından aşağıdaki işleme kurallarını uygularsınız.

kurallar :

  • Sağlanan birleştirme düzeltme eki hedefte görünmeyen üyeler içeriyorsa, bu üyeler eklenir.

  • Hedef üye içeriyorsa, değer değiştirilir.

  • Birleştirme yamasındaki boş değerlere, hedefte var olan değerlerin kaldırıldığını göstermek için özel bir anlam verilir.

Yukarıdaki kuralları gösteren örnek test senaryoları (bu RFC'nin ekinde görüldüğü gibi ):

 ORIGINAL         PATCH           RESULT
--------------------------------------------
{"a":"b"}       {"a":"c"}       {"a":"c"}

{"a":"b"}       {"b":"c"}       {"a":"b",
                                 "b":"c"}
{"a":"b"}       {"a":null}      {}

{"a":"b",       {"a":null}      {"b":"c"}
"b":"c"}

{"a":["b"]}     {"a":"c"}       {"a":"c"}

{"a":"c"}       {"a":["b"]}     {"a":["b"]}

{"a": {         {"a": {         {"a": {
  "b": "c"}       "b": "d",       "b": "d"
}                 "c": null}      }
                }               }

{"a": [         {"a": [1]}      {"a": [1]}
  {"b":"c"}
 ]
}

["a","b"]       ["c","d"]       ["c","d"]

{"a":"b"}       ["c"]           ["c"]

{"a":"foo"}     null            null

{"a":"foo"}     "bar"           "bar"

{"e":null}      {"a":1}         {"e":null,
                                 "a":1}

[1,2]           {"a":"b",       {"a":"b"}
                 "c":null}

{}              {"a":            {"a":
                 {"bb":           {"bb":
                  {"ccc":          {}}}
                   null}}}

1

Check out http://www.odata.org/

MERGE yöntemini tanımlar, bu durumda sizin durumunuz şöyle olur:

MERGE /customer/123

<customer>
   <status>DISABLED</status>
</customer>

Yalnızca statusözellik güncellenir ve diğer değerler korunur.


MERGEgeçerli bir HTTP fiil?
John Saunders

3
PATCH'e bakın - yakında standart HTTP olacak ve aynı şeyi yapacak.
Jan Algermissen

John Saunders Evet, bu bir uzatma yöntemidir.
Max Toro

FYI MERGE, OData v4'ten kaldırıldı. MERGE was used to do PATCH before PATCH existed. Now that we have PATCH, we no longer need MERGE. Bkz. Docs.oasis-open.org/odata/new-in-odata/v4.0/cn01/…
tanguy_k

0

Önemli değil. REST açısından, bir GET yapamazsınız, çünkü önbelleğe alınamaz, ancak POST veya PATCH veya PUT veya herhangi bir şey kullanmanızın önemi yoktur ve URL'nin neye benzediği önemli değildir. REST yapıyorsanız, önemli olan, kaynağınızın sunucudan bir temsilini aldığınızda, bu temsilin istemci durumuna geçiş seçenekleri verebilmesidir.

GET yanıtınızda durum geçişleri varsa, istemcinin bunları nasıl okuyacağını bilmesi gerekir ve sunucu gerekirse bunları değiştirebilir. Burada bir güncelleme POST kullanılarak yapılır, ancak PATCH olarak değiştirildiyse veya URL değişirse, istemci hala nasıl güncelleme yapılacağını bilir:

{
  "customer" :
  {
  },
  "operations":
  [
    "update" : 
    {
      "method": "POST",
      "href": "https://server/customer/123/"
    }]
}

İstemcinin size geri vermesi için gerekli / isteğe bağlı parametreleri listeleyebildiğiniz kadar ileri gidebilirsiniz. Uygulamaya bağlıdır.

İş operasyonlarına gelince, bu müşteri kaynağından farklı bir kaynak olabilir. Müşteriye bir e-posta göndermek istiyorsanız, belki de bu hizmet POST'u atayabileceğiniz kendi kaynağıdır, bu nedenle müşteri kaynağına aşağıdaki işlemi ekleyebilirsiniz:

"email":
{
  "method": "POST",
  "href": "http://server/emailservice/send?customer=1234"
}

Bazı iyi videolar ve sunum yapan kişinin REST mimarisi örneği bunlardır. Stormpath yalnızca GET / POST / DELETE kullanır, çünkü REST'in kullandığınız işlemler veya URL'lerin nasıl görünmesi gerektiği (GET'lerin önbelleğe alınabilmesi dışında) ile hiçbir ilgisi yoktur:

https://www.youtube.com/watch?v=pspy1H6A3FM ,
https://www.youtube.com/watch?v=5WXYw4J4QOU ,
http://docs.stormpath.com/rest/quickstart/

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.