Powiedzmy, że mam trzy podobne zasoby:
Grandparent (collection) -> Parent (collection) -> and Child (collection)
Powyżej przedstawia relację między tymi zasobami w następujący sposób: Każdy dziadek może odwzorować jednego lub kilku rodziców. Każdy rodzic może przypisać jedno lub więcej dzieci. Chcę mieć możliwość obsługi wyszukiwania dla zasobu podrzędnego, ale z kryteriami filtrowania:
Jeśli moi klienci przekażą mi dowód tożsamości dziadka, chcę szukać tylko w stosunku do dzieci, które są bezpośrednimi potomkami tego dziadka.
Jeśli moi klienci przekażą mi referencję do rodzica, chcę wyszukiwać tylko w stosunku do dzieci, które są bezpośrednimi potomkami mojego rodzica.
Pomyślałem o czymś takim:
GET /myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}
i
GET /myservice/api/v1/parents/{parentID}/children?search={text}
odpowiednio dla powyższych wymagań.
Ale mógłbym też zrobić coś takiego:
GET /myservice/api/v1/children?search={text}&grandparentID={id}&parentID=${id}
W tym projekcie mógłbym pozwolić mojemu klientowi przekazać mi jedno lub drugie w ciągu zapytania: albo grandparentID lub parentID, ale nie jedno i drugie.
Moje pytania to:
1) Który projekt API jest bardziej RESTful i dlaczego? Semantycznie mają na myśli i zachowują się w ten sam sposób. Ostatnim zasobem w URI jest „potomek”, co skutecznie sugeruje, że klient działa na zasobu potomnym.
2) Jakie są zalety i wady dla każdego pod względem zrozumiałości z perspektywy klienta i łatwości konserwacji z perspektywy projektanta.
3) Do czego tak naprawdę używane są ciągi zapytania oprócz „filtrowania” zasobów? Jeśli wybierzesz pierwsze podejście, parametr filtru zostanie osadzony w samym URI jako parametr ścieżki zamiast parametru ciągu zapytania.
Dzięki!
I want to only search against children who are INdirect descendants of that grandparent.
? Zgodnie z twoją strukturą, Dziadek nie ma bezpośrednich dzieci.potential design flaw
a jeśli masz informacje o osobie, ale bez informacji o swoich rodziców, czy kwalifikują się one jakochild
? (np. Adam i Ewa) :)Odpowiedzi:
Pierwszy
Zgodnie z RFC 3986 §3.4 (Jednolite identyfikatory zasobów § (Składniki składniowe) | Zapytanie
Komponenty zapytania służą do wyszukiwania danych niehierarchicznych; jest kilka rzeczy bardziej zhierarchizowanych niż drzewo genealogiczne! Ergo - niezależnie od tego, czy uważasz, że jest to „REST-y”, czy nie - w celu zachowania zgodności z formatami, protokołami i ramami oraz do opracowywania systemów w Internecie nie wolno używać ciągu zapytania do identyfikacji tych informacji.
REST nie ma nic wspólnego z tą definicją.
Przed udzieleniem odpowiedzi na konkretne pytania parametr zapytania „szukaj” jest źle nazwany. Lepiej byłoby traktować segment zapytania jako słownik par klucz-wartość.
Łańcuch zapytania można lepiej zdefiniować jako
?first_name={firstName}&last_name={lastName}&birth_date={birthDate}
itp.Aby odpowiedzieć na twoje konkretne pytania
Nie sądzę, że jest to tak jasne, jak się wydaje.
Żaden z tych interfejsów zasobów nie jest w stanie RESTful. Ważnym warunkiem dla relaksującego stylu jest to, że przemiany Zastosowanie państwowe muszą być przekazywane z serwera jako hipermediów. Ludzie pracowali nad strukturą URI, aby uczynić je w jakiś sposób „RESTful URI”, ale oficjalna literatura na temat REST naprawdę niewiele ma na ten temat do powiedzenia. Moje osobiste zdanie jest takie, że wiele meta-dezinformacji na temat REST zostało opublikowanych z zamiarem przełamania starych, złych nawyków. (Budowa prawdziwie „RESTful” to w rzeczywistości sporo pracy. Przemysł przeszedł do „REST” i zaspokoił niektóre ortogonalne obawy bezsensownymi kwalifikacjami i ograniczeniami.)
Literatura REST mówi, że jeśli zamierzasz używać HTTP jako protokołu aplikacji, musisz przestrzegać formalnych wymagań specyfikacji protokołu i nie możesz „tworzyć HTTP podczas pracy i nadal deklarować, że używasz HTTP” ; jeśli zamierzasz używać identyfikatorów URI do identyfikowania swoich zasobów, musisz przestrzegać formalnych wymagań specyfikacji dotyczących URI / adresów URL.
Twoje pytanie jest adresowane bezpośrednio w RFC3986 §3.4, które zamieściłem powyżej. Najważniejsze w tej kwestii jest to, że chociaż zgodny identyfikator URI jest niewystarczający, aby uznać interfejs API za „RESTful”, jeśli chcesz, aby Twój system rzeczywiście był „RESTful” i używasz HTTP i URI, nie możesz zidentyfikować danych hierarchicznych za pomocą ciąg zapytania, ponieważ:
... to takie proste.
„Zaletami” pierwszych dwóch jest to, że są na właściwej ścieżce . „Wadą” trzeciego jest to, że wydaje się całkowicie błędny.
Jeśli chodzi o twoją zrozumiałość i łatwość konserwacji, są one zdecydowanie subiektywne i zależą od poziomu zrozumienia twórcy klienta i elementów projektu projektanta. Specyfikacja URI jest ostateczną odpowiedzią na to, w jaki sposób powinny być formatowane URI. Dane hierarchiczne powinny być reprezentowane na ścieżce wraz z parametrami ścieżki. Dane niehierarchiczne powinny być reprezentowane w zapytaniu. Fragment jest bardziej skomplikowany, ponieważ jego semantyka zależy w szczególności od rodzaju mediów żądanej reprezentacji. Aby zająć się częścią „zrozumiałości” twojego pytania, postaram się przetłumaczyć dokładnie to, co mówią twoje pierwsze dwa identyfikatory URI. Następnie spróbuję przedstawić to, co mówisz, że próbujesz osiągnąć za pomocą prawidłowych identyfikatorów URI.
Tłumaczenie dosłownych identyfikatorów URI na ich znaczenie semantyczne
/myservice/api/v1/grandparents/{grandparentID}/parents/children?search={text}
To mówi dla rodziców dziadków, że ich dziecko masearch={text}
to, co powiedziałeś z URI, jest spójne tylko wtedy, gdy szukasz rodzeństwa dziadka. Z „dziadkami, rodzicami, dziećmi” znaleźliście „dziadka”, który dorastał z pokoleniem do swoich rodziców, a następnie powrócił do pokolenia „dziadków”, patrząc na dzieci rodziców./myservice/api/v1/parents/{parentID}/children?search={text}
Mówi to, że dla rodzica zidentyfikowanego przez {parentID}, znajdź jego dziecko mające?search={text}
To jest bliższe do poprawienia tego, czego chcesz, i reprezentuje relację rodzic-dziecko, które prawdopodobnie może być użyte do modelowania całego API. Aby modelować to w ten sposób, klient jest obciążony rozpoznaniem, że jeśli mają „dziadka”, to istnieje warstwa pośrednicząca między posiadanym identyfikatorem a częścią wykresu rodzinnego, którą chcą zobaczyć. Aby znaleźć „dziecko” według „dziadka”, możesz zadzwonić do/parents/{parentID}/children
serwisu, a następnie wskazać dziecko, które zostanie zwrócone, poszukaj dzieci w poszukiwaniu identyfikatora osoby.Implementacja twoich wymagań jako identyfikatorów URI Jeśli chcesz modelować bardziej rozszerzalny identyfikator zasobu, który może chodzić po drzewie, mogę wymyślić kilka sposobów na osiągnięcie tego.
1) Pierwszy, o którym już wspomniałem. Reprezentuj wykres „Ludzie” jako strukturę złożoną. Każda osoba ma odniesienie do pokolenia nad nią poprzez ścieżkę Rodziców i do pokolenia pod nim poprzez ścieżkę Dzieci.
/Persons/Joe/Parents/Mother/Parents
byłby sposobem na złapanie matczynych dziadków Joe./Persons/Joe/Parents/Parents
byłby sposobem na złapanie wszystkich dziadków Joe./Persons/Joe/Parents/Parents?id={Joe.GrandparentID}
złapałby dziadka Joe, który miałby przy sobie identyfikator.i wszystko to miałoby sens (należy pamiętać, że może tu wystąpić kara wydajności w zależności od zadania przez wymuszenie dfs na serwerze z powodu braku identyfikacji gałęzi we wzorcu „Rodzice / Rodzice / Rodzice”). zdolność do obsługi dowolnej liczby pokoleń. Jeśli z jakiegoś powodu chcesz spojrzeć na 8 pokoleń, możesz to przedstawić jako
/Persons/Joe/Parents/Parents/Parents/Parents/Parents/Parents/Parents/Parents?id={Joe.NotableAncestor}
ale prowadzi to do drugiej dominującej opcji reprezentowania tych danych: poprzez parametr ścieżki.
2) Użyj parametrów ścieżki do „zapytania do hierarchii”. Możesz opracować następującą strukturę, aby zmniejszyć obciążenie konsumentów i nadal mieć sensowny interfejs API.
Aby spojrzeć wstecz na 147 pokoleń, reprezentacja tego identyfikatora zasobu za pomocą parametrów ścieżki pozwala to zrobić
/Persons/Joe/Parents;generations=147?id={Joe.NotableAncestor}
Aby zlokalizować Joe od jego pradziadka, możesz spojrzeć w dół wykresu znanej liczby pokoleń identyfikatora Joe.
/Persons/JoesGreatGrandparent/Children;generations=3?id={Joe.Id}
Najważniejszą rzeczą w tych podejściach jest to, że bez dalszych informacji w identyfikatorze i żądaniu należy spodziewać się, że pierwszy identyfikator URI pobiera od osoby Joe pokolenia 147 o identyfikatorze Joe.NotableAncestor. Powinieneś spodziewać się, że drugi odzyska Joe. Załóżmy, że tak naprawdę chcesz, aby klient wywołujący mógł pobrać cały zestaw węzłów i ich relacje między osobą główną a ostatecznym kontekstem twojego identyfikatora URI. Możesz to zrobić przy użyciu tego samego identyfikatora URI (z pewną dodatkową dekoracją) i ustawiając opcję Akceptuj
text/vnd.graphviz
na żądanie, która jest typem nośnika zarejestrowanym przez IANA do.dot
reprezentacji wykresu. Dzięki temu zmień identyfikator URI na/Persons/Joe/Parents;generations=147?id={Joe.NotableAncestor.Id}#directed
z nagłówkiem żądania HTTP
Accept: text/vnd.graphviz
, dzięki czemu klienci mogą dość wyraźnie komunikować, że chcą skierowanego wykresu hierarchii pokoleniowej między Joe a 147 pokoleniami wcześniej, gdy 147. pokolenie przodków zawiera osobę zidentyfikowaną jako „godny uwagi przodek” Joe'ego.Nie jestem pewien, czy text / vnd.graphviz ma jakąś predefiniowaną semantykę dla swojego fragmentu; nie mogłem znaleźć żadnej w poszukiwaniu instrukcji. Jeśli ten typ nośnika faktycznie zawiera wstępnie zdefiniowane informacje o fragmencie, należy zastosować jego semantykę, aby utworzyć zgodny identyfikator URI. Ale jeśli te semantyki nie są wstępnie zdefiniowane, specyfikacja URI stwierdza, że semantyka identyfikatora fragmentu jest nieograniczona i zamiast tego jest zdefiniowana przez serwer, dzięki czemu użycie jest prawidłowe.
Wydaje mi się, że już całkowicie pobiłem to na śmierć, ale ciągi zapytań nie służą do „filtrowania” zasobów. Służą do identyfikacji Twojego zasobu na podstawie danych niehierarchicznych. Jeśli przejrzałeś swoją hierarchię swoją ścieżką, idąc
/person/{id}/children/
i chcesz zidentyfikować określone dziecko lub określony zestaw dzieci, użyjesz atrybutu, który dotyczy zestawu, który identyfikujesz i umieścisz go w zapytaniu.źródło
Tutaj źle to rozumiesz:
W systemach REST klient nigdy nie powinien przejmować się identyfikatorami. Jedynymi identyfikatorami zasobów, o których powinien wiedzieć klient, powinny być identyfikatory URI. Jest to zasada „jednolitego interfejsu”.
Pomyśl o tym, jak klienci będą wchodzić w interakcje z twoim systemem. Powiedzmy, że użytkownik zaczyna przeglądać listę dziadków, wybrał jedno z dzieci dziadka, do którego go prowadzi
/grandparent/123
. Jeśli klient powinien być w stanie przeszukać dzieci/grandparent/123
, to zgodnie z „HATEOAS”, cokolwiek zwróciło się podczas zapytania,/grandparent/123
powinno zwrócić adres URL do interfejsu wyszukiwania. Ten adres URL powinien już zawierać dane potrzebne do filtrowania według obecnego dziadka.Czy związek wygląda
/grandparent/123?search={term}
lub/parent?grandparent=123&search={term}
lub/parent?grandparentTerm=someterm&someothergplocator=blah&search={term}
są nieistotne zgodnie z resztą. Zauważ, że wszystkie te adresy URL mają tę samą liczbę parametrów, to znaczy{term}
, mimo że używają różnych kryteriów. Możesz przełączać się między tymi adresami URL lub mieszać je w zależności od konkretnych dziadków, a klient nie zerwie się, ponieważ logiczna relacja między zasobami jest taka sama, chociaż podstawowa implementacja może się znacznie różnić.Jeśli zamiast tego utworzyłeś usługę wymagającą,
/grandparent/{grandparentID}?search={term}
gdy idziesz w jedną stronę, a/children?parent={parentID}&search={term}
a}, gdy idziesz inną drogą, jest to zbyt duże sprzężenie, ponieważ klient musiałby wiedzieć, aby interpolować różne rzeczy w różnych relacjach, które są koncepcyjnie podobne.Niezależnie od tego, czy faktycznie wybierasz,
/grandparent/123?search={term}
czy/parent?grandparent=123&search={term}
jest to kwestia gustu i którekolwiek wdrożenie jest teraz łatwiejsze. Ważne jest, aby nie wymagać modyfikacji klienta, jeśli zmienisz strategię adresu URL lub zastosujesz różne strategie dla różnych relacji rodzic-dziecko.źródło
Nie jestem pewien, dlaczego ludzie myślą, że umieszczanie wartości identyfikatora w adresie URL oznacza, że jest to interfejs API REST, REST polega na obsłudze czasowników, przekazywaniu zasobów.
Więc jeśli chcesz ZŁOŻYĆ nowego użytkownika, musisz wysłać sporą porcję danych, a żądanie HTTP POST jest idealne, więc chociaż możesz wysłać klucz (np. Identyfikator użytkownika), wyślesz dane użytkownika (np. nazwisko, adres) jako dane POST.
Teraz powszechnym idiomem jest umieszczanie identyfikatora zasobu w URI, ale jest to bardziej konwencja niż jakakolwiek forma kanoniczna „nie jest to REST, jeśli nie jest w URI”. Pamiętaj, że oryginalna teza REST w ogóle nie wspomina o http, jest to idiom do obsługi danych w kliencie-serwerze, a nie coś, co jest rozszerzeniem do http (choć oczywiście http jest naszą podstawową formą implementacji REST) .
Na przykład Fielding wykorzystuje przykład pracy naukowej jako przykład. Chcesz odzyskać zasób „Dr John's Paper on the drink of beers”, ale możesz także chcieć początkowej lub najnowszej wersji, więc identyfikator zasobu może nie być czymś, co można łatwo nazwać pojedynczym identyfikatorem, który może być umieszczone w URI. REST pozwala na to, a deklarowaną intencją jest:
Nic więc nie stoi na przeszkodzie, aby użyć statycznego identyfikatora URI do odzyskania rodziców, przekazując wyszukiwane hasło w ciągu zapytania, aby zidentyfikować dokładnego użytkownika. W tym przypadku identyfikowany „zasób” to zbiór dziadków (a zatem identyfikator URI zawiera „dziadków” jako część identyfikatora URI. REST obejmuje koncepcję „danych kontrolnych” zaprojektowanych do określania, która reprezentacja twojego zasób ma zostać pobrany - przykładem podanym w tym jest kontrola pamięci podręcznej, ale także kontrola wersji - więc moje żądanie doskonałej pracy dr Johna można uściślić, przekazując wersję jako dane kontrolne, a nie część identyfikatora URI.
Myślę, że przykładem interfejsu REST, o którym zwykle nie wspomina się, jest SMTP . Podczas konstruowania wiadomości e-mail wysyłane są czasowniki (FROM, TO itp.) Z danymi zasobów dla każdej części wiadomości e-mail. Jest to RESTful, mimo że nie używa czasowników http, używa własnego zestawu.
Więc ... chociaż musisz mieć identyfikację zasobu w twoim URI, nie musi to być twój identyfikator. Można to na szczęście wysłać jako dane kontrolne, w ciągu zapytania lub nawet w danych POST. To, co naprawdę identyfikujesz w interfejsie API REST, to to, że szukasz dziecka, które już masz w swoim identyfikatorze URI.
Moim zdaniem, czytając definicję REST, prosisz o zasób potomny, przekazujesz dane kontrolne (w formie kwerendy), aby ustalić, który z nich chcesz zwrócić. W rezultacie nie można wysłać żądania identyfikatora URI dla dziadka lub rodzica. Chcesz, aby dzieci zostały zwrócone, więc termin rodzic / dziadek lub id zdecydowanie nie powinien znajdować się w takim identyfikatorze URI. Dzieci powinny być.
źródło
Wiele osób już mówiło o tym, co oznacza REST itp. Ale nikt nie wydaje się zajmować prawdziwym problemem: twoim projektem.
Dlaczego dziadek różni się od ojca? oboje mają dzieci, które mogą mieć dzieci, które ...
W końcu wszyscy są „ludźmi”. Prawdopodobnie masz to również w swoim kodzie. Więc użyj tego:
Zwróci przydatne informacje o człowieku. (Nazwisko i inne rzeczy)
oczywiście zwróci tablicę dzieci. Jeśli nie ma dzieci, prawdopodobnie najlepszym rozwiązaniem jest pusta tablica.
Aby to ułatwić, możesz na przykład dodać flagę „hasChildren”. lub „hasGrandChildren”.
źródło
Poniżej znajduje się bardziej RESTfull, ponieważ każdy identyfikator dziadka otrzymuje własny adres URL. W ten sposób zasób zostaje zidentyfikowany w unikalny sposób.
Wyszukiwanie parametrów zapytania jest dobrym sposobem na wykonanie wyszukiwania w kontekście tego zasobu.
Gdy rodzina staje się bardzo duża, możesz użyć opcji start / limit jako opcji zapytania, na przykład:
Jako programista dobrze jest mieć różne zasoby z unikalnym adresem URL / URI. Myślę, że powinieneś używać parametru zapytania tylko wtedy, gdy można je pominąć.
Być może jest to dobra lektura http://www.thoughtworks.com/insights/blog/rest-api-design-resource-modeling, a poza tym oryginalna praca doktorska Roy T Fieldinga https://www.ics.uci.edu /~fielding/pubs/dissertation/fielding_dissertation.pdf, który wyjaśnia te pojęcia bardzo dobrze i kompletnie.
źródło
Pójdę z
W takim przypadku każdy prefiks jest prawidłowym zasobem:
/myservice/api/v1/people
: wszyscy ludzie/myservice/api/v1/people/{personID}
: jedna osoba z pełnymi informacjami (w tym przodkowie, rodzeństwo, potomkowie)/myservice/api/v1/people/{personID}/descendants
: potomkowie jednej osoby/myservice/api/v1/people/{personID}/descendants/2
: wnuki jednej osobyMoże nie być najlepszą odpowiedzią, ale przynajmniej ma dla mnie sens.
źródło
wielu wcześniej zauważyło, że format adresu URL jest nieistotny w kontekście usługi RESTful ... moje dwa centy byłyby ... ważnym aspektem REST, jak zalecono w pierwotnym piśmie, koncepcja „zasobów”. .przy projektowaniu adresu URL jednym z aspektów, które należy wziąć pod uwagę, jest to, że „Zasób” nie jest wierszem w pojedynczej tabeli (choć może być) .. raczej jest reprezentacją .. to też jest spójne .. .i mogą być używane do wprowadzania zmian w stanie tej reprezentacji (i efektywnie w oparciu o nośnik pamięci) .. a dany zasób może mieć znaczenie tylko w kontekście biznesowym .. na przykład;
W twoim przykładzie / myservice / api / v1 / dziadkowie / {grandparentID} / rodzice / dzieci? Szukaj = {tekst}
Może być bardziej znaczącym zasobem, jeśli skrócisz to / myservice / api / v1 / siblingsOfGrandParents? Search = ..
w takim przypadku możesz zadeklarować „siblingsOfGrandParents” jako zasób .. upraszcza to adres URL
Jak zauważyli inni, istnieje powszechne, błędne przekonanie, że musisz dopasować się do każdego rodzaju relacji hierarchicznej między obiektami domen w bardziej wyraźnej formie w adresie URL. Nie ma twardej i szybkiej reguły na mapowanie 1 na 1 między obiektami domeny i zasoby i reprezentują wszystkie ich relacje ... pytanie powinno raczej brzmieć, wierzę ... czy istnieje potrzeba ujawnienia takiego związku poprzez adresy URL ... konkretnie segmenty ścieżki ... może nie.
źródło