Transakcje w REST?

147

Zastanawiam się, jak zaimplementowałbyś następujący przypadek użycia w REST. Czy w ogóle można się obejść bez kompromisu w modelu konceptualnym?

Odczytuj lub aktualizuj wiele zasobów w ramach jednej transakcji. Na przykład przelej 100 USD z konta bankowego Roberta na konto Jana.

O ile wiem, jedynym sposobem na to jest oszukiwanie. Możesz POST do zasobu powiązanego z Janem lub Bobem i przeprowadzić całą operację przy użyciu pojedynczej transakcji. Jeśli o mnie chodzi, zrywa to architekturę REST, ponieważ zasadniczo tunelujesz wywołanie RPC przez POST, zamiast naprawdę działać na poszczególnych zasobach.

Gili
źródło

Odpowiedzi:

91

Rozważ scenariusz RESTful koszyka zakupów. Koszyk zakupów jest koncepcyjnie opakowaniem transakcji. W ten sam sposób, w jaki możesz dodać wiele pozycji do koszyka, a następnie przesłać ten koszyk w celu przetworzenia zamówienia, możesz dodać wpis konta Roberta do opakowania transakcji, a następnie wpis konta Billa do opakowania. Kiedy wszystkie elementy są na miejscu, możesz POST / WPISAĆ opakowanie transakcji ze wszystkimi elementami składowymi.

Darrel Miller
źródło
18
Dlaczego TransferMoneyTransaction nie miałby być opłacalnym zasobem bankowym?
Darrel Miller,
8
Jeśli upewnisz się, że punkty końcowe odwołują się do rzeczowników, zazwyczaj intuicyjne jest to, co standardowe czasowniki GET, PUT, POST, DELETE zrobią z tym rzeczownikiem. RPC pozwala punktom końcowym być samymi czasownikami i dlatego mogą kolidować z czasownikami HTTP, a intencja staje się myląca.
Darrel Miller,
10
np. co się stanie, jeśli wykonasz HTTP DELETE na punkcie końcowym UpdateXYZ? Czy usuwa XYZ? Czy usuwa aktualizację, czy po prostu wykonuje aktualizację i ignoruje czasownik HTTP delete. Utrzymując czasowniki poza punktem końcowym, usuwasz zamieszanie.
Darrel Miller,
5
A co z transakcjami obejmującymi wiele usług? a co w przypadku, gdy chcesz wprowadzić zestaw „niepowiązanych” zmian, aby usługa nie ujawniała żadnego niejawnego kontenera transakcji .. plus, po co mieć określony typ transakcji, kiedy przechodzimy do transakcji ogólnego przeznaczenia, które są całkowicie niezwiązane z twoimi rzeczywistymi danymi zmiany. Transakcje mogą nie pasować spokojnie, ale wydaje się, że transakcje powinny być warstwowe na wierzchu, niezwiązane z pozostałymi wywołaniami, z wyjątkiem faktu, że nagłówki żądań zawierałyby odniesienie do transakcji.
meandmycode
4
@meandmycode Transakcje bazy danych powinny być ułożone za interfejsem REST. Alternatywnie możesz ujawnić transakcję biznesową (nie transakcję bazy danych) jako sam zasób, a następnie musisz podjąć działania kompensujące w przypadku niepowodzenia.
Darrel Miller
60

Jest kilka ważnych przypadków, na które nie ma odpowiedzi na to pytanie, co moim zdaniem jest złe, ponieważ ma wysoką pozycję w Google pod względem wyszukiwanych haseł :-)

W szczególności fajne byłoby, gdyby: Jeśli POST dwa razy (ponieważ część pamięci podręcznej czknęła w pośrednim), nie powinieneś przenosić kwoty dwukrotnie.

Aby się do tego dostać, należy utworzyć transakcję jako obiekt. Może zawierać wszystkie znane już dane i ustawić transakcję w stanie oczekującym.

