Pozyskiwanie zdarzeń i REST

17

Natrafiłem na projekt Event Sourcing i chciałbym użyć go w aplikacji, w której potrzebny jest klient REST (a dokładniej RESTful). Jednak nie udało mi się połączyć ich ze sobą, ponieważ REST jest dość podobny do CRUD, a pozyskiwanie zdarzeń jest oparte na zadaniach. Zastanawiałem się, jak zaprojektować tworzenie poleceń na podstawie żądań do serwera REST. Rozważ ten przykład:

Za pomocą REST możesz nadać nowy zasób nazwie File. W jednym żądaniu możesz wysłać nową nazwę pliku, możesz zmienić folder nadrzędny i / lub zmienić właściciela pliku i tak dalej.

Jak zbudować serwer, aby móc korzystać ze źródeł zdarzeń. Myślałem o tych możliwościach:

  1. Ustal na serwerze, które pola zostały zmienione i stworzyć odpowiednie polecenia ( RenameFileCommand, MoveFileCommand, ChangeOwnerCommand, ...) i wysyła je indywidualnie. Jednak w tej konfiguracji każde z poleceń może zawieść, pozostawiając innych poza transakcją, a tym samym poza „atomową” zmianą zasobu.

  2. Wysyłamy tylko jedno polecenie ( UpdateFileCommand) oraz obsługi poleceń, a dokładniej w agregacie, określić, które pola zostały zmienione i wysyłanie pojedynczych zdarzeń zamiast ( FileRenamedEvent, FileMovedEvent, OwnerChangedEvent, ...)

  3. Ten w ogóle mi się nie podoba: w żądaniu do serwera w nagłówkach określam, której komendy użyć, ponieważ interfejs użytkownika jest nadal oparty na zadaniach (ale komunikacja odbywa się za pośrednictwem REST). Nie powiedzie się to jednak przy żadnym innym użyciu komunikacji REST (np. W aplikacjach zewnętrznych), ponieważ nie są zobowiązani do zmiany tylko jednego pola w jednym żądaniu. Wnoszę też całkiem spore sprzężenie z interfejsem użytkownika, REST i ES.

Który wolisz, czy jest na to lepszy sposób?

Uwaga dodatkowa: aplikacja napisana w Javie i Axon Framework do pozyskiwania zdarzeń.

rudowłosy
źródło
Na pewno nie trzeci. Zrobiłbym pierwszy, ale muszę o tym pomyśleć.
inf3rno
Czy zastanawiasz się, czy jedno żądanie HTTP może skutkować wieloma poleceniami? Czy dobrze rozumiem? Moim zdaniem. powinno być w stanie to zrobić, ale od dłuższego czasu nie czytałem o DDD, więc muszę sprawdzić przykładowy kod, jak to zaimplementować.
inf3rno
Znalazłem przykład, ale to CRUD. github.com/szjani/predaddy-issuetracker-sample/blob/3.0/src/hu/ ... Zapytam autora, jakie jest jego zdanie, on wie więcej o DDD ode mnie.
inf3rno
1
Moje jest to, że powinieneś używać wielu poleceń w „jednostce pracy”, jeśli chcesz natychmiastowej spójności. Jeśli chodzi o ostateczną spójność, to pytanie nie ma dla mnie sensu. Innym możliwym rozwiązaniem do wysłania polecenia CompositeCommand, które może zawierać polecenia, które chcesz wykonać atomowo. Może to być prosta kolekcja, jedyne, co ważne, to że autobus da sobie z nią radę.
inf3rno
1
Według niego powinieneś spróbować osiągnąć stosunek 1: 1 między poleceniami a żądaniami HTTP. Jeśli nie możesz tego zrobić (to jest nieprzyjemny zapach), powinieneś użyć luzu (nazwałem go kompozytem), aby był atomowy.
inf3rno

Odpowiedzi:

11

