W tym artykule autor twierdzi, że
Czasami wymagane jest ujawnienie operacji w interfejsie API, która z natury nie jest w stanie RESTful.
i to
Jeśli interfejs API ma zbyt wiele działań, oznacza to, że albo został zaprojektowany z punktu widzenia RPC, a nie z wykorzystaniem zasad RESTful, lub że dany interfejs API jest naturalnie lepiej dopasowany do modelu typu RPC.
Odzwierciedla to, co czytałem i słyszałem także w innych miejscach.
Uważam to jednak za dość mylące i chciałbym lepiej zrozumieć tę sprawę.
Przykład I: Wyłączanie maszyny wirtualnej za pośrednictwem interfejsu REST
Są, moim zdaniem, dwa zasadniczo różne sposoby modelowania zamknięcia maszyny wirtualnej. Każda droga może mieć kilka odmian, ale na razie skupmy się na najbardziej fundamentalnych różnicach.
1. Popraw właściwość stanu zasobu
PATCH /api/virtualmachines/42
Content-Type:application/json
{ "state": "shutting down" }
(Alternatywnie, PUT
w podrzędnym zasobie /api/virtualmachines/42/state
).
Maszyna wirtualna zostanie wyłączona w tle, a w późniejszym czasie, zależnie od tego, czy wcześniejsze zamknięcie zakończy się powodzeniem, czy nie, stan może zostać wewnętrznie zaktualizowany po wyłączeniu zasilania.
2. PUT lub POST we właściwości akcji zasobu
PUT /api/virtualmachines/42/actions
Content-Type:application/json
{ "type": "shutdown" }
Wynik jest dokładnie taki sam jak w pierwszym przykładzie. Stan zostanie natychmiast zaktualizowany do „wyłączenia”, a być może do „wyłączenia”.
Czy oba projekty są RESTful?
Który projekt jest lepszy?
Przykład II: CQRS
Co jeśli mamy domenę CQRS z wieloma takimi „działaniami” (inaczej komendami), które mogą potencjalnie prowadzić do aktualizacji wielu agregatów lub nie mogą być odwzorowane na operacje CRUD na konkretnych zasobach i pod-zasobach?
Czy powinniśmy próbować modelować tyle komend, ile konkretnych kreacji lub aktualizacji konkretnych zasobów, o ile to możliwe (zgodnie z pierwszym podejściem z przykładu I) i używać „punktów końcowych akcji” dla reszty?
Czy też powinniśmy odwzorować wszystkie polecenia na punkty końcowe akcji (jak w drugim podejściu z przykładu I)?
Gdzie powinniśmy narysować linię? Kiedy projekt staje się mniej ODPOWIEDNI?
Czy model CQRS lepiej pasuje do interfejsu API typu RPC?
Zgodnie z cytowanym tekstem powyżej jest to, jak rozumiem.
Jak widać z wielu moich pytań, jestem trochę zdezorientowany w tym temacie. Czy możesz mi pomóc lepiej to zrozumieć?
źródło
Odpowiedzi:
W pierwszym przypadku (zamknięcie maszyn wirtualnych) nie rozważałbym żadnej z alternatywnych metod operacyjnych jako RESTful. Oczywiście, jeśli użyjesz modelu dojrzałości Richardsona jako miernika, oba są interfejsami API co najmniej 2, ponieważ używają zasobów i czasowników.
Żadne z nich jednak nie używa kontrolek hipermedialnych i moim zdaniem jest to jedyny typ REST, który odróżnia projekt interfejsu API RESTful od RPC. Innymi słowy, trzymaj się poziomu 1 i 2, a w większości przypadków będziesz mieć interfejs API w stylu RPC.
Aby modelować dwa różne sposoby wyłączania maszyny wirtualnej, odsłoniłbym samą maszynę wirtualną jako zasób, który (między innymi) zawiera łącza:
Jeśli klient chce zamknąć
Ploeh
maszynę wirtualną, powinien skorzystać z łącza zshut-down
typem relacji. (Zwykle, jak opisano w książce RESTful Web Services Cookbook , możesz użyć IRI lub bardziej rozbudowanego schematu identyfikacji dla typów relacji, ale postanowiłem, aby przykład był tak prosty, jak to możliwe).W takim przypadku akcji jest niewiele, dlatego klient powinien po prostu wykonać pusty test POST względem adresu URL w
href
:(Ponieważ to żądanie nie ma treści, pokusa byłoby modelować to jako żądanie GET, ale żądania GET nie powinny mieć żadnych obserwowalnych skutków ubocznych, więc test POST jest bardziej poprawny.)
Podobnie, jeśli klient chce wyłączyć maszynę wirtualną,
power-off
zamiast tego podąży za linkiem.Innymi słowy, typy relacji łączy zapewniają afordancje wskazujące na zamiar. Każdy typ relacji ma określone znaczenie semantyczne. Dlatego czasami rozmawiamy o sieci semantycznej .
Aby zachować jak najjaśniejszy przykład, celowo ukryłem adresy URL w każdym linku. Gdy serwer hosting odbiera żądanie przychodzące, że ona wie, że
fdaIX
środki zamknięty , aCHTY91
środki wyłączanie zasilania .Zwykle po prostu kodowałbym akcję w samym adresie URL, aby adresy URL były
/vms/1234/shut-down
i/vms/1234/power-off
, ale podczas uczenia, zaciera to rozróżnienie między typami relacji (semantyka) i adresami URL (szczegóły implementacji).W zależności od posiadanych klientów możesz rozważyć, aby nie można hakować adresów URL RESTful .
CQRS
Jeśli chodzi o CQRS, jedną z niewielu rzeczy, o których zgadzają się Greg Young i Udi Dahan, jest to, że CQRS nie jest architekturą najwyższego poziomu . Dlatego byłbym ostrożny w tworzeniu interfejsu API RESTful zbyt podobnego do CQRS, ponieważ oznaczałoby to, że klienci staną się częścią Twojej architektury.
Często siłą napędową prawdziwego interfejsu API RESTful (poziom 3) jest chęć rozwijania interfejsu API bez rozbijania klientów i bez kontroli nad nimi. Jeśli to twoja motywacja, CQRS nie byłby moim pierwszym wyborem.
źródło
DELETE
wydaje mi się dziwny, ponieważ po wyłączeniu vm nadal będzie istniał, tylko w stanie „power off” (lub coś takiego).Zamykanie maszyny wirtualnej za pośrednictwem interfejsu REST
To właściwie dość znany przykład, przedstawiony przez Tima Braya w 2009 roku .
Roy Fielding, omawiając problem, podzielił się spostrzeżeniem :
Krótko mówiąc, masz jeden zasób informacyjny, który zwraca bieżącą reprezentację monitorowanego stanu; reprezentacja ta może zawierać hipermedialny link do formularza , który żąda zmiany tego stanu, a formularz ma inne łącze do zasobu do obsługi (każdego) żądania zmiany.
Seth Ladd miał kluczowe informacje na temat problemu
RESTful programowanie to biurokracja Vogon na skalę internetową. Jak robisz cokolwiek RESTful? Wymyśl dla niego nowe formalności i zdigitalizuj formalności.
W nieco bardziej wyrafinowanym języku to, co robisz, to definiowanie protokołu aplikacji domeny dla „zamykania maszyny wirtualnej” i identyfikowanie zasobów, których potrzebujesz do ujawnienia / wdrożenia tego protokołu
Patrząc na własne przykłady
W porządku; tak naprawdę nie traktujesz samego żądania jako osobnego zasobu informacyjnego, ale nadal możesz nim zarządzać.
Trochę przeoczyłeś swoją reprezentację zmiany.
Na przykład typ nośnika poprawki JSON formatuje instrukcje tak, jakbyś bezpośrednio modyfikował dokument JSON
Alternatywnie pomysł jest bliski, ale oczywiście nie poprawny.
PUT
to całkowite zastąpienie stanu zasobu pod docelowym adresem URL , więc prawdopodobnie nie wybrałbyś pisowni, która wygląda jak kolekcja jako cel reprezentacji pojedynczej jednostki.Jest zgodny z fikcją, że dołączamy akcję do kolejki
Jest zgodny z fikcją, że aktualizujemy element ogona w kolejce; to trochę dziwne robić to w ten sposób. Zasada najmniejszego zaskoczenia zaleca nadanie każdemu PUT jego unikalnego identyfikatora, zamiast umieszczania ich wszystkich w jednym miejscu i modyfikowania wielu zasobów jednocześnie.
Zauważ, że w zakresie, w jakim omawiamy pisownię URI - REST nie ma znaczenia;
/cc719e3a-c772-48ee-b0e6-09b4e7abbf8b
jest idealnie cromulent URI, jeśli chodzi o REST. Czytelność, podobnie jak w przypadku nazw zmiennych, stanowi odrębny problem. Używanie pisowni zgodnej z RFC 3986 sprawi, że ludzie będą znacznie szczęśliwsi.CQRS
Greg Young w CQRS
Biorąc pod uwagę, że mówimy o CQRS w kontekście HTTP / REST, rozsądne wydaje się założenie, że pracujesz w tym ostatnim kontekście, więc przejdźmy do tego.
Ten, co zaskakujące, jest jeszcze łatwiejszy niż twój poprzedni przykład. Powód tego jest prosty: polecenia to komunikaty .
Jim Webber opisuje HTTP jako protokół aplikacyjny biura z lat 50. XX wieku; praca jest wykonywana przez pobieranie wiadomości i umieszczanie ich w skrzynkach odbiorczych. Taki sam pomysł - otrzymujemy pustą kopię formularza, wypełnij go znanymi nam szczegółami, dostarcz go. Ta da
Tak, o ile „konkretne zasoby” to komunikaty, a nie jednostki w modelu domeny.
Kluczowy pomysł: twój interfejs API REST wciąż jest interfejsem ; powinieneś być w stanie zmienić model bazowy bez potrzeby zmiany komunikatów przez klientów. Wydając nowy model, uwalniasz nową wersję internetowych punktów końcowych, które wiedzą, jak pobrać protokół domeny i zastosować go do nowego modelu.
Niezupełnie - w szczególności pamięci podręczne są doskonałym przykładem „ostatecznie spójnego modelu odczytu”. Umożliwiając niezależne adresowanie każdego z twoich widoków, każdy z własnymi regułami buforowania, daje ci możliwość skalowania za darmo. Stosunkowo mało pociąga podejście wyłącznie do RPC do czytania.
W przypadku zapisów jest to trudniejsze pytanie: wysłanie wszystkich poleceń do jednego modułu obsługi w jednym punkcie końcowym lub jednej rodzinie punktów końcowych jest z pewnością łatwiejsze . REST jest tak naprawdę bardziej o tym, jak znaleźć komunikację, gdzie punkt końcowy jest do klienta.
Traktowanie wiadomości jako własnego unikalnego zasobu ma tę zaletę, że można użyć PUT, ostrzegając komponenty pośredniczące o tym, że obsługa wiadomości jest idempotentna, aby mogli uczestniczyć w niektórych przypadkach obsługi błędów, miło jest mieć . (Uwaga: z punktu widzenia klientów, jeśli zasoby mają inny identyfikator URI, to są to różne zasoby; fakt, że wszystkie mogą mieć ten sam kod obsługi żądań na serwerze źródłowym, jest szczegółem implementacji ukrytym przez mundur berło).
Fielding (2008)
źródło
Można użyć 5 poziomów typu nośnika, aby określić polecenie w polu nagłówka typu treści żądania.
W przykładzie maszyny wirtualnej byłoby to coś takiego
Następnie
Lub
Zobacz https://www.infoq.com/articles/rest-api-on-cqrs
źródło
Przykład w połączonym artykule opiera się na pomyśle, że uruchomienie komputera i wyłączenie go musi być kierowane przez polecenia zamiast przez zmiany stanu modelowanych zasobów. To ostatnie jest właściwie tym, czym REST żyje i oddycha. Lepsze modelowanie maszyny Wirtualnej wymaga spojrzenia na to, jak działa jej odpowiednik w świecie rzeczywistym i jak Ty, jako człowiek, mógłbyś z nią współdziałać. Jest to długa droga, ale myślę, że daje dobry wgląd w rodzaj myślenia wymagany do dobrego modelowania.
Istnieją dwa sposoby kontrolowania stanu zasilania komputera na moim biurku:
W przypadku maszyny wirtualnej oba mogą być modelowane jako wartości logiczne do odczytu / zapisu:
power
- Po zmianie natrue
nic się nie dzieje poza odnotowaniem, że przełącznik został ustawiony w tym stanie. Po zmianiefalse
na maszyna wirtualna otrzymuje polecenie natychmiastowego wyłączenia. Dla kompletności, jeśli wartość nie zmieni się po zapisie, nic się nie stanie.onoff
- Po zmianie natrue
nic się nie dzieje, jeślipower
jestfalse
, w przeciwnym razie polecenie uruchomienia maszyny wirtualnej. Po zmianie nafalse
nic się nie dzieje, jeśli tak niepower
jestfalse
, w przeciwnym razie maszyna wirtualna otrzyma polecenie powiadomienia oprogramowania o zaplanowanym wyłączeniu, co zrobi, a następnie powiadomi maszynę wirtualną, że może przejść w stan wyłączenia. Ponownie, dla kompletności, zapis bez zmian nic nie robi.Z tym wszystkim wynika, że istnieje jedna sytuacja, w której stan maszyny nie odzwierciedla stanu przełączników, i to podczas wyłączania.
power
nadal będzietrue
ionoff
będziefalse
, ale procesor nadal się wyłącza i do tego musimy dodać kolejny zasób, abyśmy mogli powiedzieć, co faktycznie robi maszyna:running
- Wartość tylko do odczytu, która występuje,true
gdy maszyna wirtualna jest uruchomiona, afalse
kiedy nie, określana przez zapytanie hiperwizora o jej stan.Wynikiem tego jest to, że jeśli chcesz, aby maszyna wirtualna została uruchomiona, musisz upewnić się, że
power
ionoff
zasoby zostały ustawionetrue
. (Możesz zezwolić napower
pominięcie tego kroku poprzez samodzielne zresetowanie go, tak aby po ustawieniufalse
stał siętrue
po tym, jak maszyna wirtualna została mocno zatrzymana. Czy to RESTYSTYCZNIE czysta rzecz do zrobienia to pasza na kolejną dyskusję.) Jeśli chcesz to zrobić procedurę zamykania, można ustawićonoff
, abyfalse
i wrócić później, aby sprawdzić, czy urządzenie rzeczywiście zatrzymał się, ustawiającpower
sięfalse
, czy nie.Podobnie jak w prawdziwym świecie, nadal masz problem z uruchomieniem maszyny wirtualnej po
onoff
zmianie,false
ale nadal występuje,running
ponieważ jest w trakcie zamykania. Sposób, w jaki sobie z tym radzisz, jest decyzją polityczną.źródło
Jeśli więc chcesz myśleć spokojnie, zapomnij o poleceniach. Klient nie mówi serwerowi, aby zamknął maszynę wirtualną. Klient „zamyka dow” (mówiąc metaforycznie) swoją kopię reprezentacji zasobu, aktualizując jej stan, a następnie umieszcza tę reprezentację z powrotem na serwerze. Serwer akceptuje tę nową reprezentację stanu i jako efekt uboczny faktycznie wyłącza maszynę wirtualną. Aspekt skutków ubocznych należy do serwera.
To mniej
Hej, serwerze, tutaj klient, czy mógłbyś wyłączyć maszynę wirtualną?
i więcej
Hej, serwer, klient tutaj, zaktualizowałem stan zasobu VM 42 do stanu zamknięcia, zaktualizowałem kopię tego zasobu, a następnie zrobiłem to, co uważasz za stosowne
Jako efekt uboczny serwera akceptującego ten nowy stan może on sprawdzić, jakie działania musi faktycznie wykonać (np. Fizycznie zamknąć VM 42), ale jest to przezroczyste dla klienta. Klient nie jest zainteresowany żadnymi działaniami, które serwer musi podjąć, aby zachować zgodność z tym nowym stanem
Więc zapomnij o poleceniach. Jedynymi poleceniami są czasowniki w HTTP dla transferu stanu. Klient nie wie ani nie dba o to, jak serwer doprowadzi fizyczną maszynę wirtualną do stanu zamknięcia. Klient nie wydaje serwerowi poleceń, aby to osiągnąć, po prostu mówi, że to jest nowy stan, zrozum to .
Siła tego polega na tym, że oddziela klienta od serwera pod względem kontroli przepływu. Jeśli później serwer zmieni sposób działania z maszynami wirtualnymi, klient nie będzie się tym przejmował. Brak punktów końcowych polecenia do zaktualizowania. W RPC, jeśli zmienisz punkt końcowy interfejsu API z
shutdown
nashut-down
, zepsułeś wszystkich swoich klientów, ponieważ nie znają teraz polecenia do wywołania na serwerze.REST jest podobny do programowania deklaratywnego, w którym zamiast wypisywania zestawu instrukcji, aby coś zmienić, wystarczy podać, jak chcesz, i pozwolić środowisku programistycznemu to określić.
źródło
POST /api/virtualmachines/42/shutdown
zamiast mieć „efekt uboczny”. Interfejs API musi być zrozumiały dla użytkownika. Skąd mam wiedzieć, że na przykładDELETE /api/virtualmachines/42
zamknie maszynę wirtualną? Efektem ubocznym jest dla mnie błąd. Powinniśmy zaprojektować nasze interfejsy API tak, aby były zrozumiałe i opisowe