POST /transfer/txn
{"source":"john's account", "destination":"bob's account", "amount":10}

{"id":"/transfer/txn/12345", "state":"pending", "source":...}

Gdy już masz tę transakcję, możesz ją zatwierdzić, na przykład:

PUT /transfer/txn/12345
{"id":"/transfer/txn/12345", "state":"committed", ...}

{"id":"/transfer/txn/12345", "state":"committed", ...}

Zauważ, że wielokrotne puttowanie nie ma w tym momencie znaczenia; nawet GET na txn zwróciłby bieżący stan. W szczególności druga PUT wykryłaby, że pierwsza była już w odpowiednim stanie i po prostu zwróciłaby ją - lub, jeśli spróbujesz wprowadzić ją w stan „wycofania” po tym, jak jest już w stanie „zatwierdzone”, otrzymasz błąd, a rzeczywista zatwierdzona transakcja z powrotem.

Tak długo, jak rozmawiasz z pojedynczą bazą danych lub bazą danych ze zintegrowanym monitorem transakcji, mechanizm ten będzie działał dobrze. Możesz dodatkowo wprowadzić limity czasu dla transakcji, które możesz nawet wyrazić za pomocą nagłówków Expires, jeśli chcesz.

Jon Watte
źródło
Ciekawa dyskusja! Dodam, że pierwszy wpis należy wykonać w jednym kroku. Nie można go później dodać (wtedy jesteśmy na terytorium koszyków na zakupy, a wózki sklepowe mają wiele czeków i sald, aby zapobiec wyrządzeniu szkody użytkownikowi końcowemu, nawet ustawodawstwo, przelewy bankowe nie) ...
Erk
33

W kategoriach REST zasoby to rzeczowniki, na które można działać za pomocą czasowników CRUD (tworzenie / odczytywanie / aktualizowanie / usuwanie). Ponieważ nie ma czasownika „transfer pieniędzy”, musimy zdefiniować zasób „transakcji”, na który można działać za pomocą CRUD. Oto przykład w HTTP + POX. Pierwszym krokiem jest TWORZENIE (metoda HTTP POST) nowej pustej transakcji:

POST /transaction

Zwraca to identyfikator transakcji, np. „1234” i zgodnie z adresem URL „/ transaction / 1234”. Należy pamiętać, że wielokrotne uruchomienie tego testu POST nie utworzy tej samej transakcji z wieloma identyfikatorami, a także pozwoli uniknąć wprowadzenia stanu „oczekujący”. Ponadto POST nie zawsze może być idempotentny (wymaganie REST), więc ogólnie dobrą praktyką jest minimalizowanie danych w POST.

Możesz pozostawić generowanie identyfikatora transakcji klientowi. W takim przypadku należy POST / transaction / 1234 utworzyć transakcję „1234”, a serwer zwróciłby błąd, gdyby już istniała. W odpowiedzi błędu serwer może zwrócić aktualnie nieużywany identyfikator z odpowiednim adresem URL. Nie jest dobrym pomysłem wysyłanie zapytań do serwera o nowy identyfikator za pomocą metody GET, ponieważ GET nigdy nie powinien zmieniać stanu serwera, a utworzenie / zarezerwowanie nowego identyfikatora zmieniłoby stan serwera.

Następnie AKTUALIZUJEMY (metoda PUT HTTP) transakcję ze wszystkimi danymi, pośrednio ją zatwierdzając:

PUT /transaction/1234
<transaction>
  <from>/account/john</from>
  <to>/account/bob</to>
  <amount>100</amount>
</transaction>

Jeśli transakcja o identyfikatorze „1234” została wcześniej PUT, serwer wyświetli odpowiedź o błędzie, w przeciwnym razie odpowiedź OK i adres URL umożliwiający wyświetlenie zakończonej transakcji.

Uwaga: w / account / john, „john” powinno naprawdę być unikalnym numerem konta Jana.

