Aplikacja internetowa jako klient interfejsu API REST: sposób obsługi identyfikatorów zasobów

21

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 hrefatrybucie? Jak zachować adres URL encji API w aplikacji internetowej, aby móc uzyskać encję, gdy użytkownik otworzy stronę docelową?

Wymagania wydają się sprzeczne:

  1. Hiperłącze hrefpowinno prowadzić do aplikacji internetowej, ponieważ jest to system obsługujący interfejs użytkownika
  2. hrefPowinien mieć jakiś identyfikator podmiotu ponieważ aplikacja internetowa musi być w stanie zająć się podmiot, gdy strona docelowa otwiera
  3. 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ć 1234adresu 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%2F1234są 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:

  1. 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.
  2. 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.
  3. Przechowuj wszystkie selfadresy 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. Trzymam http://my.rest.api/pet/678adres URL gdzieś z nowym kluczem, powiedzmy 3, i generuję adres URL strony internetowej jako http://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?

Pavel Gatilov
źródło
1
Nie jest jasne, co próbujesz osiągnąć, prawdopodobnie dlatego, że twoja prosta intencja kryje się pod warstwami architektury, które nakładasz na siebie, więc trudno powiedzieć, czy „RESTful API” naprawdę by ci pomogły. Z tego, co rozumiem na temat twojego problemu, opcja 2 jest prostym i wykonalnym rozwiązaniem. „Problem” jest nieodłączny od „interfejsów API RESTful”. RestIsJustSqlReinvented i rzeczywiście napotkasz ten sam problem, gdy spróbujesz odzyskać wystarczająco złożony podrozdział z dowolnego RDBMS. Użyj pamięci podręcznej lub reprezentacji zoptymalizowanej pod kątem zapytań.
back2dos

Odpowiedzi:

5

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:

<Entity type="Pet">
    <Link rel="self" href="http://example.com/pets/1" />
    <Link rel="owner" href="http://example.com/people/1" />
    <UniqueName>Spot</UniqueName>
</Entity>

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:

http://example.com/GUI/pets/Spot

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:

http://example.com/GUI/owners/John/pets/1 (pierwsze zwierzę na liście dla Johna)

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:

<Entity type="ResourceList">
    <Link rel="people" href="http://example.com/api/people" />
    <Link rel="pets" href="http://example.com/api/pets" />
</Entity>

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:

<Entity type="Person">
    <Link rel="self" href="http://example.com/api/people/1" />
    <Pets>
        <Link rel="pet" href="http://example.com/api/pets/1" />
        <Link rel="pet" href="http://example.com/api/pets/2" />
    </Pets>
    <UniqueName>John</UniqueName>
</Entity>
<Entity type="Person">
    <Link rel="self" href="http://example.com/api/people/2" />
    <Pets>
        <Link rel="pet" href="http://example.com/api/pets/3" />
    </Pets>
    <UniqueName>Jane</UniqueName>
</Entity>

GUI przechodzi przez wszystkie zasoby i wypisuje element listy dla każdej osoby używającej UniqueName jako „id”:

<a href="http://example.com/gui/people/1">John</a>
<a href="http://example.com/gui/people/2">Jane</a>

Robiąc to, może również przetwarzać każdy znaleziony link z rel „pet” i uzyskać zasób zwierzaka, taki jak:

<Entity type="Pet">
    <Link rel="self" href="http://example.com/api/pets/1" />
    <Link rel="owner" href="http://example.com/api/people/1" />
    <UniqueName>Spot</UniqueName>
</Entity>

Za pomocą tego może wydrukować link, taki jak:

<!-- Assumes that a pet can exist without an owner -->
<a href="http://example.com/gui/pets/Spot">Spot</a>

lub

