RESTFul: akcje zmieniające stan

59

Planuję zbudować RESTfull API, ale są pewne pytania architektoniczne, które powodują pewne problemy w mojej głowie. Dodanie logiki biznesowej zaplecza do klientów to opcja, której chciałbym uniknąć, ponieważ aktualizowanie wielu platform klienckich jest trudne do utrzymania w czasie rzeczywistym, gdy logika biznesowa może się szybko zmienić.

Powiedzmy, że mamy artykuł jako zasób (api / artykuł), w jaki sposób powinniśmy wdrożyć działania takie jak publikowanie, cofanie publikacji, aktywowanie lub dezaktywowanie itd., Ale starając się, aby było to tak proste, jak to możliwe?

1) Czy powinniśmy użyć api / article / {id} / {action}, ponieważ tam może się zdarzyć wiele logiki zaplecza, np. Wypychanie do zdalnych lokalizacji lub zmiana wielu właściwości. Prawdopodobnie najtrudniejszą rzeczą jest to, że musimy odesłać wszystkie dane artykułów z powrotem do API w celu aktualizacji i nie można zaimplementować pracy wielu użytkowników. Na przykład redaktor mógłby wysłać dane o 5 sekund starsze i zastąpić poprawkę, którą jakiś inny dziennikarz właśnie zrobił 2 sekundy temu, i nie ma sposobu, bym mógł to wyjaśnić klientom, ponieważ publikujący artykuł tak naprawdę nie jest w żaden sposób związany z aktualizacją treści.

2) Utworzenie nowego zasobu może być również opcją api / article- {action} / id, ale zwrócony zasób nie byłby artykułem- {action}, ale artykułem, którego nie jestem pewien, czy jest to właściwe. Również w kodzie artykułów po stronie serwera klasa obsługuje wszystkie prace na obu zasobach i nie jestem pewien, czy jest to sprzeczne z myśleniem RESTfull

Wszelkie sugestie są mile widziane ..

Miro Svrtan
źródło
Zgodne z prawem jest, aby „akcje” były częścią identyfikatora URI usługi RESTful - jeśli określają akcję / algorytm do wykonania. Co jest nie tak z api/article?action=publish? Parametry zapytania są przeznaczone do takich przypadków, w których stan zasobu zależy od wspomnianego „algorytmu” (lub akcji). Np. api/articles?sort=ascJest ważny
doktorat
1
Sugeruję przejrzenie tego artykułu, który może zainspirować Cię jeszcze bardziej RESTfulowym rozwiązaniem.
Benjol
Jednym z problemów, które widzę w przypadku interfejsu API / artykułu? Akcja = publikacja, jest to, że w aplikacji RESTfull powinien wysyłać WSZYSTKIE dane artykułu do publikacji, a ja wolę to zrobić: api / artykuł / 4545 / publikacja / bez żadnych dodatkowych informacji
Miro Svrtan
2
Doskonałe zasoby na ten temat i nie tylko: Projekt interfejsu API REST - Modelowanie zasobów
Izhaki
@PhD: chociaż w protokole jest to całkowicie legalne, URI powinny zasadniczo być rzeczownikami, a nie czasownikami. Posiadanie czasownika w URI jest zwykle oznaką złego projektu REST.
Lie Ryan,

Odpowiedzi:

49

Uważam, że opisane tutaj praktyki są pomocne:

Co z działaniami, które nie pasują do świata operacji CRUD?

To tutaj rzeczy mogą się rozmazać. Istnieje wiele podejść:

  1. Zmień strukturę akcji, aby wyglądała jak pole zasobu. Działa to, jeśli akcja nie przyjmuje parametrów. Na przykład akcja aktywacji może być odwzorowana na activatedpole logiczne i zaktualizowana za pomocą PATCHA do zasobu.
  2. Traktuj to jak pod-zasób z zasadami RESTful. Na przykład, GitHub API pozwala gwiazdką sens z PUT /gists/:id/stari usuń zaznaczenie z DELETE /gists/:id/star.
  3. Czasami naprawdę nie masz sposobu na zmapowanie akcji do rozsądnej struktury RESTful. Na przykład wyszukiwanie wielu zasobów nie ma sensu, aby stosować je do punktu końcowego określonego zasobu. W takim przypadku /searchmiałoby to sens, nawet jeśli nie jest to zasób. To jest OK - po prostu rób to, co jest właściwe z punktu widzenia konsumenta interfejsu API i upewnij się, że jest to dobrze udokumentowane, aby uniknąć zamieszania.