Tuckster
źródło
4
Zrównanie REST z CRUD jest poważnym błędem. POST nie musi oznaczać TWORZENIA.
12
Poważny błąd? Wiem, że istnieją różnice między PUT i POST, ale istnieje luźne mapowanie do CRUD. "Poważnie"?
Ted Johnson,
3
Tak poważnie. CRUD to sposób na ustrukturyzowanie przechowywania danych; REST to sposób na ustrukturyzowanie przepływu danych aplikacji. Możesz zrobić CRUD na REST, ale nie możesz zrobić REST na CRUD. Nie są równoważne.
Jon Watte,
20

Świetne pytanie, REST jest głównie wyjaśniony na przykładach podobnych do bazy danych, gdzie coś jest przechowywane, aktualizowane, odzyskiwane, usuwane. Takich przykładów jak ten jest kilka, gdzie serwer ma w jakiś sposób przetwarzać dane. Nie sądzę, żeby Roy Fielding zawarł to w swojej pracy, która w końcu była oparta na http.

Ale on mówi o „reprezentacyjnym transferze stanu” jako o maszynie stanów, z łączami przechodzącymi do następnego stanu. W ten sposób dokumenty (reprezentacje) śledzą stan klienta, zamiast tego, że serwer musi to robić. W ten sposób nie ma stanu klienta, a jedynie określ, na którym linku się znajdujesz.

Myślałem o tym i wydaje mi się rozsądne, że aby zmusić serwer do przetworzenia czegoś za Ciebie, po przesłaniu serwera automatycznie utworzy powiązane zasoby i poda linki do nich (w rzeczywistości nie nie trzeba ich tworzyć automatycznie: może po prostu wskazać linki, a tworzy je tylko wtedy, gdy je podążasz - leniwe tworzenie). A także aby dać ci linki do tworzenia nowych powiązanych zasobów - powiązany zasób ma ten sam identyfikator URI, ale jest dłuższy (dodaje sufiks). Na przykład:

  1. Przesyłasz ( POST ) przedstawienie koncepcji transakcji ze wszystkimi informacjami. To wygląda jak wywołanie RPC, ale tak naprawdę tworzy „proponowany zasób transakcji”. np. URI: /transaction Glitch spowoduje utworzenie wielu takich zasobów, każdy z innym URI.
  2. Odpowiedź serwera podaje identyfikator URI utworzonego zasobu, jego reprezentację - obejmuje to łącze ( URI ) do utworzenia powiązanego zasobu nowego „zatwierdzonego zasobu transakcyjnego”. Inne powiązane zasoby to łącze umożliwiające usunięcie proponowanej transakcji. Są to stany w maszynie stanów, które klient może śledzić. Logicznie rzecz biorąc, są to część zasobu, który został utworzony na serwerze, poza informacjami dostarczonymi przez klienta. np URI: /transaction/1234/proposed, /transaction/1234/committed
  3. Ci POST do linku do stworzenia „popełnił zasób transakcji” , który tworzy ten zasób, zmiany stanu serwera (sald dwóch rachunków) **. Z natury zasób ten można utworzyć tylko raz i nie można go aktualizować. Dlatego nie mogą wystąpić usterki związane z popełnieniem wielu transakcji.
  4. Możesz POBRAĆ te dwa zasoby, aby zobaczyć, jaki jest ich stan. Zakładając, że POST może zmienić inne zasoby, propozycja zostanie oznaczona jako „zatwierdzona” (lub być może w ogóle niedostępna).

Jest to podobne do działania stron internetowych, gdzie końcowa strona internetowa mówi „Czy na pewno chcesz to zrobić?” Ta ostatnia strona internetowa jest sama w sobie reprezentacją stanu transakcji, która zawiera łącze do następnego stanu. Nie tylko transakcje finansowe; także (np.) podgląd, a następnie zatwierdzenie na wikipedii. Wydaje mi się, że różnica w REST polega na tym, że każdy etap w sekwencji stanów ma jawną nazwę (jego identyfikator URI).

