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 POST
usł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
ACTION
czasownik 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 bark
akcję, którą można potajemnie wprowadzić POST
do 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 :
POST
Wniosek 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, POST
wniosek nie musi w ogóle zawierać reprezentacji.
W następstwie powyższego opisu widać, że bark
można modelować jako o subresource Urządzonydog
(ponieważ bark
zawiera się w psa, to jest kora jest „szczekaly” przez psa).
Z tego rozumowania już mamy:
- Metoda jest taka
POST
/barks
Zasobem jest podźródło psa:, /v1/dogs/1/barks
reprezentują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.email
i 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 barks
zasobó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 OK
to wystarczy. Pokazuje, że wszystko poszło zgodnie z oczekiwaniami.
1.2 Bark wysyła e-mail dog.email
i nic nie rejestruje (jako zadanie asynchroniczne)
W takim przypadku klient musi mieć możliwość śledzenia bark
zadania. bark
Nastę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 bark
jest identyfikowalny. Klient może następnie wydać GET
na bark
URI, by poznać jego aktualny stan. Może nawet użyj, DELETE
aby to anulować.
2. kora wysyła e-mail do, dog.email
a następnie zwiększadog.barkCount
o 1
To może być trudniejsze, jeśli chcesz poinformować klienta, że dog
zasó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 location
intencją 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
POST
aktywowanego skryptu przekierowywały agenta użytkownika do wybranego zasobu.
Jeśli zadanie jest asynchroniczne, bark
zasób podrzędny jest potrzebny tak jak 1.2
sytuacja i 303
powinien zostać zwrócony GET .../barks/Y
po zakończeniu zadania.
3. Kora tworzy nowy " bark
" rekord z bark.timestamp
nagraniem, kiedy nastąpiło szczekanie. Zwiększa się również dog.barkCount
o 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 bark
jest utworzony w wyniku żądania, więc status 201 Created
jest stosowany.
Jeśli tworzenie jest asynchroniczne, zamiast tego 202 Accepted
wymagana jest litera ( zgodnie z dokumentem HTTP RFC ).
Zapisana sygnatura czasowa jest częścią bark
zasobu i można ją pobrać za pomocą GET
do. 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.owner
informacją, ż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 GET
S aby /v1/dogs/1/barks/a65h44
poznać 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 POST
jest 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 POST
czasownika.
Programista będzie wiedział: a POST
do barks
daje 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 GET
i 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 PUT
nową reprezentację elementu order
z canceled
elementem 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/canceled
w 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, GET
at /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=en
jest to wyraźnie ten sam zasób, co /post?lang=jp
tylko inna reprezentacja). Są raczej używane do przekazywania stanu klienta (na przykład ?page=10
stan ten nie jest przechowywany na serwerze; ?lang=en
jest 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=something
w identyfikatorze URI, nie jest RESTful, są właściwości czasowników HTTP:
GET
i HEAD
są bezpieczne (i idempotentne);
PUT
i DELETE
są tylko idempotentni;
POST
Nie jest.
Bezpieczeństwo : żądanie GET
lub HEAD
żądanie to żądanie odczytania niektórych danych, a nie żądanie zmiany stanu serwera. Klient może poprosić GET
lub HEAD
poprosić 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 DELETE
raz otrzymałeś zasób, ponowne usunięcie będzie miało ten sam efekt (zasób GONE
już jest ).
POST
nie 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) POST
wszystkie 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.