RESTful API: czasowniki HTTP ze wspólnymi lub konkretnymi adresami URL?

25

Czy podczas tworzenia interfejsu API RESTful należy używać czasowników HTTP dla tego samego adresu URL (jeśli jest to możliwe), czy też powinienem utworzyć określony adres URL dla akcji?

Na przykład:

GET     /items      # Read all items
GET     /items/:id  # Read one item
POST    /items      # Create a new item
PUT     /items/:id  # Update one item
DELETE  /items/:id  # Delete one item

Lub z określonymi adresami URL, takimi jak:

GET     /items            # Read all items
GET     /item/:id         # Read one item
POST    /items/new        # Create a new item
PUT     /item/edit/:id    # Update one item
DELETE  /item/delete/:id  # Delete one item
53777A
źródło

Odpowiedzi:

46

W drugim schemacie czasowniki są przechowywane w adresach URL zasobów. Należy tego unikać, ponieważ do tego celu należy używać czasowników HTTP. Zastosuj podstawowy protokół zamiast go ignorować, powielać lub zastępować.

Wystarczy spojrzeć DELETE /item/delete/:id, umieszczasz te same informacje dwa razy w tym samym żądaniu. Jest to zbyteczne i należy tego unikać. Osobiście myliłbym się z tym. Czy interfejs API faktycznie obsługuje DELETEżądania? Co się stanie, jeśli wstawię deleteadres URL i użyję innego czasownika HTTP? Czy to coś pasuje? Jeśli tak, który zostanie wybrany? Jako klient właściwie zaprojektowanego API nie powinienem zadawać takich pytań.

Być może potrzebujesz go w jakiś sposób do obsługi klientów, którzy nie mogą wydawać DELETEani PUTżądać. W takim przypadku przekazałbym te informacje w nagłówku HTTP. Niektóre interfejsy API używają X-HTTP-Method-Overridenagłówka do tego konkretnego celu (co moim zdaniem i tak jest dość brzydkie). Z pewnością nie umieszczałbym czasowników na ścieżkach.

Idź po

GET     /items      # Read all items
GET     /items/:id  # Read one item
POST    /items      # Create a new item
PUT     /items/:id  # Update one item
DELETE  /items/:id  # Delete one item

Najważniejsze w czasownikach jest to, że są one już dobrze zdefiniowane w specyfikacji HTTP, a zgodność z tymi regułami pozwala na używanie pamięci podręcznej, serwerów proxy i ewentualnie innych narzędzi zewnętrznych w stosunku do aplikacji, które rozumieją semantykę HTTP, ale nie semantykę aplikacji . Należy pamiętać, że powodem, dla którego należy unikać umieszczania ich w adresach URL, nie jest to, że interfejsy API RESTful wymagają czytelnych adresów URL. Chodzi o unikanie niepotrzebnej dwuznaczności.

Co więcej, interfejs API RESTful może mapować te czasowniki (lub dowolny ich podzbiór) na dowolny zestaw semantyki aplikacji, o ile nie jest niezgodny ze specyfikacją HTTP. Na przykład jest całkowicie możliwe zbudowanie interfejsu API RESTful, który korzysta z żądań GET tylko wtedy, gdy wszystkie operacje na nim dozwolone są zarówno bezpieczne, jak i idempotentne . Powyższe mapowanie jest tylko przykładem, który pasuje do Twojego przypadku użycia i jest zgodny ze specyfikacją. Nie musi tak być.

Należy również pamiętać, że prawdziwie RESTful API nigdy nie powinien wymagać od programisty czytania obszernej dokumentacji dostępnych adresów URL, o ile przestrzega się zasady HATEOAS (Hypertext as Engine of Application State), która jest jednym z podstawowych założeń REST . Odsyłacze mogą być całkowicie niezrozumiałe dla ludzi, o ile aplikacja kliencka może je zrozumieć i wykorzystać do ustalenia możliwych przejść stanu aplikacji.