W rzeczywistych transakcjach / sprzedaży często istnieją różne dokumenty fizyczne dla różnych etapów transakcji (propozycja, zamówienie zakupu, pokwitowanie itp.). Jeszcze bardziej za zakup domu, z rozliczeniem itp.

OTOH To dla mnie jak zabawa semantyką; Nie podoba mi się nominalizacja konwertowania czasowników na rzeczowniki, aby była REST-owa, „ponieważ używa rzeczowników (URI) zamiast czasowników (wywołania RPC)”. tzn. rzeczownik „zatwierdzony zasób transakcji” zamiast czasownika „zatwierdzić tę transakcję”. Myślę, że jedną z zalet nominalizacji jest to, że możesz odwoływać się do zasobu po nazwie, zamiast określać go w inny sposób (np. Utrzymując stan sesji, aby wiedzieć, czym jest ta transakcja ...)

Ale ważne pytanie brzmi: jakie są zalety tego podejścia? tj. w jaki sposób ten styl REST jest lepszy niż styl RPC? Czy technika, która doskonale sprawdza się w przypadku stron internetowych, jest również pomocna w przetwarzaniu informacji, poza przechowywaniem / pobieraniem / aktualizowaniem / usuwaniem? Myślę, że kluczową zaletą REST jest skalowalność; jednym z aspektów nie jest konieczność jawnego utrzymywania stanu klienta (ale uczynienie go niejawnym w identyfikatorze URI zasobu, a następne stany jako łącza w jego reprezentacji). W tym sensie to pomaga. Może to pomaga również w układaniu warstw / rurociągach? OTOH tylko jeden użytkownik będzie patrzył na swoją konkretną transakcję, więc nie ma żadnej korzyści z buforowania jej, aby inni mogli ją odczytać, wielka wygrana dla http.

13ren
źródło
Czy mógłbyś wyjaśnić, w jaki sposób „brak konieczności utrzymywania stanu klienta” pomaga w skalowalności? Jaki rodzaj skalowalności? W jakim sensie skalowalność?
jhegedus
11

Jeśli cofniesz się przed podsumowaniem dyskusji tutaj, jest całkiem jasne, że REST nie jest odpowiedni dla wielu interfejsów API, szczególnie gdy interakcja klient-serwer jest z natury stanowa, tak jak ma to miejsce w przypadku nietrywialnych transakcji. Po co przeskakiwać przez wszystkie sugerowane obręcze, zarówno dla klienta, jak i serwera, aby pedantycznie przestrzegać jakiejś zasady, która nie pasuje do problemu? Lepszą zasadą jest zapewnienie klientowi najłatwiejszego, najbardziej naturalnego i produktywnego sposobu komponowania z aplikacją.

Podsumowując, jeśli naprawdę wykonujesz wiele transakcji (typów, a nie instancji) w swojej aplikacji, naprawdę nie powinieneś tworzyć interfejsu API RESTful.

Peris
źródło
9
Zgadza się, ale jaka powinna być alternatywa w przypadku rozproszonej architektury mikrousług?
Vitamon
11

Od 10 lat oddalam się od tego tematu. Wracając, nie mogę uwierzyć w religię podszywającą się pod naukę, w którą wkraczasz, gdy Google Rest + jest wiarygodne. Zamieszanie jest mityczne.

