Kilka koncepcji związanych z konfliktem REST w mojej głowie, gdy próbuję go zaimplementować.
Mam system API zaplecza REST, który obsługuje logikę biznesową, oraz aplikację internetową, która udostępnia interfejs użytkownika. Z różnych zasobów na temat REST (szczególnie REST w praktyce: Hypermedia i architektura systemów ) wiem, że nie powinienem ujawniać surowych identyfikatorów moich bytów, ale raczej zwracać hiperłącza rel="self"
.
Rozważ przykład. Interfejs API REST ma zasób, który zwraca osobę:
<Person>
<Links>
<Link rel="self" href="http://my.rest.api/api/person/1234"/>
</Links>
<Pets>
<Link rel="pet" href="http://my.rest.api/api/pet/678"/>
</Pets>
</Person>
Problem pojawia się w przypadku aplikacji internetowej. Załóżmy, że zwraca stronę zawierającą hiperłącze do przeglądarek:
<body class="person">
<p>
<a href="http://my.web.app/pet/???????" />
</p>
</body>
Co powinienem umieścić w href
atrybucie? Jak zachować adres URL encji API w aplikacji internetowej, aby móc uzyskać encję, gdy użytkownik otworzy stronę docelową?
Wymagania wydają się sprzeczne:
- Hiperłącze
href
powinno prowadzić do aplikacji internetowej, ponieważ jest to system obsługujący interfejs użytkownika href
Powinien mieć jakiś identyfikator podmiotu ponieważ aplikacja internetowa musi być w stanie zająć się podmiot, gdy strona docelowa otwiera- Ta aplikacja mówi, że aplikacja internetowa nie powinna analizować / konstruować adresów URL REST, ponieważ nie jest to REST-ful
Identyfikatory URI powinny być nieprzejrzyste dla konsumentów. Tylko wydawca identyfikatora URI wie, jak go zinterpretować i odwzorować na zasób.
Dlatego nie mogę po prostu pobrać 1234
adresu URL odpowiedzi interfejsu API, ponieważ jako klient RESTful powinienem traktować go tak, jakby był czymś podobnym http://my.rest.api/api/AGRIDd~ryPQZ^$RjEL0j
. Z drugiej strony muszę podać adres URL, który prowadzi do mojej aplikacji internetowej i wystarczy, aby aplikacja w jakiś sposób przywróciła oryginalny adres URL interfejsu API i użyła tego adresu URL, aby uzyskać dostęp do zasobów interfejsu API.
Najprostszym sposobem jest prawdopodobnie użycie adresów URL API zasobów jako ich identyfikatorów ciągów. Ale adresy URL stron internetowych http://my.web.app/person/http%3A%2F%2Fmy.rest.api%2Fapi%2Fperson%2F1234
są brzydkie.
Wszystko wydaje się dość łatwe w przypadku aplikacji komputerowej lub jednostronicowej aplikacji javascript. Ponieważ żyją nieprzerwanie, mogą po prostu przechowywać adresy URL w pamięci wraz z obiektami usług przez cały okres użytkowania aplikacji i używać ich w razie potrzeby.
Dzięki aplikacji internetowej mogę sobie wyobrazić kilka podejść, ale wszystkie wydają się dziwne:
- Zamień hosta na adresy URL API i zachowaj tylko wynik. Ogromną wadą jest to, że wymaga aplikacji internetowej do obsługi dowolnego adresu URL generowanego przez interfejs API, co oznacza monstrualne połączenie. Co więcej, nie jest to ponownie RESTful, ponieważ moja aplikacja internetowa zaczyna interpretować adresy URL.
- Ujawnij nieprzetworzone identyfikatory w interfejsie API REST wraz z łączami, użyj ich do zbudowania adresów URL aplikacji sieci Web, a następnie użyj identyfikatorów na serwerze aplikacji sieci Web, aby znaleźć wymagane zasoby w interfejsie API. Jest to lepsze, ale wpłynie to na wydajność serwera aplikacji WWW, ponieważ aplikacja internetowa będzie musiała przejść przez nawigację usługi REST, wydając ciąg żądań get-by-id jakiejkolwiek formy, aby obsłużyć dowolne żądanie z przeglądarki. W przypadku nieco zagnieżdżonego zasobu może to być kosztowne.
- Przechowuj wszystkie
self
adresy URL zwrócone przez interfejs API w trwałym mapowaniu (DB?) Na serwerze aplikacji WWW. Wygeneruj dla nich kilka identyfikatorów, użyj ich do zbudowania adresów URL stron aplikacji sieci Web i uzyskania adresów URL zasobów usługi REST. Tj. Trzymamhttp://my.rest.api/pet/678
adres URL gdzieś z nowym kluczem, powiedzmy3
, i generuję adres URL strony internetowej jakohttp://my.web.app/pet/3
. Wygląda to jak pewnego rodzaju implementacja pamięci podręcznej HTTP. Nie wiem dlaczego, ale wydaje mi się to dziwne.
Czy to wszystko oznacza, że interfejsy API RESTful nie mogą służyć jako zaplecze dla aplikacji internetowych?
źródło
Odpowiedzi:
Edytowano w celu uwzględnienia aktualizacji pytań, poprzednia odpowiedź została usunięta
Patrząc na zmiany w swoim pytaniu, myślę, że rozumiem problem, z którym się jeszcze bardziej borykasz. Ponieważ w twoich zasobach nie ma pola, które byłoby identyfikatorem (tylko link), nie możesz w żaden sposób odwoływać się do tego konkretnego zasobu w GUI (tj. Link do strony opisującej konkretnego zwierzaka).
Pierwszą rzeczą do ustalenia jest to, czy zwierzę ma sens bez właściciela. Jeśli możemy mieć zwierzaka bez właściciela, powiedziałbym, że potrzebujemy jakiejś wyjątkowej właściwości na zwierzaku, której możemy użyć, aby się do niego odwołać. Nie sądzę, aby naruszało to nieujawnianie identyfikatora bezpośrednio, ponieważ rzeczywisty identyfikator zasobu byłby nadal ukryty w łączu, którego klient REST nie przeanalizowałby. Mając to na uwadze, nasze zasoby dla zwierząt domowych mogą wyglądać następująco:
Możemy teraz zaktualizować nazwę tego zwierzaka z Spot do Fido bez konieczności bałagania się z jakimkolwiek identyfikatorem zasobu w całej aplikacji. Podobnie możemy odwołać się do tego zwierzaka w naszym GUI za pomocą czegoś takiego:
Jeśli zwierzę nie ma sensu bez właściciela (lub zwierzęta nie są dozwolone w systemie bez właściciela), możemy użyć właściciela jako części „tożsamości” zwierzęcia w systemie:
Jedna mała uwaga, jeśli zarówno Zwierzęta, jak i Ludzie mogą istnieć osobno, nie uczyniłbym punktu wejścia dla interfejsu API zasobem „Ludzie”. Zamiast tego stworzyłbym bardziej ogólny zasób, który zawierałby link do People and Pets. Może zwrócić zasób, który wygląda:
Więc znając tylko pierwszy punkt wejścia do API i nie przetwarzając żadnego z adresów URL w celu ustalenia identyfikatorów systemu, możemy zrobić coś takiego:
Użytkownik loguje się do aplikacji. Klient REST uzyskuje dostęp do całej listy dostępnych zasobów osób, które mogą wyglądać następująco:
GUI przechodzi przez wszystkie zasoby i wypisuje element listy dla każdej osoby używającej UniqueName jako „id”:
Robiąc to, może również przetwarzać każdy znaleziony link z rel „pet” i uzyskać zasób zwierzaka, taki jak:
Za pomocą tego może wydrukować link, taki jak:
lub
Jeśli pójdziemy z pierwszym linkiem i założymy, że nasz zasób wejściowy ma link ze stosunkiem „zwierzaków domowych”, przepływ sterowania wyglądałby w GUI mniej więcej tak:
Użycie drugiego linku byłoby podobnym łańcuchem zdarzeń, z tym wyjątkiem, że People jest punktem wejścia do API, a my najpierw otrzymalibyśmy listę wszystkich osób w systemie, znaleźliśmy tę, która pasuje, a następnie znaleźliśmy wszystkie zwierzęta, które należą do tej osoby (ponownie używając tagu rel) i znajdź osobę o nazwie Spot, abyśmy mogli wyświetlić związane z nią informacje.
źródło
rel
s do wybierania linków, ale nie powinni zakładać żadnej wiedzy o strukturze adresów URL. REST zapewnia, że API może dowolnie zmieniać adresy URL pod warunkiem, żerel
pozostaną takie same. Analiza adresów URL przenosi nas bliżej SOAP niż REST.Wątpię, czy warto rozróżniać interfejs API REST od aplikacji internetowej. Twój „aplikacja internetowa” powinny być tylko alternatywne (HTML) reprezentacje tych samych zasobów - to znaczy, ja nie rozumiem, w jaki sposób i dlaczego można oczekiwać, aby uzyskać dostęp
http://my.rest.api/...
ihttp://my.web.app/...
i że być jednocześnie takie same i różnią.W tym przypadku „klientem” jest przeglądarka, która rozumie HTML i JavaScript. To jest moim zdaniem aplikacja internetowa. Teraz możesz się nie zgodzić i pomyśleć, że uzyskujesz dostęp do wspomnianej aplikacji internetowej za pomocą foo.com i ujawniasz wszystko inne za pośrednictwem api.foo.com - ale musisz zapytać, w jaki sposób foo.com zapewniło mi reprezentację zasobu? „Back-end” foo.com doskonale rozumie, jak odkrywać zasoby z api.foo.com. Twoja aplikacja internetowa stała się jedynie serwerem proxy - niczym innym niż rozmową z innym interfejsem API (od kogoś innego).
Twoje pytanie można uogólnić na: „Jak opisać zasoby przy użyciu własnych identyfikatorów URI istniejących w innych systemach?” co jest trywialne, gdy weźmie się pod uwagę, że to nie klient (HTML / JavaScript) musi zrozumieć, jak to zrobić, ale serwer. Jeśli zgadzasz się z moim pierwszym wyzwaniem, możesz po prostu pomyśleć o swojej aplikacji sieciowej jako o oddzielnym interfejsie API REST, który zastępuje lub deleguje na inny interfejs API REST.
Tak więc, gdy klient uzyskuje dostęp
my.web.app/pets/1
, wie, że może przedstawić interfejs zwierzaka, ponieważ albo to zostało zwrócone przez szablon po stronie serwera, albo jeśli jest to asynchroniczne żądanie innej reprezentacji (np. JSON lub XML), nagłówek typu zawartości informuje o tym .Serwer, który to zapewnia, jest odpowiedzialny za zrozumienie, czym jest zwierzę domowe i jak odkryć zwierzę domowe w zdalnym systemie. To, jak to zrobisz, zależy od Ciebie - możesz po prostu wziąć identyfikator i wygenerować inny identyfikator URI, co uważasz za nieodpowiednie, lub możesz mieć własną bazę danych, która przechowuje zdalny identyfikator URI i zastępuje żądanie. Przechowywanie tego identyfikatora URI jest w porządku - jest to odpowiednik zakładek. Zrobiłbyś to wszystko, aby mieć osobną nazwę domeny. Nie wiem szczerze, dlaczego tego chcesz - twoje identyfikatory URI interfejsu API REST również muszą mieć możliwość tworzenia zakładek.
Przywołałeś już większość tego w swoim pytaniu, ale wydaje mi się, że sformułowałeś to w sposób, który tak naprawdę nie potwierdził, że jest to praktyczny sposób robienia tego, co chcesz robić (w oparciu o to, co uważam za arbitralne ograniczenie - aby API i aplikacja były oddzielne). Pytając, czy interfejsy API REST nie mogą być back-endami dla aplikacji internetowych, i sugerując, że wydajność byłaby problemem, myślę, że koncentrujesz się na niewłaściwych rzeczach. To tak, jakby powiedzieć, że nie możesz utworzyć Mashup. To tak, jakby powiedzieć, że sieć nie działa.
źródło
my.web.app/pets/3
bez analizowania adresów URL interfejsu API REST”?my.web.app/pets/3
bez analizowania adresu URL odpowiedniego interfejsu API RESTmy.rest.api/v0/persons/2/pets/3
? A co mam tam umieścić?3
wapp/pets/3
ponieważapp/pets/3
jest nieprzejrzysty, co wskazuje na zasób aplikacja internetowa chce. Jeśli jest to złożony widok kilku innych zasobów (w innych systemach - Twój interfejs API jest jednym z nich), to do Ciebie należy przechowywanie hiperłączy do tych systemów na serwerze aplikacji WWW, a następnie ich odzyskiwanie, rozwiązywanie ich do ich reprezentacji ( np. JSON lub XML), a następnie podaj je jako część odpowiedzi.board/1
wskazuje -facebook.com/post/123
itwitter.com/status/789
kiedy przejdziesz do przedstawienia reprezentacji swojej tablicy, będziesz musiał rozwiązać te identyfikatory URI na reprezentację, z którą możesz pracować. Pamięć podręczna w razie potrzeby.Przedmowa
Ta odpowiedź w szczególności odnosi się do pytania, jak zarządzać własnym schematem URL, w tym unikatowymi adresami URL z możliwością rezerwacji, dla zasobów, dla których interfejs API REST zaplecza nie ujawnia wyraźnie identyfikatora i bez interpretacji adresów URL dostarczonych przez interfejs API.
Odkrywalność wymaga pewnej wiedzy, więc oto moje podejście do rzeczywistego scenariusza:
Powiedzmy, że chcemy strony wyszukiwania, na
http://my.web.app/person
której wyniki zawierają link do strony ze szczegółami dla każdej osoby. Jedyną rzeczą, nasz kod front-end musi wiedzieć, aby zrobić coś w ogóle jest bazowy adres URL dla źródła danych REST:http://my.rest.api/api
. Odpowiedź na żądanie GET na ten adres URL może być:Ponieważ naszym celem jest wyświetlenie listy osób, następnie wysyłamy
GET
zapytanie do href zperson
linku href, które może zwrócić:Chcemy wyświetlać wyniki wyszukiwania, więc będziemy korzystać z usługi wyszukiwania, wysyłając
GET
zapytanie dosearch
linku href, które może zwrócić:Nareszcie mamy nasze wyniki, ale jak budujemy nasze frontowe adresy URL?
Zdejmijmy część, którą znamy na pewno: podstawowy adres URL interfejsu API, a resztę wykorzystaj jako nasz identyfikator frontonu:
http://my.rest.api/api
http://my.rest.api/api/person/1
/person/1
http://my.web.app
http://my.web.app/person/1
Nasze wyniki mogą wyglądać następująco:
Gdy użytkownik podąży za tym linkiem do strony ze szczegółowymi informacjami, do jakiego adresu URL wysyłamy
GET
prośbę o szczegółowe informacje na ten tematperson
? Znamy naszą metodę mapowania adresów URL zaplecza na adresy URL, dlatego po prostu odwracamy:http://my.web.app/person/1
http://my.web.app
/person/1
http://my.rest.api/api
http://my.rest.api/api/person/1
Jeśli interfejs API REST zmieni się w taki sposób, że
person
teraz jest adres URL,http://my.rest.api/api/different-person-base/person/1
a ktoś wcześniej zrobił zakładkęhttp://my.web.app/person/1
, interfejs API REST powinien (przynajmniej przez pewien czas) zapewnić zgodność wsteczną, odpowiadając na stary adres URL, przekierowując na nowy. Wszystkie generowane łącza frontonu automatycznie obejmowałyby nową strukturę.Jak zapewne zauważyłeś, jest kilka rzeczy, które musimy wiedzieć, aby poruszać się po API:
person
relacjasearch
relacjaNie sądzę, żeby było w tym coś złego; w żadnym momencie nie zakładamy określonej struktury adresu URL, więc struktura adresu URL jednostki
http://my.rest.api/api/person/1
może ulec zmianie i dopóki interfejs API zapewnia zgodność wsteczną, nasz kod będzie nadal działał.Zapytałeś, w jaki sposób nasza logika routingu może odróżnić dwa frontowe adresy URL:
http://my.rest.api/api/person/1
http://my.rest.api/api/pet/3
.Najpierw zaznaczę, że użyłeś bazy API w swoim komentarzu, gdy w naszym przykładzie używamy oddzielnych podstawowych adresów URL dla interfejsu użytkownika i interfejsu API REST. Kontynuuję przykład, używając oddzielnych baz, ale udostępnianie bazy nie stanowi problemu. Możemy (lub powinniśmy być w stanie) mapować metody routingu interfejsu użytkownika za pomocą typu nośnika z nagłówka Accept żądania.
Jeśli chodzi o przekierowanie do konkretnej strony ze szczegółami, nie możemy rozróżnić tych dwóch adresów URL, jeśli staramy się unikać jakiejkolwiek wiedzy o strukturze
self
adresu URL dostarczonego przez interfejs API (tj. Nieprzezroczysty identyfikator ciągu). Aby to zadziałało, umieśćmy w naszych adresach URL inny z naszych znanych fragmentów informacji - typ encji, z którym współpracujemy.Wcześniej nasze frontowe adresy URL miały format:
${UI base}/${opaque string id}
Nowym formatem może być:
${UI base}/${entity type}/${opaque string id}
Tak więc na
/person/1
przykładzie byśmy skończylihttp://my.web.app/person/person/1
.W tym formacie nasza logika routingu interfejsu użytkownika będzie działać
/person/person/1
, a wiedząc, że pierwszy token w ciągu został wstawiony przez nas samych, możemy go wyciągnąć i skierować do odpowiedniej (szczegółowej w tym przykładzie osoby) na tej stronie. Jeśli czujesz się zakłopotany tym adresem URL, możemy wstawić tam trochę więcej; może:http://my.web.app/person/detail/person/1
W takim przypadku przeanalizujemy
/person/detail
routing, a resztę użyjemy jako nieprzezroczysty identyfikator ciągu.Zgaduję, że masz na myśli, że ponieważ nasz wygenerowany front-end URL zawiera część adresu URL interfejsu API, jeśli struktura adresu URL interfejsu API zmieni się bez obsługi starej struktury, potrzebujemy zmiany kodu, aby przetłumaczyć zaznaczony adres URL na nowa wersja adresu URL interfejsu API. Innymi słowy, jeśli interfejs API REST zmieni identyfikator zasobu (nieprzezroczysty ciąg), wówczas nie będziemy mogli rozmawiać z serwerem o tym zasobie przy użyciu starego identyfikatora. Nie sądzę, abyśmy mogli uniknąć zmiany kodu w takiej sytuacji.
Możesz użyć dowolnej struktury URL. Pod koniec dnia adres URL konkretnego bukmachera dla określonego zasobu musi zawierać coś, czego można użyć do uzyskania adresu URL interfejsu API, który jednoznacznie identyfikuje ten zasób. Jeśli wygenerujesz własny identyfikator i umieścisz go w pamięci podręcznej za pomocą adresu URL interfejsu API, tak jak w podejściu nr 3, będzie to działać, dopóki ktoś nie spróbuje użyć tego adresu URL z zakładkami po usunięciu tego wpisu z pamięci podręcznej.
Odpowiedź zależy od relacji. Tak czy inaczej, potrzebujesz sposobu mapowania interfejsu użytkownika na adresy URL API.
źródło
person/1
,pet/3
), to skąd miałaby wiedzieć, że jeśli przeglądarka się otworzyhttp://my.rest.api/api/person/1
, powinna pokazywać interfejs użytkownika osoby, a jeśli otwiera sięhttp://my.rest.api/api/pet/3
, a następnie interfejs użytkownika zwierzaka?Spójrzmy prawdzie w oczy, nie ma magicznego rozwiązania. Czy czytałeś Model dojrzałości Richardsona ? Podział dojrzałości architektury REST na 3 poziomy: zasoby, czasowniki HTTP i elementy sterujące hipermedia.
To jest kontrola hipermedialna. Czy naprawdę tego potrzebujesz? Takie podejście ma kilka bardzo dobrych zalet (o nich można przeczytać tutaj ). Ale nie ma czegoś takiego jak darmowe posiłki i będziesz musiał ciężko pracować (np. Drugie rozwiązanie), jeśli chcesz je otrzymać.
To kwestia równowagi - czy chcesz poświęcić wydajność (i skomplikować kod), ale uzyskać bardziej elastyczny system? A może wolisz, aby rzeczy były szybsze i prostsze, ale płacisz później, kiedy wprowadzisz zmiany w swoim API / modelu?
Jako ktoś, kto opracował podobny system (warstwa logiki biznesowej, warstwa internetowa i klienci WWW) wybrałem drugą opcję. Ponieważ moja grupa opracowała wszystkie warstwy, zdecydowaliśmy, że lepiej jest trochę powiązać (informując warstwę internetową o identyfikatorach encji i budowaniu adresów URL interfejsu API), w zamian otrzymując kod, który jest prostszy. Kompatybilność wsteczna również nie była istotna w naszym przypadku.
Jeśli aplikacja internetowa została opracowana przez firmę zewnętrzną lub jeśli problem polegał na kompatybilności wstecznej, moglibyśmy wybrać inaczej, ponieważ zmiana struktury adresu URL bez zmiany aplikacji internetowej była bardzo cenna. Wystarczająco, aby uzasadnić skomplikowanie kodu.
Myślę, że to oznacza, że nie musisz tworzyć idealnej implementacji REST. Możesz przejść do drugiego rozwiązania lub ujawnić identyfikator jednostki, a może przekazać adresy URL api . Jest OK, dopóki rozumiesz implikacje i kompromisy.
źródło
Myślę, że jeśli trzymasz się czegoś podobnego do tego,
Atom Syndication Format
jesteś dobry.Tutaj metadane opisujące reprezentowany wpis można określić za pomocą dodatkowych elementów / atrybutów:
Zgodnie z [RFC4287] zawiera identyfikator URI, który jednoznacznie identyfikuje wpis
Zgodnie z [RFC4287] ten element jest opcjonalny. Jeśli jest dołączony, zawiera identyfikator URI, którego klient powinien użyć do pobrania wpisu.
To tylko moje dwa centy.
źródło
Nie martw się o adresy URL, martw się o typy mediów.
Zobacz tutaj (w szczególności trzeci punkt).
W przypadku typowej aplikacji internetowej klient jest człowiekiem ; przeglądarka jest tylko agentem .
Tak jak tag kotwicy
odpowiada coś takiego
Adres URL jest nadal nieprzejrzysty dla użytkownika, zależy jej tylko na typach mediów (np
text/html, application/pdf, application/flv, video/x-flv, image/jpeg, image/funny-cat-picture etc
.). Tekst opisowy zawarty w kotwicy (i w atrybucie tytułu) jest jedynie sposobem na rozszerzenie rodzaju relacji w sposób zrozumiały dla ludzi.Powodem, dla którego chcesz, aby identyfikator URI był nieprzezroczysty dla klientów, jest ograniczenie sprzężenia (jeden z głównych celów REST). Serwer może zmieniać / reorganizować identyfikatory URI bez wpływu na klienta (o ile masz dobrą politykę buforowania - co może oznaczać brak buforowania).
W podsumowaniu
Tylko upewnij się, że klient (człowiek lub maszyna) dba o typy mediów i relacje, a nie adresy URL, a wszystko będzie dobrze.
źródło
Myślę, że masz rację, to najprostszy sposób. Możesz relatywizować adresy URL,
http://my.rest.api/api
aby były mniej brzydkie:Jeśli URL podany przez API nie jest względny w stosunku do tej bazy, degraduje się do brzydkiej formy:
Aby pójść o krok dalej, sprawdź odpowiedź serwera API, aby określić, jaki widok chcesz przedstawić, i przestań kodować separator segmentu ścieżki i dwukropki:
źródło