toniedzwiedz
źródło
4
W przypadku braku PUTi DELETEwolałbym dodać go do ścieżki, nie różnicując go ciągiem zapytania. To nie jest modyfikacja ciągu zapytania do istniejącej operacji; to osobna operacja.
Robert Harvey
4
@RobertHarvey w tym przypadku i tak nazwałbym to hackowaniem. Jak mówisz, jest to operacja i nie jest to coś, co postawiłbym na ścieżce podczas projektowania interfejsu API, który ma być RESTful. Umieszczenie go w ciągu zapytania wydaje się po prostu mniej inwazyjne. Zapobiega buforowaniu, ale nie sądzę, aby odpowiedzi na tego rodzaju żądania i tak były buforowane. Umożliwia także konsumentowi interfejsu API łatwe wskazanie metody bez analizowania ani konstruowania adresu URL. Idealnie, RESTful API powinien zapewniać hiperłącza bez konieczności samodzielnego tworzenia adresów URL przez klientów.
toniedzwiedz
Jeśli nie masz wszystkich czasowników, to i tak nie jest to całkowicie ODPOCZYNEK, prawda?
Robert Harvey
@RobertHarvey to prawda, ale traktuję je jako awarię, a nie jako zamierzony projekt. Wyobrażam sobie, że interfejs API powinien obsługiwać rzeczywiste metody HTTP i jeśli jakiś klient nie może ich wdrożyć z jakiegokolwiek powodu, mogą po prostu zastąpić ich użycie tymi parametrami zapytania. Serwer proxy może nawet pobierać je w locie i przekształcać żądania w te przy użyciu oryginalnych czasowników HTTP, więc serwer nie musi się tym przejmować. Niewiele interfejsów API jest naprawdę RESTful. Jeśli chodzi o ogólne internetowe interfejsy API, to naprawdę kwestia gustu. Osobiście wybrałbym czyste adresy URL. Łatwiej zrozumieć IMHO.
toniedzwiedz
1
@RobertHarvey, jak wyjaśniono, nie jest to zamierzony sposób ich użycia. Uważam to za mniejsze zło, gdy trzeba pokonać ograniczenia klienta. Pamiętam, że czytałem dokumentację dla takiego interfejsu API, ale muszę go trochę przeszukać w historii / zakładkach przeglądarki, aby go znaleźć. Teraz, gdy o tym myślę, nagłówek może być lepszy w tym przypadku. Zgodziłbyś się?
toniedzwiedz
14

Pierwszy.

URI / URL to identyfikator zasobu (wskazówka w nazwie: jednolity identyfikator zasobu). Zgodnie z pierwszą konwencją zasób, o którym mówi się podczas wykonywania polecenia „GET / user / 123”, a zasób, o którym mówi się, gdy wykonuje się polecenie „DELETE / user / 123”, jest wyraźnie tym samym zasobem, ponieważ ma ten sam adres URL.

W przypadku drugiej konwencji nie można mieć pewności, że „GET / user / 123” i „DELETE / user / delete / 123” faktycznie są tym samym zasobem i wydaje się sugerować, że usuwa się powiązany zasób, a nie zasób sam w sobie, więc raczej zaskakujące jest to, że usunięcie /user/delete/123faktycznie usuwa /user/123. Jeśli wszystkie operacje działają na różnych adresach URL, identyfikator URI nie działa już jako identyfikator zasobu.

Kiedy mówisz DELETE /user/123, mówisz „usuń” rekord użytkownika o identyfikatorze 123 ”. Chociaż, jeśli powiesz DELETE /user/delete/123, to sugerujesz, że „usuwasz” rekord użytkownika usuwający z identyfikatorem 123 ”, co prawdopodobnie nie jest tym, co chcesz powiedzieć. I nawet jeśli użyjesz bardziej poprawnego czasownika w tej sytuacji: „POST / user / delete / 123”, który mówi „wykonaj operację dołączoną do„ deletora użytkownika o identyfikatorze 123 ””, nadal jest to okrężny sposób na usunięcie rekordu (jest to podobne do czasownika w języku angielskim).