Podzieliłbym to szerokie pytanie na trzy:

  • Usługi niższego szczebla. Każda utworzona usługa sieciowa będzie miała usługi podrzędne, których używasz i których składnię transakcji nie masz innego wyjścia, jak tylko przestrzegać. Powinieneś spróbować ukryć to wszystko przed użytkownikami twojej usługi i upewnić się, że wszystkie części twojej operacji zakończą się sukcesem lub niepowodzeniem jako grupa, a następnie zwróć ten wynik użytkownikom.
  • Twoje usługi. Klienci chcą jednoznacznych wyników wywołań usług internetowych, a zwykły wzorzec REST polegający na wykonywaniu żądań POST, PUT lub DELETE bezpośrednio na zasobach merytorycznych wydaje mi się słabym i łatwo ulepszonym sposobem zapewnienia tej pewności. Jeśli zależy Ci na niezawodności, musisz identyfikować żądania działań. Ten identyfikator może być identyfikatorem guid utworzonym na kliencie lub wartością początkową z relacyjnej bazy danych na serwerze, nie ma to znaczenia. W przypadku identyfikatorów wygenerowanych przez serwer należy użyć odpowiedzi na żądanie „inspekcji wstępnej”, aby wymienić identyfikator akcji. Jeśli to żądanie się nie powiedzie lub w połowie się powiedzie, nie ma problemu, klient po prostu powtarza żądanie. Niewykorzystane identyfikatory nie szkodzą.

    Jest to ważne, ponieważ pozwala wszystkim kolejnym żądaniom być w pełni idempotentne, w tym sensie, że jeśli zostaną powtórzone n razy, zwrócą ten sam wynik i nic więcej się nie stanie. Serwer przechowuje wszystkie odpowiedzi względem identyfikatora akcji i jeśli zobaczy to samo żądanie, odtwarza tę samą odpowiedź. Pełniejsze omówienie wzoru znajduje się w tym dokumencie Google . Dokument sugeruje implementację, która, jak sądzę (!), Jest zgodna z zasadami REST. Eksperci na pewno powiedzą mi, jak to narusza innych. Ten wzorzec może być przydatny w przypadku każdego niebezpiecznego połączenia z usługą internetową, niezależnie od tego, czy są zaangażowane transakcje podrzędne.
  • Integracja Twojej usługi w „transakcje” kontrolowane przez usługi upstream. W kontekście usług internetowych, pełne transakcje ACID są uważane za zwykle niewarte wysiłku, ale możesz znacznie pomóc konsumentom swojej usługi, zapewniając anulowanie i / lub potwierdzenie linków w odpowiedzi potwierdzającej, a tym samym osiągać transakcje za pomocą rekompensaty .

Twoje wymagania są fundamentalne. Nie pozwól ludziom wmawiać, że Twoje rozwiązanie nie jest koszerne. Oceń ich architektury pod kątem tego, jak dobrze i jak prosto rozwiązują Twój problem.

bbsimonbb
źródło
9

Będziesz musiał utworzyć własny typ zarządzania wysyłkami typu „identyfikator transakcji”. Więc to byłyby 4 wywołania:

http://service/transaction (some sort of tx request)
http://service/bankaccount/bob (give tx id)
http://service/bankaccount/john (give tx id)
http://service/transaction (request to commit)

Będziesz musiał obsłużyć przechowywanie akcji w bazie danych (jeśli obciążenie jest zrównoważone) lub w pamięci lub w tym podobnym, a następnie obsłużyć zatwierdzenie, wycofanie, limit czasu.

Niezbyt spokojny dzień w parku.

TheSoftwareJedi
źródło
4
Nie sądzę, żeby to była szczególnie dobra ilustracja. Potrzebujesz tylko dwóch kroków: Utwórz transakcję (tworzy transakcję w stanie „oczekującym”) i Zatwierdź transakcję (zatwierdza, jeśli niezatwierdzono i przenosi zasób do stanu zatwierdzonego lub wycofanego).
Jon Watte,
2

Myślę, że w tym przypadku całkowicie dopuszczalne jest w tej sytuacji złamanie czystej teorii REST. W każdym razie nie sądzę, aby w REST było coś, co mówi, że nie można dotykać zależnych obiektów w przypadkach biznesowych, które tego wymagają.

