REST API Najlepsza praktyka: jak zaakceptować listę wartości parametrów jako dane wejściowe [zamknięte]


409

Wprowadzamy nowy interfejs API REST i chciałem, aby społeczność przekazała informacje o najlepszych praktykach dotyczących sposobu formatowania parametrów wejściowych:

Obecnie nasze API jest bardzo skoncentrowane na JSON (zwraca tylko JSON). Debata na temat tego, czy chcemy / musimy zwrócić XML, to osobna kwestia.

Ponieważ nasze dane wyjściowe API są skoncentrowane na JSON, poszliśmy ścieżką, w której nasze dane wejściowe są nieco skoncentrowane na JSON i pomyślałem, że może to być wygodne dla niektórych, ale ogólnie dziwne.

Na przykład, aby uzyskać kilka szczegółów produktu, w których można pobrać wiele produktów naraz, obecnie mamy:

http://our.api.com/Product?id=["101404","7267261"]

Czy powinniśmy to uprościć:

http://our.api.com/Product?id=101404,7267261

A może przydaje się wejście JSON? Więcej bólu?

Możemy zaakceptować oba style, ale czy ta elastyczność faktycznie powoduje więcej zamieszania i ból głowy (łatwość konserwacji, dokumentacja itp.)?

Bardziej złożony przypadek ma miejsce, gdy chcemy zaoferować bardziej złożone dane wejściowe. Na przykład, jeśli chcemy zezwolić na wiele filtrów podczas wyszukiwania:

http://our.api.com/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}

Niekoniecznie chcemy umieszczać typy filtrów (np. ProductType i kolor) jako nazwy żądań takie jak to:

http://our.api.com/Search?term=pumas&productType=["Clothing","Bags"]&color=["Black","Red"]

Ponieważ chcieliśmy zgrupować wszystkie filtry wejściowe razem.

Czy to naprawdę ma znaczenie? Może być prawdopodobne, że istnieje tak wiele narzędzi JSON, że typ danych wejściowych po prostu nie ma większego znaczenia.

Wiem, że nasi klienci JavaScript wykonujący wywołania AJAX do API mogą docenić dane wejściowe JSON, aby ułatwić im życie.

Odpowiedzi:


341

Cofnij się

Przede wszystkim REST opisuje URI jako uniwersalnie unikalny identyfikator. Zdecydowanie zbyt wiele osób przyłapuje się na strukturze URI i które URI są bardziej „uspokajające” niż inne. Ten argument jest tak samo absurdalny, jak powiedzenie, że nadanie komuś imienia „Bob” jest lepsze niż nadanie mu imienia „Joe” - oba nazwiska wykonują zadanie „identyfikacji osoby”. Identyfikator URI to nic innego jak uniwersalnie unikalna nazwa.

Tak więc w oczach REST-u kłócą się o ?id=["101404","7267261"]to, czy jest bardziej kojące, ?id=101404,7267261czy \Product\101404,7267261raczej daremne.

Teraz, powiedziawszy to, wiele razy skonstruowanie identyfikatorów URI może zwykle służyć jako dobry wskaźnik dla innych problemów w usłudze RESTful. W twoich URI jest kilka czerwonych flag i ogólnie pytanie.