Jednym ze sposobów myślenia o adresie URL jest traktowanie go jak wskaźników do obiektów i zasobów jako obiektów w programowaniu obiektowym. Gdy to zrobisz GET /user/123, DELETE /user/123możesz pomyśleć o nich myśleć jako metody w obiekcie: [/user/123].get(), [/user/123].delete()gdzie []jest jak wskaźnik operatora wyłuskania ale dla URL (jeśli znasz języka, które mają wskaźniki). Jedną z podstawowych zasad REST jest jednolity interfejs, tzn. Posiadanie małego i ograniczonego zestawu czasowników / metod, które działają na wszystko w rozległej sieci zasobów / obiektów.

Dlatego pierwszy jest lepszy.

PS: oczywiście patrzy się na REST w najczystszy sposób. Czasami praktyczność przebija czystość i trzeba zrobić ustępstwa dla martwych mózgów klientów lub platformy, która utrudnia prawidłowe REST.

Lie Ryan
źródło
+1 dla przykładu OOP :)
53777A
6

(przepraszam, mój pierwszy raz przegapiłem / edit / i / delete / in (2) ...)

Idea URI polega na tym, że jest to identyfikator zasobu adresowalnego , a nie wywołanie metody . Dlatego identyfikator URI powinien wskazywać określony zasób. A jeśli uszanujesz identyfikator URI, zawsze powinieneś uzyskać ten sam zasób.

Oznacza to, że powinieneś myśleć o identyfikatorach URI w taki sam sposób, jak myślisz o kluczu podstawowym wiersza w bazie danych. To jednoznacznie identyfikuje coś: Universal Resource Identifier.

Niezależnie od tego, czy używasz liczby mnogiej czy pojedynczej, identyfikator URI powinien być identyfikatorem, a nie wywołaniem . To, co próbujesz zrobić, odbywa się w metodzie, a mianowicie: GET (pobierz), PUT (utwórz / zaktualizuj), DELETE (usuń) lub POST (wszystko inne).

Zatem „/ item / delete / 123” przerywa REST, ponieważ nie wskazuje zasobu, jest raczej wywołaniem metody.

(Również semantycznie powinieneś być w stanie UZYSKAĆ ​​URI, zdecydować, że jest nieaktualny, a następnie USUNĄĆ ten sam URI - ponieważ jest to identyfikator. Jeśli GET URI nie ma „/ delete /”, a DELETE ma, to jest sprzeczne z semantyką HTTP. Nadajesz 2 lub więcej identyfikatorów URI na zasób, gdzie 1 to zrobi.)

Teraz oszustwo jest takie: nie ma naprawdę jasnej definicji tego, co jest zasobem, a nie zasobem, więc powszechnym unikaniem w REST jest zdefiniowanie „rzeczownika przetwarzającego” i wskazanie na to URI. To właściwie gra słowna, ale spełnia semantykę.

Jeśli na przykład z jakiegoś powodu naprawdę nie można tego użyć:

DELETE /items/123

możesz zadeklarować światu, że masz zasób przetwarzający „deletor” i jego użycie

POST /items/deletor  { id: 123 }

Teraz wygląda to bardzo podobnie do RPC (Remote Procedura Call), ale przechodzi przez rozległą lukę w klauzuli „przetwarzania danych” specyfikacji POST nazwanej w specyfikacji HTTP.

Jednak robi to rodzaj wyjątkowe, a jeśli mogą korzystać ze wspólnej PUT dla tworzenia / UPDATE, DELETE do usunięcia, a POST dla append, tworzyć i wszystko inne, to powinien , bo to bardziej standardowe użycie protokołu HTTP. Ale jeśli masz trudny przypadek, taki jak „zatwierdzenie” lub „opublikuj” lub „zredaguj”, to przypadek użycia rzeczownika procesora spełnia wymagania purystów REST i nadal zapewnia semantykę, której potrzebujesz.

Obrabować
źródło