Naprawdę uważam, że nie warto robić dodatkowych kółek, przez które przeskoczysz, aby stworzyć niestandardowego menedżera transakcji, kiedy możesz po prostu wykorzystać bazę danych do tego.

Toby Hede
źródło
2

Przede wszystkim przekazanie pieniędzy to nic, czego nie możesz zrobić w jednym wezwaniu do pomocy. Akcja, którą chcesz zrobić, to wysyłanie pieniędzy. Więc dodajesz zasób przekazu pieniężnego na konto nadawcy.

POST: accounts/alice, new Transfer {target:"BOB", abmount:100, currency:"CHF"}.

Gotowe. Nie musisz wiedzieć, że jest to transakcja, która musi być atomowa itp. Po prostu przelewasz pieniądze aka. wysłać pieniądze z punktu A do B.


Ale w rzadkich przypadkach tutaj ogólne rozwiązanie:

Jeśli chcesz zrobić coś bardzo złożonego, obejmującego wiele zasobów w określonym kontekście z wieloma ograniczeniami, które faktycznie przekraczają barierę „co / dlaczego” (wiedza biznesowa a wdrożeniowa), musisz przenieść stan. Ponieważ REST powinien być bezstanowy, jako klient musisz przenosić stan.

Jeśli przenosisz stan, musisz ukryć informacje przed klientem. Klient nie powinien znać wewnętrznych informacji potrzebnych tylko do wdrożenia, ale nie powinien posiadać informacji istotnych z punktu widzenia biznesu. Jeśli te informacje nie mają wartości biznesowej, należy zaszyfrować stan i użyć metafory, takiej jak token, przepustka lub coś innego.

W ten sposób można przekazać stan wewnętrzny, a szyfrowanie i podpisywanie systemu może być nadal bezpieczne i solidne. Znalezienie odpowiedniej abstrakcji dla klienta, dlaczego przekazuje informacje o stanie, zależy od projektu i architektury.


Prawdziwe rozwiązanie:

Pamiętaj, że REST mówi o HTTP, a HTTP zawiera koncepcję używania plików cookie. Te pliki cookie są często zapomniane, gdy ludzie mówią o REST API oraz przepływach pracy i interakcjach obejmujących wiele zasobów lub żądań.

Pamiętaj, co jest napisane w Wikipedii o plikach cookie HTTP:

Pliki cookie mają być niezawodnym mechanizmem umożliwiającym witrynom zapamiętywanie informacji stanowych (takich jak pozycje w koszyku) lub rejestrowanie czynności przeglądania użytkownika (w tym klikania w poszczególne przyciski, logowania lub zapisywania, które strony odwiedził użytkownik) sprzed miesięcy lub lat).

Zasadniczo, jeśli chcesz przekazać stan, użyj pliku cookie. Został zaprojektowany z dokładnie tego samego powodu, jest to HTTP i dlatego jest zgodny z REST z założenia :).


Lepsze rozwiązanie:

Jeśli mówisz o kliencie wykonującym przepływ pracy obejmujący wiele żądań, zwykle mówisz o protokole. Każda forma protokołu zawiera zestaw warunków wstępnych dla każdego potencjalnego kroku, takich jak wykonanie kroku A, zanim będzie można wykonać B.

Jest to naturalne, ale ujawnienie protokołu klientom komplikuje wszystko. Aby tego uniknąć, pomyśl tylko, co robimy, gdy musimy wykonywać złożone interakcje i robić rzeczy w prawdziwym świecie ... Korzystamy z agenta.

Korzystając z metafory agenta, możesz zapewnić zasób, który może wykonać wszystkie niezbędne kroki za Ciebie i zapisać na swojej liście faktyczny przydział / instrukcje, na podstawie których działa (abyśmy mogli użyć POST na agencie lub „agencji”).

Złożony przykład:

Kupno domu:

Musisz udowodnić swoją wiarygodność (np. Dostarczając wpisy z akt policyjnych), musisz zapewnić szczegóły finansowe, musisz kupić rzeczywisty dom za pośrednictwem prawnika i zaufanej osoby trzeciej przechowującej fundusze, zweryfikować, czy dom należy teraz do Ciebie i dodaj informacje o zakupach do swoich ewidencji podatkowych itp. (na przykład niektóre kroki mogą być błędne lub cokolwiek innego).

Wykonanie tych czynności może zająć kilka dni, niektóre można wykonać równolegle itp.

Aby to zrobić, wystarczy dać agentowi zadanie kupna domu, takie jak:

POST: agency.com/ { task: "buy house", target:"link:toHouse", credibilities:"IamMe"}.

Gotowe. Agencja odsyła Ci referencję, której możesz użyć do sprawdzenia i śledzenia statusu tej pracy, a reszta jest wykonywana automatycznie przez agentów agencji.

Pomyśl na przykład o narzędziu do śledzenia błędów. Zasadniczo zgłaszasz błąd i możesz użyć identyfikatora błędu, aby sprawdzić, co się dzieje. Możesz nawet skorzystać z usługi, aby nasłuchiwać zmian w tym zasobie. Misja wykonana.

Martin Kersten
źródło
1

Nie możesz używać transakcji po stronie serwera w REST.

Jeden z ograniczeń REST:

Bezpaństwowcy

Komunikacja klient-serwer jest dodatkowo ograniczona przez brak kontekstu klienta na serwerze pomiędzy żądaniami. Każde żądanie od dowolnego klienta zawiera wszystkie informacje niezbędne do obsługi żądania, a każdy stan sesji jest przechowywany w kliencie.

Jedynym sposobem RESTful jest utworzenie dziennika powtórzeń transakcji i wprowadzenie go do stanu klienta. Wraz z żądaniami klient wysyła dziennik powtórzeń, a serwer ponawia transakcję i

  1. wycofuje transakcję, ale udostępnia nowy dziennik powtórzeń transakcji (krok dalej)
  2. lub ostatecznie sfinalizuj transakcję.

Ale może prostsze jest użycie technologii opartej na sesji serwera, która obsługuje transakcje po stronie serwera.

bebbo
źródło
Cytat pochodzi z wpisu REST w Wikipedii. Czy to jest prawdziwe źródło, czy też Wikipedia skądś je wzięła? Kto ma powiedzieć, jaki jest kontekst klienta, a jaki kontekst serwera?
bbsimonbb
1

Uważam, że miałoby to miejsce w przypadku użycia unikalnego identyfikatora wygenerowanego na kliencie, aby upewnić się, że czkawka połączenia nie oznacza dwulicowości zapisanej przez API.

Myślę, że użycie pola GUID wygenerowanego przez klienta wraz z obiektem transferu i upewnienie się, że ten sam identyfikator GUID nie został ponownie wstawiony, byłoby prostszym rozwiązaniem w przypadku przelewu bankowego.

Nie znasz bardziej złożonych scenariuszy, takich jak rezerwacja wielu biletów lotniczych lub mikroarchitektury.

Znalazłem artykuł na ten temat, dotyczący doświadczeń związanych z niepodzielnością transakcji w usługach RESTful .

Eduardo Rolim
źródło
0

W prostym przypadku (bez zasobów rozproszonych) transakcję można by traktować jako zasób, w którym akt jej tworzenia osiąga cel końcowy.

Tak więc, aby przenieść między <url-base>/account/ai <url-base>/account/b, możesz wysłać następujące wiadomości do <url-base>/transfer.

<transfer>
    <from> <url-base> / account / a </from>
    <to> <url-base> / account / b </to>
    <amount> 50 </amount>
</transfer>

Spowoduje to utworzenie nowego zasobu transferu i zwrócenie nowego adresu URL transferu - na przykład <url-base>/transfer/256.

