Neden RESTful tasarımı hedefliyoruz?
RESTful ilkeleri , web sitelerini kolaylaştıran özellikleri ( rastgele bir insan kullanıcının "sörf" yapması ) web hizmetleri API tasarımına getirir , böylece bir programcının kullanımı kolaydır. REST iyi değil çünkü REST, iyi çünkü iyi. Ve çoğunlukla iyidir çünkü basittir .
Bazılarının "özellik eksikliği" olarak adlandırdığı düz HTTP'nin (SOAP zarfları ve tek URI'ye aşırı yüklenmiş POST
hizmetler olmadan) basitliği aslında en büyük gücüdür . Hemen, HTTP adreslenebilirlik ve vatansızlığa sahip olmanızı ister : HTTP'yi günümüzün mega sitelerine (ve mega hizmetlerine) ölçeklendiren iki temel tasarım kararı.
Ancak REST gümüş bir bullet değildir: Bazen bir RPC tarzı ("Uzak Yordam Çağrısı" - SOAP gibi) uygun olabilir ve bazen diğer ihtiyaçlar Web'in erdemlerinden önceliklidir. Bu iyi. Gerçekten sevmediğimiz şey gereksiz karmaşıklıktır . Çoğu zaman bir programcı veya şirket, düz eski HTTP'nin iyi işleyebileceği bir iş için RPC tarzı Hizmetler getirir. Etkisi, HTTP'nin "gerçekten" ne olduğunu açıklayan muazzam bir XML yükü için bir taşıma protokolüne indirgenmiş olmasıdır (URI veya HTTP yöntemi bu konuda bir ipucu vermez). Ortaya çıkan hizmet çok karmaşıktır, hata ayıklamak imkansızdır ve istemcileriniz geliştiricinin istediği şekilde tam olarak kurulmadıkça çalışmaz .
Java / C # kodu olabilir aynı şekilde değil sadece bir tasarım RESTful yapmaz HTTP kullanarak, nesne yönelimli. Birisi , eylemleri ve çağrılması gereken uzak yöntemler açısından hizmetleri hakkında düşünme acele yakalanabilir . Bunun çoğunlukla bir RPC-Stil hizmetine (veya bir REST-RPC-melezine) neden olacağına şaşmamalı. İlk adım farklı düşünmektir. RESTful tasarımı birçok yönden elde edilebilir, bunun bir yolu uygulamanızı eylemler yerine kaynaklar açısından düşünmektir:
💡 Gerçekleştirebileceği eylemler açısından düşünmek yerine ("haritada yerler için arama yapın") ...
... bu işlemlerin sonuçları açısından düşünmeye çalışın ("haritadaki bir arama ölçütleriyle eşleşen yerlerin listesi").
Aşağıdaki örneklere bakacağım. (REST'in diğer önemli yönü HATEOAS kullanımıdır - Burada fırçalamıyorum, ancak başka bir gönderide hızlı bir şekilde konuşuyorum .)
İlk tasarımın sorunları
Önerilen tasarıma bir göz atalım:
ACTION http://api.animals.com/v1/dogs/1/
Öncelikle, yeni bir HTTP fiili ( ACTION
) oluşturmayı düşünmemeliyiz . Genel olarak konuşursak, bu birkaç nedenden dolayı istenmeyen bir durumdur :
- (1) Yalnızca hizmet URI'sı verildiğinde, "rastgele" bir programcı
ACTION
fiilin var olduğunu nasıl bilecek ?
- (2) programcı var olduğunu biliyorsa, anlambilimini nasıl bilecek? Bu fiil ne anlama geliyor?
- (3) fiilin sahip olması gereken özellikler (güvenlik, idempotence) ne olmalıdır?
- (4) programcının yalnızca standart HTTP fiillerini işleyen çok basit bir istemcisi varsa ne olur?
- (5) ...
Şimdi let kullanmayı düşününPOST
(aşağıda, sadece şimdi bunun için benim sözüme niye ele alacağız):
POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
{"action":"bark"}
Bu iyi olabilir ... ama sadece :
{"action":"bark"}
bir belgedir; ve
/v1/dogs/1/
"belge işlemcisi" (fabrika benzeri) bir URI idi. "Belge işlemcisi", yalnızca "şeyleri" atacağınız ve onlar hakkında "unutacağınız" bir URI'dir - işlemci, "fırlatma" işleminden sonra sizi yeni oluşturulan bir kaynağa yönlendirebilir. Örneğin, mesaj gönderildikten sonra sizi mesajın işlenmesinin durumunu gösteren bir URI'ye yönlendirecek olan bir Message Broker hizmetine mesaj göndermek için URI.
Sisteminiz hakkında fazla bir şey bilmiyorum, ama her ikisinin de doğru olmadığına bahse girerim:
{"action":"bark"}
bir belge değil , aslında hizmete ninja gizlice girmeye çalıştığınız yöntemdir ; ve
/v1/dogs/1/
URI "köpek" kaynak (muhtemelen köpek temsil id==1
) ve bir belge işlemci.
Şimdi bildiğimiz tek şey yukarıdaki tasarımın o kadar RESTful olmadığı, ama tam olarak nedir? Bunda bu kadar kötü olan ne? Temel olarak, kötü çünkü karmaşık anlamları olan karmaşık URI. Ondan bir şey çıkaramazsınız. Bir programcı, bir köpeğin içine bark
gizlice aşılan bir eylemi olduğunu nasıl bilecektir POST
?
Sorunuzun API çağrılarını tasarlama
Öyleyse kovalamaca başlayalım ve kaynak açısından düşünerek bu kabukları DİNLENECEK şekilde tasarlamaya çalışalım . Restful Web Services kitabını alıntılamama izin ver :
Bir POST
istek varolan birinden yeni kaynak oluşturmak için bir girişimdir. Varolan kaynak, bir veri yapısı anlamında yenisinin üst öğesi olabilir, bir ağacın kökünün tüm yaprak düğümlerinin üst öğesi olması. Veya mevcut kaynak
, tek amacı başka kaynaklar üretmek olan özel bir "fabrika" kaynağı olabilir . Bir POST
istekle birlikte gönderilen temsil , yeni kaynağın başlangıç durumunu açıklar. PUT ile olduğu gibi, bir POST
isteğin de bir temsili içermesi gerekmez.
Biz görebilirsiniz yukarıdaki açıklamayı takiben bark
olarak modellenebilir bir bir subresourcedog
(a beri bark
, bir kabuk "havlamaya" edilir bir köpek, içinde bulunur tarafından bir köpek).
Bu akıl yürütmeden zaten:
- Yöntem
POST
- Kaynak,
/barks
köpeğin alt kaynağı : /v1/dogs/1/barks
bir bark
"fabrika" yı temsil eder . Bu URI her köpek için benzersizdir (altında olduğu için /v1/dogs/{id}
).
Artık listenizdeki her durumun belirli bir davranışı var.
1. havlama sadece bir e-posta gönderir dog.email
ve hiçbir şey kaydeder.
Birincisi, havlama (e-posta gönderme) eşzamanlı mı yoksa eşzamansız mı? İkincisi, bark
istek herhangi bir belge (e-posta, belki) gerektiriyor mu yoksa boş mu?
1.1 havlama e-posta gönderir dog.email
ve hiçbir şey kaydetmez (eşzamanlı görev olarak)
Bu dava basit. barks
Fabrika kaynağına yapılan bir çağrı hemen bir havlama (gönderilen bir e-posta) verir ve yanıt (tamamsa ya da değilse) hemen verilir:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(entity-body is empty - or, if you require a **document**, place it here)
200 OK
Hiçbir şeyi kaydetmediği (değiştirdiği) 200 OK
için yeterlidir. Her şeyin beklendiği gibi gittiğini gösterir.
1.2 havlama e-posta gönderir dog.email
ve hiçbir şey kaydetmez (eşzamansız görev olarak)
Bu durumda, istemcinin bark
görevi izlemek için bir yolu olmalıdır . bark
Görev sonra o kendi URI ile bir kaynak olmalıdır .:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Bu şekilde, her bark
biri izlenebilir. Istemci daha sonra geçerli durumunu bilmek GET
için bark
URI için a verebilir . Belki de DELETE
iptal etmek için a kullanın.
2. ağaç kabuğu bir e-posta gönderir dog.email
ve sonra artar dog.barkCount
1
İstemciye dog
kaynağın değiştiğini bildirmek istiyorsanız, bu daha zor olabilir :
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}
303 See Other
Location: http://api.animals.com/v1/dogs/1
Bu durumda, location
başlığın amacı müşteriye bir göz atması gerektiğini bildirmektir dog
. Gönderen yaklaşık HTTP RFC303
:
Bu yöntem öncelikle, POST
etkinleştirilmiş bir komut dosyasının çıktısının kullanıcı aracısını seçilen bir kaynağa yönlendirmesine izin vermek
için vardır.
Görev eşzamansızsa, durum bark
gibi bir alt kaynağa ihtiyaç duyulur ve görev tamamlandığında bir defada döndürülmelidir .1.2
303
GET .../barks/Y
3. ağaç kabuğu oluştuğunda bark
kayıt ile yeni bir " " kaydı oluşturur bark.timestamp
. Ayrıca dog.barkCount
1 artar .
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Burada, bark
istek nedeniyle oluşturulan, bu nedenle durum 201 Created
uygulanır.
Oluşturma senkronize değilse, bunun yerine 202 Accepted
( HTTP RFC'nin dediği gibi ) gereklidir .
Kaydedilen zaman damgası bark
kaynağın bir parçasıdır ve bir a ile alınabilir GET
. Güncellenen köpek de "belgelenebilir" GET dogs/X/barks/Y
.
4. bark, Github'dan köpek kodunun en son sürümünü aşağı çekmek için bir sistem komutu çalıştırır. Daha sonra dog.owner
, yeni köpek kodunun üretimde olduğunu bildirmek için bir metin mesajı gönderir .
Bunun ifadesi karmaşıktır, ancak hemen hemen basit bir asenkron görevdir:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Müşteri daha sonra geçerli durumu bilmek için GET
s /v1/dogs/1/barks/a65h44
gönderir (kod çekildiyse, e-posta sahibine gönderilir). Köpek her değiştiğinde, a 303
uygulanabilir.
Paketleme
Roy Fielding'den alıntı :
REST'in yöntemlere gereksinim duyduğu tek şey, bunların tüm kaynaklar için eşit olarak tanımlanmasıdır (yani, aracıların, talebin anlamını anlamak için kaynak türünü bilmeleri gerekmez).
Yukarıdaki örneklerde, POST
eşit olarak tasarlanmıştır. Köpeği " bark
" yapacak . Bu güvenli değildir (kabuğun kaynaklar üzerinde etkileri vardır) veya fiili iyi bark
uyan idempotent (her istek yeni bir sonuç verir ) POST
.
Bir programcı bilir: a POST
, a barks
verir bark
. Yanıt durum kodları (gerektiğinde varlık-gövdesi ve başlıklar ile birlikte) neyin değiştiğini ve müşterinin nasıl ilerleyebileceğini ve ilerlemesi gerektiğini açıklamakla görevlidir.
Not: Kullanılan başlıca kaynaklar: " Restful Web Services " kitabı, HTTP RFC ve Roy Fielding'in blogu .
Düzenle:
Soru ve dolayısıyla cevap ilk yaratıldıklarından bu yana biraz değişti. Orijinal soru bir URI gibi tasarımı konusunda sorular:
ACTION http://api.animals.com/v1/dogs/1/?action=bark
Aşağıda neden iyi bir seçim olmadığının açıklaması verilmiştir:
İstemciler sunucu anlatmak nasıl GEREKENLER verilerle olan yöntem bilgisi .
- RESTful web hizmetleri HTTP yönteminde yöntem bilgilerini iletir.
- Tipik RPC Stili ve SOAP hizmetleri, varlıklarını gövde gövdesinde ve HTTP üstbilgisinde tutar.
Verilerin HANGİ PARÇASI [istemci sunucunun] üzerinde çalışmasını istemek, kapsam belirleme bilgisidir .
- RESTful hizmetleri URI kullanır. SOAP / RPC-Style hizmetleri varlık gövdesi ve HTTP üstbilgilerini bir kez daha kullanır.
Örnek olarak Google'ın URI'sını ele alalım http://www.google.com/search?q=DOG
. Orada yöntem bilgisi GET
ve kapsam bilgisi vardır /search?q=DOG
.
Uzun lafın kısası:
- Gelen dinlendirici mimarileri , yöntem bilgisi HTTP yöntemi girer.
- In Kaynak Odaklı Mimarilerin , kapsam bilgisi URI girer.
Ve temel kural:
HTTP yöntemi yöntem bilgileriyle eşleşmezse, hizmet RESTful değildir. Kapsam belirleme bilgileri URI'de değilse, hizmet kaynak odaklı değildir.
URL'ye (veya varlık gövdesine) "havlama" " işlemini " koyabilir ve kullanabilirsiniz POST
. Sorun değil, işe yarıyor ve bunu yapmanın en basit yolu olabilir, ancak bu RESTful değil .
Hizmetinizi gerçekten GERÇEKLEŞTİRMEK için geri adım atmanız ve burada gerçekten ne yapmak istediğinizi düşünmeniz gerekebilir (kaynaklar üzerinde ne gibi etkileri olacaktır).
Özel iş ihtiyaçlarınız hakkında konuşamam, ancak size bir örnek vereyim: Siparişlerin URI'lerde olduğu RESTful bir sipariş servisi düşünün example.com/order/123
.
Şimdi bir siparişi iptal etmek istediğimizi söyleyin, bunu nasıl yapacağız? Bir bu olduğunu düşünüyorum için cazip olabilir "iptal" "eylem" ve olarak dizayn POST example.com/order/123?do=cancel
.
Yukarıda bahsettiğimiz gibi bu RESTful değil. Bunun yerine, adresine gönderilen bir öğeyle PUT
yeni bir temsili olabilir :order
canceled
true
PUT /order/123 HTTP/1.1
Content-Type: application/xml
<order id="123">
<customer id="89987">...</customer>
<canceled>true</canceled>
...
</order>
Ve bu kadar. Sipariş iptal edilemezse, belirli bir durum kodu döndürülebilir. ( POST /order/123/canceled
Varlık gövdesinde olduğu gibi bir alt kaynak tasarımı true
basitlik açısından da kullanılabilir.)
Özel senaryoda, benzer bir şey deneyebilirsiniz. Bu şekilde, bir köpek havlarken, örneğin, bir GET
at /v1/dogs/1/
bu bilgiyi içerebilir (örn. <barking>true</barking>
) . Veya ... bu çok karmaşıksa, RESTful gereksiniminizi gevşetin ve sadık kalın POST
.
Güncelleme:
Cevabı çok büyük yapmak istemiyorum, ancak bir dizi kaynak olarak bir algoritmayı (bir eylemi ) açığa çıkarmak biraz zaman alıyor . Eylemler açısından düşünmek yerine ( "haritada yerler için arama yapın" ), kişinin bu eylemin sonuçları açısından düşünmesi gerekir ( "harita üzerinde bir arama ölçütleriyle eşleşen yerlerin listesi" ).
Tasarımınızın HTTP'nin tek tip arayüzüne uymadığını fark ederseniz bu adıma geri dönebilirsiniz.
Sorgu değişkenleri olan bilgiyi kapsam , ama yapacak değil (yeni kaynaklar anlamında olabildikleri /post?lang=en
açıkça aynı şekilde kaynak /post?lang=jp
, sadece farklı gösterim). Aksine, onlar iletmek için kullanılan istemci durumunu (gibi ?page=10
, yani devlet sunucusunda tutulur değildir bu yüzden, ?lang=en
burada da bir örnektir) ya da giriş parametreleri için algoritmik kaynakların ( /search?q=dogs
, /dogs?code=1
). Yine, farklı kaynaklar değil.
HTTP fiillerinin (yöntemler) özellikleri:
?action=something
URI'de gösterilen başka bir açık nokta RESTful değil, HTTP fiillerinin özellikleridir:
GET
ve HEAD
güvenli (ve idempotent);
PUT
ve DELETE
sadece idempotenttir;
POST
Ne de.
Güvenlik : A GET
veya HEAD
istek, herhangi bir sunucu durumunu değiştirme isteği değil, bazı verileri okuma isteğidir. Müşteri bir kez yapabilir GET
veya HEAD
10 kez talep edebilir ve bir kez yapmak ya da hiç yapmamakla aynıdır .
Idempotence : Bir defada veya birden çok kez uygulansanız da aynı etkiye sahip bir idempotent işlem (matematikte, sıfıra çarpma idempotenttir). Eğer DELETE
bir kaynak bir kez, tekrar silmeyi aynı etkiyi (kaynaktır olacak GONE
zaten).
POST
ne güvenli ne de idempotent. POST
Bir 'fabrika' kaynağına iki özdeş istekte bulunmak muhtemelen aynı bilgileri içeren iki alt kaynağa neden olacaktır. Aşırı yüklendiğinde (URI veya varlık gövdesinde yöntem) POST
, tüm bahisler kapalıdır.
Her iki özellik de HTTP protokolünün başarısı için önemliydi (güvenilmez ağlar üzerinden!): GET
Sayfayı tamamen yüklenene kadar beklemeden kaç kez güncellediniz ( )?
Bir eylem oluşturmak ve URL'ye yerleştirmek, HTTP yöntemlerinin sözleşmesini açıkça ihlal eder. Bir kez daha, teknoloji size izin veriyor, bunu yapabilirsiniz, ancak bu RESTful tasarımı değil.