<!-- Assumes that a pet MUST have an owner -->
<a href="http://example.com/gui/people/John/pets/Spot">Spot</a>

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:

  1. Strona jest otwarta i prośba o Spot zwierzaka.
  2. Załaduj listę zasobów z punktu wejścia API.
  3. Załaduj zasób powiązany z terminem „zwierzęta domowe”.
  4. Przejrzyj każdy zasób z odpowiedzi „zwierzaków domowych” i znajdź taki, który pasuje do Spot.
  5. Wyświetl informacje o miejscu.

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.

Mikrofon
źródło
Dzięki, Mike. Zaktualizowałem moje pytanie, aby było bardziej jasne. Problem z twoją odpowiedzią polega na tym, że nie mogę zgodzić się, aby klient REST mógł analizować adresy URL. Jeśli tak, to zostaje sprzężony z adresami URL. I to narusza jedną z podstawowych idei REST: klienci powinni używać rels 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, że relpozostaną takie same. Analiza adresów URL przenosi nas bliżej SOAP niż REST.
Pavel Gatilov
Jeszcze raz dziękuję. Opisałeś dotychczasowe podejście. W pewien sposób ujawniamy identyfikatory. Jedyną rzeczą jest to, że staramy się ujawniać naturalne identyfikatory, gdy tylko jest to możliwe.
Pavel Gatilov
6

Czy to wszystko oznacza, że ​​interfejsy API RESTful nie mogą służyć jako zaplecze dla aplikacji internetowych?

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/...i http://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.

Doug
źródło
Nie oczekuję, że aplikacja internetowa będzie po prostu reprezentacją interfejsu API. Może mieć wiele różnic, np. Wyświetlać kilka zasobów podrzędnych wraz z rootem na jednej stronie. Nie chcę, aby adresy URL aplikacji internetowej zawierały wewnętrzne identyfikatory pamięci danych API, jeśli masz na myśli to, że oczekuję, że oba systemy będą takie same. Nie zależy mi na wydajności, to nie problem. Pytanie brzmi tak: „Jak wstawić 3 my.web.app/pets/3bez analizowania adresów URL interfejsu API REST”?
Pavel Gatilov
Korygowanie własnego ponownego sformułowania: „Jak wprowadzić 3 my.web.app/pets/3bez analizowania adresu URL odpowiedniego interfejsu API REST my.rest.api/v0/persons/2/pets/3? A co mam tam umieścić?
Pavel Gatilov
Myślę, że mylisz stan klienta z reprezentacjami, które określają ten stan. Nie umieścić 3w app/pets/3ponieważ app/pets/3jest 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.
Doug
Pomyśl o tym w ten sposób - zapomnij o interfejsie API i aplikacji. Załóżmy, że chcesz utworzyć witrynę, która pozwoli użytkownikom gromadzić ulubione posty na Facebooku i Twitterze. To są systemy zdalne. Nie będziesz próbował tunelować ani szablonować identyfikatorów URI do tych systemów za pośrednictwem własnego. Utworzyłbyś zasób „forum” i to twój serwer wiedziałby, że to board/1wskazuje - facebook.com/post/123i twitter.com/status/789kiedy 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.
Doug
A zatem, ponieważ chcesz, aby Twój interfejs API był znacząco inny niż Twoja aplikacja (nadal uważam, że jest to wątpliwe) - traktowanie go jak systemu zdalnego nie różni się niczym. Powiedziałeś, że wydajność nie stanowi problemu, ale powiedziałeś także w swoim pytaniu, że coś takiego „wpłynie na wydajność”.
Doug
5

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/personktó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ć:

<Links>
    <Link ref="self" href="http://my.rest.api/api" />
    <Link rel="person" href="http://my.rest.api/api/person" />
    <Link rel="pet" href="http://my.rest.api/api/pet" />
</Links>

Ponieważ naszym celem jest wyświetlenie listy osób, następnie wysyłamy GETzapytanie do href z personlinku href, które może zwrócić:

<Links>
    <Link ref="self" href="http://my.rest.api/api/person" />
    <Link rel="search" href="http://my.rest.api/api/person/search" />
</Links>

Chcemy wyświetlać wyniki wyszukiwania, więc będziemy korzystać z usługi wyszukiwania, wysyłając GETzapytanie do searchlinku href, które może zwrócić:

<Persons>
    <Person>
        <Links>
            <Link rel="self" href="http://my.rest.api/api/person/1"/>
        </Links>
        <Pets>
            <Link rel="pet" href="http://my.rest.api/api/pet/10"/>
        </Pets>
    </Person>
    <Person>
        <Links>
            <Link rel="self" href="http://my.rest.api/api/person/2"/>
        </Links>
        <Pets>
            <Link rel="pet" href="http://my.rest.api/api/pet/20"/>
        </Pets>
    </Person>
</Persons>

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:

  • znana baza API: http://my.rest.api/api
  • podany adres URL dla pojedynczego podmiotu: http://my.rest.api/api/person/1
  • unikalny identyfikator: /person/1
  • nasz podstawowy adres URL: http://my.web.app
  • nasz wygenerowany URL frontonu: http://my.web.app/person/1

Nasze wyniki mogą wyglądać następująco:

<ul>
    <li><a href="http://my.web.app/person/1">A person</a></li>
    <li><a href="http://my.web.app/person/2">A person</a></li>
</ul>

Gdy użytkownik podąży za tym linkiem do strony ze szczegółowymi informacjami, do jakiego adresu URL wysyłamy GETprośbę o szczegółowe informacje na ten temat person? Znamy naszą metodę mapowania adresów URL zaplecza na adresy URL, dlatego po prostu odwracamy:

  • front-end URL: http://my.web.app/person/1
  • nasz podstawowy adres URL: http://my.web.app
  • unikalny identyfikator: /person/1
  • znana baza API: http://my.rest.api/api
  • wygenerowany URL interfejsu API: http://my.rest.api/api/person/1

Jeśli interfejs API REST zmieni się w taki sposób, że personteraz jest adres URL, http://my.rest.api/api/different-person-base/person/1a 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:

  • podstawowy adres URL interfejsu API
  • personrelacja
  • searchrelacja

Nie 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/1moż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 selfadresu 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/1przykładzie byśmy skończyli http://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/detailrouting, a resztę użyjemy jako nieprzezroczysty identyfikator ciągu.


Myślę, że wprowadza to wyjątkowo ścisłe połączenie aplikacji internetowej z interfejsem API.

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.

Co jeśli chciałbym, aby struktura URL aplikacji internetowej różniła się od interfejsu API?

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.

Co się stanie, jeśli podmioty mojej aplikacji sieci Web nie będą mapowane na jednostki interfejsu API 1-1?

Odpowiedź zależy od relacji. Tak czy inaczej, potrzebujesz sposobu mapowania interfejsu użytkownika na adresy URL API.

Mike Partridge
źródło
Mam jeden problem z tym podejściem. To właściwie numer 1 na mojej liście rozwiązań. Nie dostaję tego: jeśli aplikacja internetowa nie interpretuje adresów URL i traktuje unikalne identyfikatory jako nieprzezroczyste ciągi (po prostu person/1, pet/3), to skąd miałaby wiedzieć, że jeśli przeglądarka się otworzy http://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?
Pavel Gatilov
Dobre pytanie! Zaktualizowałem odpowiedź swoją odpowiedzią.
Mike Partridge
Dzięki, Mike. Myślę, że wprowadza to wyjątkowo ścisłe połączenie aplikacji internetowej z interfejsem API. Co jeśli chciałbym, aby struktura URL aplikacji internetowej różniła się od interfejsu API? Co się stanie, jeśli podmioty mojej aplikacji sieci Web nie będą mapowane na jednostki interfejsu API 1-1? Nadal uważam, że lepiej podejść do ujawnienia niektórych identyfikatorów, ale zachęcić klientów do korzystania z łączy do nawigacji.
Pavel Gatilov
To ciekawy temat, więc mam nadzieję, że niczego mi nie brakuje. Zaktualizowałem swoją odpowiedź o odpowiedzi na Twój komentarz. Myślę, że ujawnienie niektórych identyfikatorów jest dobrym kompromisem między całkowitą RESTfulness a użytecznością.
Mike Partridge
Moja główna troska tutaj jest nieco bardziej praktyczna. Używam ASP.NET MVC do implementacji aplikacji internetowej i ze względu na niektóre wewnętrzne reguły muszę zdefiniować wzorce adresów URL, które obsługuje aplikacja. To znaczy, jeśli zdefiniowano / a / {id}, aplikacja będzie obsługiwać / a / 1, ale nie będzie to / a / 1 / b / 2. Prowadzi to do konieczności ponownej kompilacji aplikacji sieci Web, jeśli adresy URL interfejsu API REST zmienią się nie tylko w celu zachowania zakładek z adresami URL, ale także po prostu sprawią, że aplikacja internetowa będzie działać po przejściu z katalogu głównego. Po prostu dlatego, że hiperłącza osadzone na stronach HTML nie będą działać bez tego.
Pavel Gatilov
2

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.