Tim
źródło
Głosuję za podejściem 2. Chociaż może to wyglądać na niezgrabne sztuczne zasoby dla osób wywołujących API, w rzeczywistości nie mają wiedzy o tym, co dzieje się na serwerze. Jeśli zadzwonię /article/123/deactivationsdo POST, aby utworzyć nowe żądanie dezaktywacji dla artykułu 123, serwer może nie tylko dezaktywować żądany zasób, ale faktycznie zapisać moje żądanie dezaktywacji, aby móc później odzyskać jego status.
JustAMartin
2
dlaczego PUT /gists/:id/star nie POST /gists/:id/star?
Filip Bartuzi
9
@FilipBartuzi Ponieważ PUT jest idempotentny - to znaczy, bez względu na to, ile razy wykonujesz akcję z tymi samymi parametrami, wynik jest zawsze taki sam (np. Jeśli wyłączysz światło z wyłączonego na włączone, to się zmieni. Jeśli spróbujesz włączyć to ponownie, nic się nie zmienia - jest już włączone). POST nie jest idempotentny - to znaczy za każdym razem, gdy wykonujesz akcję, nawet przy tych samych parametrach, akcja ma inny wynik (np. Jeśli wysyłasz komuś list, ta osoba dostaje list. Jeśli wysyłasz identyczny list do ta sama osoba, teraz mają 2 litery).
Raphael
9

Operacje powodujące poważne zmiany stanu i zachowania po stronie serwera, takie jak opisana akcja „publikuj”, są trudne do jednoznacznego modelowania w REST. Rozwiązaniem, które często widzę, jest kierowanie tak złożonym zachowaniem w sposób niejawny poprzez dane.

Rozważ zamówienie towarów za pośrednictwem interfejsu API REST udostępnionego przez sprzedawcę internetowego. Zamawianie jest złożoną operacją. Kilka produktów zostanie zapakowanych i wysłanych, Twoje konto zostanie obciążone, a otrzymasz paragon. Możesz anulować swoje zamówienie na ograniczony czas, a oczywiście istnieje pełna gwarancja zwrotu pieniędzy, która pozwala odesłać produkty w celu uzyskania zwrotu pieniędzy.

Zamiast złożonej operacji zakupu taki interfejs API może umożliwić utworzenie nowego zasobu, zamówienia zakupu. Na początku możesz wprowadzić dowolne modyfikacje: dodać lub usunąć produkty, zmienić adres wysyłki, wybrać inną opcję płatności lub całkowicie anulować zamówienie. Możesz zrobić to wszystko, ponieważ jeszcze niczego nie kupiłeś, po prostu manipulujesz niektórymi danymi na serwerze.

Po złożeniu zamówienia i upływie okresu karencji serwer blokuje zamówienie, aby zapobiec dalszym zmianom. Dopiero w tym momencie rozpoczyna się złożona sekwencja operacji, ale nie można jej kontrolować bezpośrednio, tylko pośrednio, poprzez dane, które wcześniej umieściłeś w zamówieniu.

Na podstawie Twojego opisu można w ten sposób zaimplementować „publikowanie”. Zamiast ujawniać operację, umieszczasz kopię recenzowanej wersji roboczej i chcesz opublikować jako nowy zasób w obszarze / opublikuj. Gwarantuje to, że wszelkie kolejne aktualizacje wersji roboczej nie zostaną opublikowane, nawet jeśli sama operacja publikowania zakończy się kilka godzin później.

Ferenc Mihaly
źródło
Pomysł zmiany całego zasobu z niepublikowanego artykułu na wersję roboczą pasowałby dokładnie do tego przypadku, ale nie pasowałby do wszystkich innych działań, które istnieją na zasobie w ogóle. Czy REST ma w ogóle sobie z tym poradzić? Może nadużywam go i powinienem go używać tylko jako CRUD i nic więcej, jak zapytania SQL, w których nie oczekuję żadnej logiki wewnątrz samego zapytania.
Miro Svrtan
Dlaczego pytam o to wszystko? Cóż, odkąd jakiś czas temu aplikacje internetowe zaczęły być wieloplatformowe i wolałbym zachować dużo logiki biznesowej na serwerze, ponieważ aktualizacja logiki biznesowej na iOS, Androida, stronie internetowej, komputerze lub innej platformie staje się niemożliwa do zrobienia szybko i chciałbym uniknąć wszystkich problemów z kompatybilnością wsteczną przy zmianie jakiegoś małego fragmentu BL.
Miro Svrtan
2
Myślę, że REST dobrze sobie radzi z logiką biznesową, ale nie jest to odpowiednie do ujawnienia istniejącej logiki biznesowej napisanej bez uwzględnienia REST. Właśnie dlatego wiele firm, takich jak Microsoft, SAP i inne, często ujawnia tylko dane podczas operacji CRUD, tak jak powiedziałeś. Zobacz, jak działa protokół Open Data Protocol.
Ferenc Mihaly
7