Propozycje

  1. Wiele identyfikatorów URI dla tego samego zasobu i Content-Location

    Możemy zaakceptować oba style, ale czy ta elastyczność faktycznie powoduje więcej zamieszania i ból głowy (łatwość konserwacji, dokumentacja itp.)?

    Identyfikatory URI identyfikują zasoby. Każdy zasób powinien mieć jeden kanoniczny identyfikator URI. Nie oznacza to, że nie możesz mieć dwóch identyfikatorów URI wskazujących na ten sam zasób, ale istnieją dobrze zdefiniowane sposoby na zrobienie tego. Jeśli zdecydujesz się używać zarówno JSON, jak i formatów opartych na listach (lub dowolnym innym formacie), musisz zdecydować, który z tych formatów jest głównym kanonicznym identyfikatorem URI. Wszystkie odpowiedzi na inne identyfikatory URI wskazujące na ten sam „zasób” powinny zawierać Content-Locationnagłówek .

    Trzymanie się analogii nazwy, posiadanie wielu identyfikatorów URI jest jak posiadanie pseudonimów dla ludzi. Jest to całkowicie akceptowalne i często bardzo przydatne, ale jeśli używam pseudonimu, prawdopodobnie nadal chcę znać ich pełne imię i nazwisko - „oficjalny” sposób na odniesienie się do tej osoby. W ten sposób, gdy ktoś wspomina kogoś po imieniu, „Nichloas Telsa”, wiem, że mówi on o tej samej osobie, którą nazywam „Nick”.

  2. „Szukaj” w twoim URI

    Bardziej złożony przypadek ma miejsce, gdy chcemy zaoferować bardziej złożone dane wejściowe. Na przykład, jeśli chcemy zezwolić na wiele filtrów podczas wyszukiwania ...

    Ogólna zasada jest taka, że ​​jeśli twój URI zawiera czasownik, może to wskazywać, że coś jest nie tak. Identyfikatory URI identyfikują zasób, jednak nie powinny wskazywać, co robimy z tym zasobem. To zadanie HTTP lub, mówiąc spokojnie, nasz „jednolity interfejs”.

    Aby przełamać analogię nazwy jako martwą, użycie czasownika w URI jest jak zmiana nazwiska kogoś, kiedy chcesz z nim wchodzić w interakcję. Jeśli wchodzę w interakcję z Bobem, imię Boba nie staje się „BobHi”, kiedy chcę się z nim przywitać. Podobnie, gdy chcemy „wyszukiwać” Produkty, nasza struktura URI nie powinna zmieniać się z „/ Product / ...” na „/ Search / ...”.

Odpowiedzi na pierwsze pytanie

  1. Odnośnie ["101404","7267261"]vs 101404,7267261: Proponuję tutaj, aby uniknąć składni JSON dla uproszczenia (tj. Nie wymagaj od użytkowników kodowania adresów URL, gdy tak naprawdę nie musisz). Sprawi, że twój interfejs API będzie nieco bardziej użyteczny. Jeszcze lepiej, jak zalecają inni, skorzystaj ze standardowego application/x-www-form-urlencodedformatu, ponieważ prawdopodobnie będzie on najbardziej znany użytkownikom końcowym (np ?id[]=101404&id[]=7267261.). To może nie być „ładne”, ale ładne identyfikatory URI nie muszą oznaczać użytecznych identyfikatorów URI. Jednak, aby powtórzyć mój punkt początkowy, ostatecznie mówiąc o REST, nie ma to znaczenia. Nie zastanawiaj się nad tym zbyt mocno.

  2. Przykład złożonego wyszukiwania identyfikatora URI można rozwiązać w bardzo podobny sposób jak przykładowy produkt. Poleciłbym powtórzenie application/x-www-form-urlencodedformatu, ponieważ jest to już standard, który wielu zna. Polecam także połączenie tych dwóch.

Twój URI ...

/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}    

Twój URI po zakodowaniu URI ...

/Search?term=pumas&filters=%7B%22productType%22%3A%5B%22Clothing%22%2C%22Bags%22%5D%2C%22color%22%3A%5B%22Black%22%2C%22Red%22%5D%7D

Można przekształcić w ...

/Product?term=pumas&productType[]=Clothing&productType[]=Bags&color[]=Black&color[]=Red

Oprócz unikania wymogu kodowania adresów URL i nadawania standardowi wyglądu, teraz nieco ujednolica interfejs API. Użytkownik wie, że jeśli chce odzyskać produkt lub listę produktów (oba są uważane za pojedynczy „zasób” w kategoriach RESTful), jest zainteresowany /Product/...identyfikatorami URI.


67
Chciałem sprawdzić i zauważyć, że []składnia nie zawsze jest obsługiwana (i mimo że jest powszechna, może nawet naruszać specyfikację URI). Niektóre serwery HTTP i języki programowania wolą po prostu powtarzać nazwę (np productType=value1&productType=value2.).
nategood