W momencie pomyślnego zaksięgowania na serwerze przeprowadzana jest więc „prawdziwa” transakcja, a kwota pobierana z jednego konta i dodawana do drugiego.

Nie obejmuje to jednak transakcji rozproszonych (jeśli, powiedzmy, „a” jest przetrzymywane w jednym banku za jedną usługą, a „b” jest przechowywane w innym banku za inną usługą) - inne niż stwierdzenie „spróbuj ująć wszystko operacje w sposób niewymagający transakcji rozproszonych ”.

Phasmal
źródło
2
Jeśli nie możesz „sformułować wszystkich operacji w sposób, który nie wymaga transakcji rozproszonych”, to naprawdę potrzebujesz dwufazowego zatwierdzenia. Najlepszym pomysłem, jaki mogłem znaleźć na implementację zatwierdzania dwufazowego w REST, jest rest.blueoxen.net/cgi-bin/wiki.pl?TwoPhaseCommit , co, co ważne, nie psuje przestrzeni nazw URL i pozwala na nałożenie dwufazowego zatwierdzenia czysta semantyka REST.
Phasmal
3
Innym problemem związanym z tą sugestią jest to, że jeśli czkawka z pamięcią podręczną wyskakuje i dwukrotnie POST, otrzymujesz dwa transfery.
Jon Watte,
To prawda, w takim przypadku musiałbyś mieć proces dwuetapowy - utwórz zasób „transfer” z unikalnym adresem URL, a następnie dodaj do niego szczegóły transferu jako część zatwierdzenia (dwie części, jak wspomniano w innych odpowiedziach). Oczywiście można to wtedy sformułować jako tworzenie zasobu „transakcyjnego”, a następnie dodawanie do niego operacji „przelew”.
Phasmal
-3

Myślę, że możesz dołączyć TAN do adresu URL / zasobu:

  1. PUT / transakcja, aby uzyskać identyfikator (np. „1”)
  2. [PUT, GET, POST, cokolwiek] / 1 / account / bob
  3. [PUT, GET, POST, cokolwiek] / 1 / konto / rachunek
  4. USUŃ / transakcja o ID 1

Tylko pomysł.

Do
źródło
Widzę dwa problemy z tym podejściem: 1) Oznacza to, że nie możesz uzyskać dostępu do zasobu poza transakcją (chociaż może to nie jest wielka sprawa). 2) Żadna z dotychczasowych odpowiedzi nie dotyczyła faktu, że serwer nie jest już bezpaństwowy, chociaż podejrzewam, że nic nie można na to poradzić.
Gili
Cóż, / 1 / account / bob i / account / bob to tylko dwa różne zasoby. :) I RE: bezstanowe, oznacza to, że zasób jest zawsze dostępny i nie zależy od poprzedniego żądania. Ponieważ poprosiłeś o transakcje, tak nie jest. Ale z drugiej strony chciałeś transakcji.
Do
1
Jeśli klient musi zebrać identyfikatory URI, Twój interfejs API nie jest zgodny z REST.
aehlke
1
Naprawdę was nie rozumiem! Jeśli traktujesz transakcję jako zasób (jak w powyższym przykładzie), po prostu przestajesz traktować transakcję w klasycznym sensie i wykorzystujesz ją w „właściwy sposób REST”, co dodatkowo upraszcza programowanie procesów transakcyjnych. Możesz na przykład dołączyć href do transakcji w swoich odpowiedziach, aby obejść przesunięcie w rozproszonym środowisku po stronie serwera, nadal jest to bezstanowe (to tylko zasób, prawda?) I możesz wdrożyć rzeczywisty mechanizm transakcji i tak chcesz (a co jeśli nie masz DB z tyłu?)
Matthias Hryniszak
1
Tak czy inaczej, jeśli po prostu przestaniesz myśleć o SQL / SOAP i zaczniesz myśleć o HTTP (tak jak robi to przeglądarka), wszystko stanie się proste
Matthias Hryniszak