Myślę, że możesz mieć tutaj niedopasowanie procesu użytkownika do procesu implementacji.

Po pierwsze: czy użytkownik szczerze chce wykonać wiele zmian w pliku jednocześnie? Zmiana nazwy (która może, ale nie musi obejmować zmiany ścieżki?), Zmiana własności i być może zmiana zawartości pliku (ze względu na argument) wydają się być osobnymi działaniami.

Weźmy przypadek, w którym odpowiedź brzmi „tak” - Twoi użytkownicy naprawdę chcą wprowadzić te zmiany jednocześnie.

W takim razie, ja zdecydowanie odradzam dowolnej implementacji, który wysyła wiele zdarzeń - RenameFileCommand, MoveFileCommand, ChangeOwnerCommand- reprezentować ten pojedynczy intencje użytkownika.

Dlaczego? Ponieważ zdarzenia mogą się nie powieść. Być może jest to niezwykle rzadkie, ale użytkownik przesłał operację, która wyglądała na atomową - jeśli jedno z poniższych zdarzeń zakończy się niepowodzeniem, stan aplikacji jest teraz nieprawidłowy.

Zapraszasz również zagrożenia rasowe do zasobu, który jest wyraźnie współdzielony przez każdą z osób zajmujących się wydarzeniami. Będziesz musiał napisać „ChangeOwnerCommand” w taki sposób, aby nazwa pliku i ścieżka pliku nie miały znaczenia, ponieważ mogą być nieaktualne do czasu otrzymania polecenia.

Wdrażając niespieszny system sterowany zdarzeniami z przenoszeniem i zmianą nazw plików, wolę zapewnić spójność, używając czegoś takiego jak system eTag - upewnij się, że edytowany zasób jest wersją ostatnio pobraną przez użytkownika, i jeśli nie, od tego czasu został zmodyfikowany. Ale jeśli wysyłasz wiele poleceń dla tej operacji dla pojedynczego użytkownika, będziesz musiał zwiększyć swoją wersję zasobu po każdym poleceniu - więc nie masz sposobu, aby wiedzieć, że zasób, który użytkownik edytuje, jest naprawdę tą samą wersją, co zasób, który ostatnio czytał .

Rozumiem przez to - co jeśli ktoś inny wykonuje inną operację na pliku w tym samym czasie. 6 poleceń można ułożyć w stos w dowolnej kolejności. Gdybyśmy mieli tylko 2 polecenia atomowe, wcześniejsze polecenie mogłoby się powieść, a późniejsze polecenie mogło się nie powieść „zasób został zmodyfikowany od ostatniego pobrania”. Ale nie ma ochrony przed tym, gdy polecenia nie są atomowe, więc naruszona jest spójność systemu.

Co ciekawe, w REST nastąpił ruch w kierunku architektury opartej na zdarzeniach, zwanej „Odpoczynek bez PUT”, zalecanej przez radar technologii Thoughtworks, styczeń 2015 . Istnieje tutaj znacznie dłuższy blog o odpoczynku bez PUT .

Zasadniczo chodzi o to, że POST, PUT, DELETE i GET są odpowiednie dla małych aplikacji, ale gdy musisz zacząć zakładać, jak interpretować wstawianie i wysyłanie i usuwanie może być interpretowane na drugim końcu, możesz wprowadzić sprzężenie. (np. „kiedy USUWAM zasób powiązany z moim kontem bankowym, konto powinno zostać zamknięte”). Proponowane rozwiązanie polega na traktowaniu REST w sposób bardziej pozyskany od zdarzenia. tj. POST zamiar użytkownika jako pojedynczy zasób zdarzenia.

Drugi przypadek jest prostszy. Jeśli użytkownicy nie chcą wykonywać wszystkich tych operacji jednocześnie, nie pozwól im. POST zdarzenie dla każdego zamiaru użytkownika. Teraz możesz korzystać z wersji etag na swoich zasobach.