1
Początkowe pytanie z tym zapytaniem .. "/ Search? Term = pumas & filter = {" productType ": [„ Odzież ”,„ Torby ”],„ kolor ”: [„ Czarny ”,„ Czerwony ”]}” przekłada się na… . (productType == odzież || productType == torby) i& (kolor == czarny || kolor == czerwony) ALE TWOJE ROZWIĄZANIE: / Product? term = pumy i productType [] = Odzież i productType [] = Torby i kolor [] = Czarny i kolor [] = Czerwony wydaje się przekładać na ... Albo (productType == odzież || productType == torby || kolor == czarny || kolor == czerwony) lub Albo (productType == odzież i& productType == torby && color == czarny && kolor == czerwony) Co wydaje mi się nieco inne. A może źle zrozumiałem?
Thomas Cheng,

2
Co z danymi wejściowymi we wniosku o wpis? Chciałem wiedzieć, czy aktualizujemy zasób, czy więc wysyłanie zapytania / filtra i danych w treści w standardowym formacie jest złą praktyką. na przykład jeśli chcę zmienić dane związane z użytkownikiem za pomocą interfejsu API /user/w organizmie, wyślę { q:{}, d: {} }z qjak zapytaniu z użytkownikiem będzie odpytywany w DB i djak zmodyfikowanych danych.
cząsteczka

1
Co robisz, gdy lista może być bardzo duża? Długość identyfikatora URI jest ograniczona w zależności od przeglądarki. Zazwyczaj przełączyłem się na prośbę o wpis i wysłałem listę w treści. Jakieś sugestie?
Troy Cosentino

4
Musiałby być bardzo duży (patrz stackoverflow.com/questions/417142/... ), ale tak, w najbardziej ekstremalnych przypadkach może być konieczne użycie treści żądania. Zapytania POST dotyczące pobierania danych to jedna z tych rzeczy, o których RESTafarianie uwielbiają dyskutować.
nategood

233

Standardowym sposobem przekazania listy wartości jako parametrów adresu URL jest ich powtórzenie:

http://our.api.com/Product?id=101404&id=7267261

Większość kodu serwera zinterpretuje to jako listę wartości, chociaż wiele z nich ma uproszczenia dla pojedynczej wartości, więc być może będziesz musiał szukać.

Wartości graniczne również są w porządku.

Jeśli potrzebujesz wysłać JSON na serwer, nie lubię go widzieć w adresie URL (który ma inny format). W szczególności adresy URL mają ograniczenia rozmiaru (w praktyce, jeśli nie teoretycznie).

Sposób, w jaki widziałem, jak RESTfully wykonuje skomplikowane zapytanie, składa się z dwóch etapów:

  1. POST wymagania dotyczące zapytania, odbieranie identyfikatora (zasadniczo tworzenie zasobu kryteriów wyszukiwania)
  2. GET wyszukiwanie, odwołując się do powyższego identyfikatora
  3. opcjonalnie USUŃ wymagania dotyczące zapytania, jeśli to konieczne, ale pamiętaj, że wymagania te są dostępne do ponownego użycia.

8
Dzięki, Kathy. Myślę, że jestem z tobą i nie podoba mi się również wyświetlanie JSON w adresie URL. Jednak nie jestem fanem robienia postu dla wyszukiwania, które jest nieodłączną operacją GET. Czy możesz wskazać przykład tego?
whatupwilly,

1
Jeśli zapytania mogą działać jako proste parametry, zrób to. Źródło pochodzi z listy dyskusyjnej reszty do dyskusji: tech.groups.yahoo.com/group/rest-discuss/message/11578
Kathy Van Stone

2
Jeśli chcesz tylko pokazać dwa zasoby, odpowiedź Jamesa Westgate'a jest bardziej typowa
Kathy Van Stone

To jest poprawna odpowiedź. W najbliższej przyszłości na pewno zobaczymy jakiś filter = id w (a, b, c, ...) obsługiwany przez OData lub coś w tym rodzaju.
Bart Calixto,

Tak działa Akka HTTP
Joan

20

Pierwszy:

Myślę, że możesz to zrobić na 2 sposoby

http://our.api.com/Product/<id> : jeśli chcesz tylko jeden rekord

