Dlaczego warto dążyć do projektu RESTful?
Zasady RESTful wprowadzają funkcje, które sprawiają, że strony internetowe są łatwe (dla losowego użytkownika do „surfowania” po nich) do projektu API usług sieciowych , dzięki czemu są one łatwe w użyciu dla programisty. REST nie jest dobry, ponieważ jest REST, jest dobry, ponieważ jest dobry. I to jest dobre głównie dlatego, że jest proste .
Prostota zwykłego protokołu HTTP (bez kopert SOAP i POSTusług przeciążonych jednym identyfikatorem URI ), co niektórzy mogą nazwać „brakiem funkcji” , jest w rzeczywistości jego największą zaletą . Od samego początku HTTP prosi o adresowalność i bezpaństwowość : dwie podstawowe decyzje projektowe, które zapewniają skalowalność HTTP do dzisiejszych mega-witryn (i mega-usług).
Ale REST nie jest najlepszym rozwiązaniem: czasami może być odpowiedni styl RPC („zdalne wywołanie procedury” - na przykład SOAP) , a czasami inne potrzeby mają pierwszeństwo przed zaletami sieci Web. Jest okej. To, czego tak naprawdę nie lubimy, to niepotrzebna złożoność . Zbyt często programista lub firma sprowadza usługi w stylu RPC do zadań, z którymi zwykły stary HTTP mógłby sobie poradzić. Efekt jest taki, że HTTP jest zredukowane do protokołu transportowego dla olbrzymiego ładunku XML, który wyjaśnia, co się „naprawdę” dzieje (nie URI ani metoda HTTP dają o tym wskazówkę). Wynikowa usługa jest zbyt złożona, niemożliwa do debugowania i nie będzie działać, jeśli Twoi klienci nie będą mieli dokładnej konfiguracji zgodnie z zamierzeniami programisty.
W ten sam sposób kod Java / C # nie może być zorientowany obiektowo, samo użycie protokołu HTTP nie czyni projektu zgodnym z REST. Można złapać się w pośpiechu myślenia o swoich usługach w kategoriach działań i metod zdalnych, które należy nazwać. Nic dziwnego, że skończy się to głównie w usłudze w stylu RPC (lub w hybrydzie REST-RPC). Pierwszym krokiem jest myślenie inaczej. Projekt zgodny ze standardem REST można osiągnąć na wiele sposobów, jednym ze sposobów jest myślenie o aplikacji w kategoriach zasobów, a nie działań:
💡 Zamiast myśleć w kategoriach działań, które może wykonać („wyszukaj miejsca na mapie”) ...
... spróbuj myśleć kategoriami wyników tych działań („lista miejsc na mapie spełniających kryteria wyszukiwania”).
Poniżej przedstawię przykłady. (Innym kluczowym aspektem REST jest użycie HATEOAS - nie szczotkuję go tutaj, ale mówię o tym szybko w innym poście .)
Zagadnienia pierwszego projektu
Rzućmy okiem na proponowany projekt:
ACTION http://api.animals.com/v1/dogs/1/
Po pierwsze, nie powinniśmy rozważać tworzenia nowego czasownika HTTP ( ACTION). Ogólnie rzecz biorąc, jest to niepożądane z kilku powodów:
- (1) Biorąc pod uwagę tylko identyfikator URI usługi, w jaki sposób „przypadkowy” programista będzie wiedział, że
ACTIONczasownik istnieje?
- (2) jeśli programista wie, że istnieje, jak pozna jego semantykę? Co oznacza ten czasownik?
- (3) jakie właściwości (bezpieczeństwo, idempotencja) powinien mieć ten czasownik?
- (4) co zrobić, jeśli programista ma bardzo prostego klienta, który obsługuje tylko standardowe czasowniki HTTP?
- (5) ...
Rozważmy teraz użyciePOST (poniżej omówię dlaczego, po prostu uwierz mi na słowo):
POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
{"action":"bark"}
To mogłoby być OK ... ale tylko wtedy, gdy :
{"action":"bark"}był dokumentem; i
/v1/dogs/1/był identyfikatorem URI „procesora dokumentów” (podobnym do fabrycznego). „Procesor dokumentów” to URI, do którego po prostu „wrzucasz” i „zapomnisz” o nich - procesor może przekierować cię do nowo utworzonego zasobu po „rzuceniu”. Np. URI do wysyłania wiadomości w usłudze brokera komunikatów, który po wysłaniu przekierowuje do URI, który pokazuje stan przetwarzania wiadomości.
Nie wiem zbyt wiele o twoim systemie, ale założę się, że oba nie są prawdziwe:
{"action":"bark"} nie jest dokumentem , w rzeczywistości jest to metoda, którą próbujesz wkraść się do serwisu; i
/v1/dogs/1/URI oznacza „pies” zasób (prawdopodobnie z psem id==1), a nie procesora dokumentów.
Więc wszystko, co teraz wiemy, to to, że powyższy projekt nie jest tak RESTful, ale co to dokładnie jest? Co w tym złego? Zasadniczo jest to złe, ponieważ jest to złożony identyfikator URI o złożonych znaczeniach. Nic z tego nie można wywnioskować. Skąd programista miałby wiedzieć, że pies ma barkakcję, którą można potajemnie wprowadzić POSTdo niego?
Projektowanie wywołań API pytania
Przejdźmy więc do sedna i spróbujmy zaprojektować te szczekanie RESTO, myśląc w kategoriach zasobów . Pozwólcie mi zacytować książkę Restful Web Services :
POSTWniosek jest próbą stworzenia nowego zasobu od istniejącej. Istniejący zasób może być rodzicem nowego w sensie struktury danych, tak jak korzeń drzewa jest rodzicem wszystkich jego węzłów liści. Lub istniejący zasób może być specjalnym
zasobem „fabrycznym”, którego jedynym celem jest generowanie innych zasobów. Reprezentacja wysłana wraz z POSTżądaniem opisuje początkowy stan nowego zasobu. Podobnie jak w przypadku PUT, POSTwniosek nie musi w ogóle zawierać reprezentacji.
W następstwie powyższego opisu widać, że barkmożna modelować jako o subresource Urządzonydog (ponieważ barkzawiera się w psa, to jest kora jest „szczekaly” przez psa).
Z tego rozumowania już mamy:
- Metoda jest taka
POST
/barksZasobem jest podźródło psa:, /v1/dogs/1/barksreprezentujące bark„fabrykę”. Ten identyfikator URI jest unikalny dla każdego psa (ponieważ znajduje się poniżej /v1/dogs/{id}).
Teraz każdy przypadek na twojej liście ma określone zachowanie.
1. kora po prostu wysyła e-mail dog.emaili nic nie rejestruje.
Po pierwsze, czy szczekanie (wysyłanie wiadomości e-mail) jest zadaniem synchronicznym czy asynchronicznym? Po drugie, czy barkżądanie wymaga jakiegoś dokumentu (może e-mail), czy jest puste?
1.1 kora wysyła e-mail do dog.email i nic nie rejestruje (jako zadanie synchroniczne)
Ta sprawa jest prosta. Wezwanie do barkszasobów fabryki powoduje natychmiastowe wyświetlenie kory (wysłanie wiadomości e-mail), a odpowiedź (jeśli jest OK lub nie) jest natychmiast udzielana:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(entity-body is empty - or, if you require a **document**, place it here)
200 OK
Ponieważ nic nie rejestruje (nie zmienia), 200 OKto wystarczy. Pokazuje, że wszystko poszło zgodnie z oczekiwaniami.
1.2 Bark wysyła e-mail dog.emaili nic nie rejestruje (jako zadanie asynchroniczne)
W takim przypadku klient musi mieć możliwość śledzenia barkzadania. barkNastępnie zadanie powinno być zasobem z własnym URI .:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
W ten sposób każdy barkjest identyfikowalny. Klient może następnie wydać GETna barkURI, by poznać jego aktualny stan. Może nawet użyj, DELETEaby to anulować.
2. kora wysyła e-mail do, dog.emaila następnie zwiększadog.barkCount o 1
To może być trudniejsze, jeśli chcesz poinformować klienta, że dogzasób zostanie zmieniony:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}
303 See Other
Location: http://api.animals.com/v1/dogs/1
W tym przypadku locationintencją nagłówka jest poinformowanie klienta, że powinien się przyjrzeć dog. Z HTTP RFC o303 :
Ta metoda istnieje głównie po to, aby dane wyjściowe
POSTaktywowanego skryptu przekierowywały agenta użytkownika do wybranego zasobu.
Jeśli zadanie jest asynchroniczne, barkzasób podrzędny jest potrzebny tak jak 1.2sytuacja i 303powinien zostać zwrócony GET .../barks/Ypo zakończeniu zadania.
3. Kora tworzy nowy " bark" rekord z bark.timestampnagraniem, kiedy nastąpiło szczekanie. Zwiększa się również dog.barkCounto 1.
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Tutaj barkjest utworzony w wyniku żądania, więc status 201 Createdjest stosowany.
Jeśli tworzenie jest asynchroniczne, zamiast tego 202 Acceptedwymagana jest litera ( zgodnie z dokumentem HTTP RFC ).
Zapisana sygnatura czasowa jest częścią barkzasobu i można ją pobrać za pomocą GETdo. Zaktualizowany pies może być również w tym „udokumentowany” GET dogs/X/barks/Y.
4. bark uruchamia polecenie systemowe, aby pobrać najnowszą wersję kodu psa z Githuba. Następnie wysyła wiadomość tekstową z dog.ownerinformacją, że nowy nieśmiertelnik jest w produkcji.
Sformułowanie tego jest skomplikowane, ale jest to w zasadzie proste zadanie asynchroniczne:
POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=
(document body, if needed)
202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44
Następnie klient wyda GETS aby /v1/dogs/1/barks/a65h44poznać aktualny stan (jeśli kod został wycofany, to adres e-mail został wysłany do właściciela i takie tam). Za każdym razem, gdy pies się zmienia, obowiązuje a 303.
Podsumowując
Cytując Roya Fieldinga :
Jedyną rzeczą, której REST wymaga od metod, jest to, że są one jednolicie zdefiniowane dla wszystkich zasobów (tj. Aby pośrednicy nie musieli znać typu zasobu, aby zrozumieć znaczenie żądania).
W powyższych przykładach POSTjest jednolicie zaprojektowany. To sprawi, że pies " bark". To nie jest bezpieczne (co oznacza, że kora ma wpływ na zasoby), ani idempotentne (każde żądanie daje nowy bark), co dobrze pasuje do POSTczasownika.
Programista będzie wiedział: a POSTdo barksdaje bark. Kody statusu odpowiedzi (w razie potrzeby również z treścią encji i nagłówkami) wyjaśniają, co się zmieniło i jak klient może i powinien postępować.
Uwaga: Głównymi używanymi źródłami były: książka „ Restful Web Services ”, HTTP RFC i blog Roya Fieldinga .
Edytować:
Pytanie, a tym samym odpowiedź, zmieniły się nieco od czasu ich powstania. Oryginalne pytanie poproszony o projektowaniu URI, takich jak:
ACTION http://api.animals.com/v1/dogs/1/?action=bark
Poniżej znajduje się wyjaśnienie, dlaczego nie jest to dobry wybór:
Sposób, w jaki klienci mówią serwerowi, CO ZROBIĆ z danymi, to informacje o metodzie .
- Usługi sieciowe RESTful przekazują informacje o metodzie w metodzie HTTP.
- Typowe usługi w stylu RPC i SOAP przechowują je w treści encji i nagłówku HTTP.
KTÓRA CZĘŚĆ danych [klient chce, aby serwer] działała, jest informacją o zakresie .
- Usługi RESTful używają identyfikatora URI. Usługi w stylu SOAP / RPC ponownie używają treści encji i nagłówków HTTP.
Jako przykład weźmy identyfikator URI Google http://www.google.com/search?q=DOG. Tam znajdują się informacje o metodzie GETi informacje o zakresie /search?q=DOG.
Krótko mówiąc:
- W architekturach RESTful informacje o metodzie trafiają do metody HTTP.
- W architekturach zorientowanych na zasoby informacje o zakresie trafiają do identyfikatora URI.
I praktyczna zasada:
Jeśli metoda HTTP nie pasuje do informacji o metodzie, usługa nie jest zgodna z REST. Jeśli informacje o zakresie nie znajdują się w identyfikatorze URI, usługa nie jest zorientowana na zasoby.
Możesz umieścić akcję „bark” w adresie URL (lub w treści encji) i użyć . Nie ma problemu, to działa i może być najprostszym sposobem na zrobienie tego, ale nie jest to RESTful .POST
Aby Twoja usługa była naprawdę RESTful, być może będziesz musiał cofnąć się o krok i pomyśleć o tym, co naprawdę chcesz tutaj zrobić (jaki wpływ będzie to miało na zasoby).
Nie mogę mówić o twoich konkretnych potrzebach biznesowych, ale pozwól mi podać przykład: Rozważ usługę zamawiania RESTful, w której zamówienia są z identyfikatorami URI example.com/order/123.
Teraz powiedz, że chcemy anulować zamówienie, jak to zrobimy? Można pokusić się o myślenie, że jest to „działanie” „anulowania ” i zaprojektowanie go jako POST example.com/order/123?do=cancel.
To nie jest RESTful, jak mówiliśmy powyżej. Zamiast tego możemy PUTnową reprezentację elementu orderz canceledelementem wysłanym do true:
PUT /order/123 HTTP/1.1
Content-Type: application/xml
<order id="123">
<customer id="89987">...</customer>
<canceled>true</canceled>
...
</order>
I to wszystko. Jeśli zamówienia nie można anulować, można zwrócić określony kod statusu. (Dla uproszczenia może być również dostępny projekt zasobów podrzędnych, podobnie jak POST /order/123/canceledw przypadku treści encji true).
W swoim konkretnym scenariuszu możesz spróbować czegoś podobnego. W ten sposób, gdy na przykład szczeka pies, GETat /v1/dogs/1/może zawierać tę informację (np<barking>true</barking> . ) . Lub ... jeśli to zbyt skomplikowane, poluzuj swoje wymagania RESTful i trzymaj sięPOST .
Aktualizacja:
Nie chcę, aby odpowiedź była zbyt obszerna, ale zrozumienie algorytmu ( akcji ) jako zestawu zasobów zajmuje trochę czasu . Zamiast myśleć w kategoriach działań ( „poszukaj miejsc na mapie” ), trzeba myśleć kategoriami wyników tego działania ( „lista miejsc na mapie spełniających kryteria wyszukiwania” ).
Może się okazać, że wrócisz do tego kroku, jeśli okaże się, że projekt nie pasuje do jednolitego interfejsu HTTP.
Zmienne zapytania to informacje o zakresie , ale nie oznaczają nowych zasobów ( /post?lang=enjest to wyraźnie ten sam zasób, co /post?lang=jptylko inna reprezentacja). Są raczej używane do przekazywania stanu klienta (na przykład ?page=10stan ten nie jest przechowywany na serwerze; ?lang=enjest to również przykład) lub parametrów wejściowych do zasobów algorytmicznych ( /search?q=dogs, /dogs?code=1). Ponownie, nie są to odrębne zasoby.
Właściwości (metody) czasowników HTTP:
Inną jasną kwestią, która pojawia się ?action=somethingw identyfikatorze URI, nie jest RESTful, są właściwości czasowników HTTP:
GETi HEADsą bezpieczne (i idempotentne);
PUTi DELETEsą tylko idempotentni;
POST Nie jest.
Bezpieczeństwo : żądanie GETlub HEADżądanie to żądanie odczytania niektórych danych, a nie żądanie zmiany stanu serwera. Klient może poprosić GETlub HEADpoprosić 10 razy i jest to to samo, co zrobienie tego raz lub nigdy nie .
Idempotencja : idempotentna operacja w jednej, która ma ten sam efekt, niezależnie od tego, czy zastosujesz ją raz, czy więcej niż raz (w matematyce mnożenie przez zero jest idempotentne). Jeśli DELETEraz otrzymałeś zasób, ponowne usunięcie będzie miało ten sam efekt (zasób GONEjuż jest ).
POSTnie jest ani bezpieczny, ani idempotentny. Wysłanie dwóch identycznych POSTżądań do zasobu „fabryki” prawdopodobnie spowoduje, że dwa podrzędne zasoby będą zawierały te same informacje. W przypadku przeciążenia (metoda w URI lub treść jednostki) POSTwszystkie zakłady są wyłączone.
Obie te właściwości były ważne dla powodzenia protokołu HTTP (w zawodnych sieciach!): Ile razy aktualizowałeś ( GET) stronę bez czekania, aż zostanie w pełni załadowana?
Utworzenie akcji i umieszczenie jej w adresie URL wyraźnie łamie kontrakt metod HTTP. Po raz kolejny technologia na to pozwala, możesz to zrobić, ale to nie jest projekt RESTful.