Podobnie jak w przypadku innych aplikacji, które używają zupełnie innego interfejsu API niż zasoby. To pachnie kłopotami. Czy możesz zbudować fasadę starego interfejsu API na szczycie interfejsu API RESTful i skierować je na fasadę? tj. udostępnić usługę, która wykonuje wiele aktualizacji pliku w sekwencji za pośrednictwem serwera REST?

Jeśli nie zbudujesz interfejsu RESTful na starym rozwiązaniu, ani nie zbudujesz fasady starego interfejsu na rozwiązaniu REST i nie będziesz próbował utrzymać obu interfejsów API wskazujących na udostępniony zasób danych, napotkasz poważne problemy.

perfekcjonista
źródło
Widzę niedopasowanie, jednak przez REST w zasadzie można zaktualizować stan wielu pól, umieszczając nowy stan w zasobie (przez pewną reprezentację). Tak działa REST z definicji - reprezentatywny transfer stanu, wadą jest to, że zamiar użytkownika zostaje utracony w procesie. Ponadto REST jest prawie standardem dla zewnętrznego interfejsu API. Chcę tylko zaprojektować aplikację, aby móc korzystać z obu. Usunięcie PUT jest jak obejście ze względu na ES. Z tego, co widzę, wykonalne jest jedno polecenie aktualizacji emitujące wiele zdarzeń, o ile obsługa poleceń i zdarzeń jest w jednej transakcji.
ruda
W każdym razie chciałbym zrobić Etag. Również link do „dłuższego posta na blogu” jest taki sam jak pierwszy link.
ruda
Wrócę do tego po kilku rozważaniach z PUT. Z pewnością usunięcie PUT to nie tylko „obejście dla ES”. Naprawiłem link do bloga.
perfekcjonista
4

Właśnie teraz natknąłem się na następujący artykuł, który zachęca do podania nazw poleceń w żądaniu do serwera w nagłówku Content-Type (przy zachowaniu 5 poziomów typu mediów).

W artykule wspominają, że styl RPC jest zły dla REST i sugerują rozszerzenie Content-Type w celu określenia nazwy polecenia:

Jednym z powszechnych podejść jest korzystanie z zasobów w stylu RPC, na przykład / api / InventoryItem / {id} / rename. Chociaż pozornie eliminuje to potrzebę stosowania czasowników, jest to sprzeczne z prezentacją zorientowaną na zasoby REST. Należy przypomnieć, że zasób jest rzeczownikiem, a czasownik HTTP to czasownik / akcja, a komunikaty opisowe (jedna z zasad REST) ​​są nośnikiem przekazywania innych osi informacji i intencji. W rzeczywistości polecenie w ładunku wiadomości HTTP powinno wystarczyć do wyrażenia dowolnego dowolnego działania. Jednak poleganie na treści wiadomości ma swoje własne problemy, ponieważ treść jest zwykle dostarczana jako strumień i buforowanie treści w całości przed zidentyfikowaniem działania nie zawsze jest możliwe lub mądre.

PUT /api/InventoryItem/4454c398-2fbb-4215-b986-fb7b54b62ac5 HTTP/1.1
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Content-Type:application/json;domain-model=RenameInventoryItemCommand`

Artykuł znajduje się tutaj: http://www.infoq.com/articles/rest-api-on-cqrs

Możesz przeczytać więcej o 5 poziomach rodzaju mediów tutaj: http://byterot.blogspot.co.uk/2012/12/5-levels-of-media-type-rest-csds.html


Chociaż udostępniają zdarzenia domeny interfejsowi API REST, co uważam za złą praktykę, podoba mi się to rozwiązanie, ponieważ nie tworzy ono nowego „protokołu” wyłącznie dla CQRS, czy to wysyłania nazw poleceń w treści, czy w dodatkowej postaci nagłówek i pozostaje wierny zasadom RESTful.

rudowłosy
źródło