http://our.api.com/Product : jeśli chcesz wszystkie rekordy

http://our.api.com/Product/<id1>,<id2> : jak zasugerował James, może być opcją, ponieważ to, co następuje po tagu produktu, jest parametrem

Lub najbardziej lubię:

Możesz użyć Hypermedia jako właściwości silnika stanu aplikacji (HATEOAS) RestFul WS i wykonać wywołanie, http://our.api.com/Productktóre powinno zwrócić równoważne adresy URL http://our.api.com/Product/<id>i wywołać je po tym.

druga

Gdy musisz wykonać zapytania dotyczące połączeń URL. Sugerowałbym ponowne użycie HATEOAS.

1) Zadzwoń pod numer http://our.api.com/term/pumas/productType/clothing/color/black

2) Zadzwoń pod numer http://our.api.com/term/pumas/productType/clothing,bags/color/black,red

3) (Korzystanie z HATEOAS) Zadzwoń do ` http://our.api.com/term/pumas/productType/ -> otrzymaj adresy URL wszystkich możliwych adresów URL odzieży -> zadzwoń do tych, których chcesz (ubrania i torby) - > otrzymaj możliwe kolorowe adresy URL -> zadzwoń do tych, których chcesz


1
Kilka dni temu znalazłem się w podobnej sytuacji. Musiałem dostroić apkę (HATEOAS) odpoczynku, aby uzyskać przefiltrowaną (dużą) listę obiektów i właśnie wybrałem twoje drugie rozwiązanie. Czy przywoływanie API w kółko dla każdego z nich nie jest trochę przesadne?
Samson,

To naprawdę zależy od twojego systemu ... Jeśli jest to prosty z kilkoma „opcjami”, prawdopodobnie powinien to być przesada. Jednak jeśli masz naprawdę duże listy, zrobienie tego wszystkiego w jednym dużym wywołaniu może być naprawdę kłopotliwe, poza tym, jeśli twój API jest publiczny, może stać się skomplikowany dla użytkowników (jeśli jest to prywatny, powinno być łatwiej ... po prostu naucz użytkowników, których znasz). Alternatywnie, możesz zaimplementować zarówno styl, HATEOAS, jak i wezwanie do „niespokojnej tablicy” dla zaawansowanych użytkowników
Diego Dias

Buduję spokojną usługę interfejsu API w szynach i muszę przestrzegać tej samej struktury adresu URL co powyżej ( our.api.com/term/pumas/productType/clothing/color/black ). Ale nie jestem pewien, jak odpowiednio skonfigurować trasy.
rubyist

czy twoje kontrolery to termin, typ produktu i kolor? Jeśli tak, musisz po prostu: zasoby: termin zrobić zasoby: productType zrobić zasoby: koniec koloru
Diego Dias

productType i kolor są parametrami. Więc parametry produktu Rodzaj to odzież, a parametry odzieży są czarne
rubinista

12

Może chcesz sprawdzić RFC 6570 . Ta specyfikacja szablonu URI pokazuje wiele przykładów tego, jak adresy URL mogą zawierać parametry.


1
Wydaje się, że sekcja 3.2.8 ma zastosowanie. Chociaż warto zauważyć, że jest to tylko proponowany standard i nie wydaje się, aby przekroczył ten punkt.
Mike Post,

3
@MikePost Teraz, gdy IETF przeszedł na dwuetapowy proces zapadalności dokumentów „śledzących standardy”, oczekuję, że 6570 pozostanie w tym stanie jeszcze przez kilka lat, zanim przejdę do „standardu internetowego”. tools.ietf.org/html/rfc6410 Specyfikacja jest wyjątkowo stabilna, ma wiele implementacji i jest szeroko stosowana.
Darrel Miller,

Ach, nie byłem świadomy tej zmiany. (Lub, TIL IETF jest teraz bardziej rozsądny.) Dzięki!
Mike Post

8

Pierwszy przypadek:

Normalne wyszukiwanie produktu wyglądałoby tak

http://our.api.com/product/1

Myślę więc, że najlepszą praktyką byłoby to zrobić

http://our.api.com/Product/101404,7267261

Druga sprawa

Szukaj z parametrami querystring - tak dobrze. Kusiłoby mnie, aby połączyć warunki z AND i OR zamiast używać [].

PS Może to być subiektywne, więc rób to, z czym czujesz się komfortowo.

Powodem umieszczenia danych w adresie URL jest to, że link można wkleić na stronie / udostępnić użytkownikom. Jeśli to nie jest problem, należy zamiast tego użyć JSON / POST.

EDYCJA: Po zastanowieniu myślę, że to podejście pasuje do bytu z kluczem złożonym, ale nie do zapytania dla wielu bytów.


3
Oczywiście w obu przykładach /nie powinno być końca, ponieważ identyfikator URI dotyczy zasobu, a nie kolekcji.
Lawrence Dol

2
Zawsze myślałem, że czasowniki HTTP, w użyciu REST, miały wykonywać określone działania, a była to linia prowadząca: GET: pobierz / odczytaj obiekt, POST utwórz obiekt, PUT zaktualizuj istniejący obiekt i USUŃ usuń obiekt. Więc nie użyłbym POST, aby coś odzyskać. Jeśli chcę w szczególności listę obiektów (filtr), zrobiłbym GET z listą parametrów url (oddzielone przecinkiem wydaje się dobre)
Alex

1

Popieram odpowiedź nategood, ponieważ jest kompletna i wydaje się, że zaspokaja twoje potrzeby. Chciałbym jednak dodać komentarz na temat identyfikacji wielu (1 lub więcej) zasobów w ten sposób:

http://our.api.com/Product/101404,7267261

W ten sposób:

Skomplikuj klientów , zmuszając ich do interpretowania twojej odpowiedzi jako tablicy, co jest dla mnie sprzeczne z intuicją, jeśli złożę następujące żądanie:http://our.api.com/Product/101404

Twórz nadmiarowe interfejsy API za pomocą jednego interfejsu API, aby uzyskać wszystkie produkty, i jednego powyżej, aby uzyskać 1 lub wiele. Ponieważ nie powinieneś pokazywać użytkownikowi więcej niż 1 strony szczegółów ze względu na UX, uważam, że posiadanie więcej niż 1 identyfikatora byłoby bezużyteczne i służyło wyłącznie do filtrowania produktów.

Może to nie być tak problematyczne, ale albo będziesz musiał poradzić sobie z tym po stronie serwera, zwracając pojedynczy byt (sprawdzając, czy twoja odpowiedź zawiera jedną lub więcej), albo pozwalając klientom nim zarządzać.

Przykład

Chcę zamówić książkę w Amazing . Wiem dokładnie, która to książka i widzę ją na liście podczas nawigacji po horrorach:

  1. 10 000 niesamowitych linii, 0 niesamowitych testów
  2. Powrót niesamowitego potwora
  3. Powielmy niesamowity kod
  4. Niesamowity początek końca

Po wybraniu drugiej książki następuje przekierowanie na stronę z opisem części książkowej listy:

--------------------------------------------
Book #1
--------------------------------------------
    Title: The return of the amazing monster
    Summary:
    Pages:
    Publisher:
--------------------------------------------

A może na stronie z pełnymi szczegółami tej książki?

---------------------------------
The return of the amazing monster
---------------------------------
Summary:
Pages:
Publisher:
---------------------------------

Moja opinia

Sugerowałbym użycie identyfikatora w zmiennej path, gdy gwarantowana jest unikalność przy pobieraniu szczegółów tego zasobu. Na przykład poniższe interfejsy API sugerują wiele sposobów uzyskania szczegółowych informacji o konkretnym zasobie (zakładając, że produkt ma unikalny identyfikator, a specyfikacja tego produktu ma unikalną nazwę i można nawigować z góry na dół):

/products/{id}
/products/{id}/specs/{name}

Gdy potrzebujesz więcej niż jednego zasobu, sugeruję odfiltrowanie z większej kolekcji. Dla tego samego przykładu:

/products?ids=

Oczywiście to jest moja opinia, ponieważ nie jest narzucona.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.