Nie powinienem ujawniać surowych identyfikatorów moich bytów, ale raczej zwracać hiperłącza z rel = "self"

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.

Czy to wszystko oznacza, że ​​interfejsy API RESTful nie mogą służyć jako zaplecze dla aplikacji internetowych?

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.

daramasala
źródło
0

Myślę, że jeśli trzymasz się czegoś podobnego do tego, Atom Syndication Formatjesteś 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.

rusev
źródło
Może czegoś nie rozumiem, ale wydaje mi się, że twoja odpowiedź nie wyjaśnia, jak generować adresy URL aplikacji internetowej, która jest klientem interfejsu API REST, prawda?
Pavel Gatilov
0

Nie martw się o adresy URL, martw się o typy mediów.

Zobacz tutaj (w szczególności trzeci punkt).

Interfejs API REST powinien poświęcić prawie cały swój wysiłek opisowy na zdefiniowanie typów mediów używanych do reprezentowania zasobów i sterowania stanem aplikacji, lub na definiowanie rozszerzonych nazw relacji i / lub narzutów z włączonym hipertekstem dla istniejących standardowych typów mediów. .


W przypadku typowej aplikacji internetowej klient jest człowiekiem ; przeglądarka jest tylko agentem .

Tak jak tag kotwicy

          <a href="example.com/foo/123">click here</a>

odpowiada coś takiego

          <link type="text/html" rel="self" href="example.com/foo/123">

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.

Rodrick Chapman
źródło
Rodrick, moje pytanie nie dotyczy budowy interfejsu API, ale raczej zbudowania aplikacji internetowej opartej na interfejsie API RESTful. Trudno mi zrozumieć, w jaki sposób typy multimediów mogą pomóc mi w tworzeniu adresów URL aplikacji internetowej. Chociaż typy nośników mają kluczowe znaczenie dla umowy o świadczenie usług i wykrywalności.
Pavel Gatilov
@PavelGatilov - Czy klient Twojej aplikacji internetowej jest człowiekiem?
Rodrick Chapman
Tak to jest. I bardzo słabo wykwalifikowany.
Pavel Gatilov
0

Najprostszym sposobem jest prawdopodobnie użycie adresów URL API zasobów jako ich identyfikatorów ciągów. Ale adresy URL stron internetowych, takie jak http://my.web.app/person/http%3A%2F%2Fmy.rest.api%2Fapi%2Fperson%2F1234, są brzydkie.

Myślę, że masz rację, to najprostszy sposób. Możesz relatywizować adresy URL, http://my.rest.api/apiaby były mniej brzydkie:

http://my.web.app/person/person%2F1234

Jeśli URL podany przez API nie jest względny w stosunku do tej bazy, degraduje się do brzydkiej formy:

http://my.web.app/person/http%3A%2F%2Fother.api.host%2Fapi%2Fperson%2F1234

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:

http://my.web.app/person/1234 (best case)
http://my.web.app/http://other.api.host/api/person/1234 (ugly case)
ajlane
źródło