musimy przesłać wszystkie dane artykułów z powrotem do interfejsu API w celu aktualizacji i nie można zaimplementować pracy wielu użytkowników. Na przykład redaktor mógłby wysłać dane o 5 sekund starsze i zastąpić poprawkę, którą jakiś inny dziennikarz właśnie zrobił 2 sekundy temu, i nie ma sposobu, bym mógł to wyjaśnić klientom, ponieważ publikujący artykuł nie jest w żaden sposób związany z aktualizacją treści.

Takie rzeczy są wyzwaniem bez względu na to, co robisz. Jest to bardzo podobny problem do rozproszonej kontroli źródła (mercurial, git itp.), A rozwiązanie, zapisane w HTTP / ReST, wygląda trochę podobnie.

Załóżmy, że masz dwóch użytkowników, Alice i Boba, którzy pracują nad tym /articles/lunch. (dla jasności odpowiedź jest odważna)

Najpierw Alice tworzy artykuł.

PUT /articles/lunch HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

301 Moved Permanently
Location: /articles/lunch/1 

Serwer nie utworzył zasobu, ponieważ do żądania nie została dołączona „wersja” (zakładając identyfikator /articles/{id}/{version}. Aby wykonać tworzenie, Alice została przekierowana na adres URL artykułu / wersji, którą będzie tworzyć. Użytkownik Alice agent ponownie zastosuje żądanie pod nowym adresem.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

201 Created

A teraz artykuł został utworzony. następnie Bob patrzy na artykuł:

GET /articles/lunch HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

301 Moved Permanently
Location: /articles/lunch/1 

Bob tam patrzy:

GET /articles/lunch/1 HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

200 Ok
Content-Type: text/plain

Hey Bob, what do you want for lunch today?

Postanawia dodać własną zmianę.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

301 Moved Permanently
Location: /articles/lunch/2

Podobnie jak w przypadku Alicji, Bob zostaje przekierowany do miejsca, w którym będzie tworzył nową wersję.

PUT /articles/lunch/2 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

201 Created

Wreszcie Alice decyduje, że chciałaby dodać do swojego artykułu:

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?
I was thinking about getting Sushi.

409 Conflict
Location: /articles/lunch/3
Content-Type: text/diff

---/articles/lunch/2
+++/articles/lunch/3
@@ 1,2 1,2 @@
 Hey Bob, what do you want for lunch today?
-Does pizza sound good to you, Alice?
+I was thinking about getting Sushi.

Zamiast zostać przekierowanym jak zwykle, klient otrzymuje inny kod stanu 409, co informuje Alice, że wersja, z której próbowała się rozgałęzić, została już rozgałęziona. Nowe zasoby i tak zostały utworzone (jak pokazano w Locationnagłówku), a różnice między nimi uwzględniono w treści odpowiedzi. Alice wie teraz, że jej prośba musi zostać w jakiś sposób połączona.


Całe to przekierowanie jest powiązane z semantyką PUT, która wymaga, aby nowe zasoby były tworzone dokładnie tam, gdzie prosi o to linia żądania. może to również zapisać cykl żądań za pomocą POSTzamiast tego, ale wtedy numer wersji musiałby zostać zakodowany w żądaniu przez inną magię, co wydawało mi się mniej oczywiste dla celów ilustracji, ale prawdopodobnie nadal byłoby preferowane w prawdziwym API aby zminimalizować cykle zapytań / odpowiedzi.

SingleNegationElimination
źródło
1
Versoning nie był tutaj problemem, powiedziałem to tylko jako przykład możliwych problemów, jeśli wykorzystuję artykuł jako źródło publikacji
Miro Svrtan
3

Oto kolejny przykład, który dotyczy nie treści dokumentów, ale bardziej stanu przejściowego. (Uważam, że przechowywanie wersji - biorąc pod uwagę, że ogólnie każda wersja może być nowym zasobem - rodzaj łatwego problemu).

Powiedzmy, że chcę udostępnić usługę działającą na komputerze za pośrednictwem usługi REST, aby można ją było zatrzymać, uruchomić, zrestartować i tak dalej.

Jakie jest najbardziej RESTful podejście tutaj? Polecenie POST / service? = Na przykład restart? Lub POST / usługa / stan z treścią, powiedzmy, „uruchomioną”?

Byłoby miło kodyfikować tutaj najlepsze praktyki i czy REST jest właściwym podejściem do tego typu sytuacji.

Po drugie, załóżmy, że chcę przeprowadzić akcję z usługi, która nie wpływa na jej stan, ale raczej wywołuje efekt uboczny. Na przykład usługa pocztowa, która wysyła raport, zbudowany w czasie połączenia, na kilka adresów e-mail.

GET / report może być sposobem na samodzielne uzyskanie kopii raportu; ale co, jeśli chcemy przesunąć na stronę serwera dalsze działania, takie jak e-mail, jak mówię powyżej. Lub pisanie do bazy danych.

Te przypadki tańczą wokół podziału na zasoby i widzę sposoby radzenia sobie z nimi w sposób zorientowany na REST, ale szczerze mówiąc, wydaje się to trochę hackowaniem. Być może kluczowym pytaniem jest, czy interfejs API REST powinien ogólnie wspierać działania niepożądane.

AMD
źródło
2

REST jest zorientowany na dane i jako takie zasoby działają najlepiej jako „rzeczy”, a nie akcje. Ukryta semantyka metod http; GET, PUT, DELETE itp. Służą do wzmocnienia orientacji. POST jest oczywiście wyjątkiem.

Zasób może być mieszanką danych, tj. treść artykułu; i metadane tj. opublikowane, zablokowane, wersja. Istnieje wiele innych możliwych sposobów podzielenia danych, ale musisz najpierw sprawdzić, jak będzie wyglądał przepływ danych, aby określić najbardziej optymalny (jeśli taki istnieje). Na przykład może być tak, że poprawki powinny być ich własnym zasobem w ramach artykułu, jak sugeruje TokenMacGuy.

Jeśli chodzi o implementację, prawdopodobnie zrobiłbym coś takiego, co sugeruje TockenMacGuy. Dodałbym również pola metadanych w artykule, a nie w wersji, takie jak „zablokowane” i „opublikowane”.

dietbuddha
źródło
1

Nie myśl o tym jako o bezpośredniej manipulacji stanem artykułu. Zamiast tego składasz zlecenie zmiany z prośbą o utworzenie artykułu.

Można modelować umieszczanie w kolejności zmian jako tworzenie nowego zasobu zlecenia zmiany (POST). Istnieje wiele zalet. Na przykład możesz określić przyszłą datę i godzinę, kiedy artykuł powinien zostać opublikowany w ramach zlecenia zmiany, i pozwolić serwerowi martwić się o to, jak to zostanie zaimplementowane.

Jeśli publikowanie nie jest procesem natychmiastowym, nie musisz czekać, aż zakończy się, zanim wrócisz do klienta. Wystarczy potwierdzić, że zlecenie zmiany zostało utworzone i zwrócić identyfikator zlecenia zmiany. Następnie możesz użyć adresu URL odpowiadającego temu zleceniu zmiany, aby udostępnić status zlecenia zmiany.

Kluczowym wglądem było dla mnie rozpoznanie tej metafory kolejności zmian, która jest po prostu innym sposobem opisania programowania obiektowego. Zamiast zasobów nazywamy obiekty. Zamiast zleceń zmiany nazywamy je wiadomościami. Jednym ze sposobów wysłania wiadomości z A do B w OO jest wywołanie metody A na B. Innym sposobem na zrobienie tego, szczególnie gdy A i B są na różnych komputerach, jest utworzenie przez A nowego obiektu, M i wyślij go do B. REST po prostu formalizuje ten proces.

Patrick McElhaney
źródło
Rozważałbym to bliżej modelu aktora niż OO. Rozpatrywanie żądań REST jako Wiadomości (którymi są) wyrównuje je bardzo starannie do Sourcingu Zdarzeń w celu zarządzania stanem. REST starannie dzieli swoje interakcje wzdłuż linii CQRS. Komunikatami GET są zapytania, POST, PUT, PATCH, DELETE mieszczące się w obszarze Command.
WillD
0

Jeśli dobrze cię rozumiem, uważam, że to, co masz, jest bardziej kwestią ustalenia „reguł biznesowych” niż kwestią techniczną.

Fakt, że artykuł może zostać zastąpiony, można rozwiązać, wprowadzając poziomy autoryzacji, w których starsi użytkownicy mogą zastępować wersje młodszych użytkowników, a także wprowadzając wersje, a także kolumnę do przechwytywania stanu artykułu (np. „W fazie rozwoju”, „końcowy” itp.), możesz to przezwyciężyć. Możesz także dać użytkownikowi możliwość wybrania danej wersji albo przez kombinację czasu przesłania i numeru wersji.

We wszystkich powyższych przypadkach usługa musi implementować ustawione reguły biznesowe. Możesz więc wywołać usługę z parametrami: identyfikator użytkownika, artykuł, wersja, działanie (w przypadku gdy wersja jest opcjonalna, znowu zależy to od reguł biznesowych).

Bez szans
źródło
Nie wierzę, że jest to zasada biznesowa, ale ściśle techniczna. Pomysł dodania wersji jest dobry, aby pomóc w zastępowaniu reguł, ale nadal nie rozwiązuje problemu, że aktualizowanie treści i publikowanie treści nie są powiązanymi działaniami.
Miro Svrtan