Jak najlepiej reprezentujesz dwukierunkową synchronizację w interfejsie API REST?

23

Zakładając, że w systemie jest aplikacja sieci Web z zasobem i odniesienie do aplikacji zdalnej z innym podobnym zasobem, w jaki sposób reprezentujesz dwukierunkową akcję synchronizacji, która synchronizuje zasób „lokalny” z zasobem „zdalnym”?

Przykład:

Mam interfejs API reprezentujący listę czynności do wykonania.

GET / POST / PUT / DELETE / todos / itp.

Ten interfejs API może odwoływać się do zdalnych usług TODO.

GET / POST / PUT / DELETE / todo_services / itp.

Mogę manipulować todos ze zdalnej usługi za pośrednictwem mojego interfejsu API jako proxy za pośrednictwem

GET / POST / PUT / DELETE / todo_services / abc123 / itd.

Chcę mieć możliwość dwukierunkowej synchronizacji między lokalnym zestawem zadań do zrobienia a zdalnym zestawem TODOS.

W pewien sposób można to zrobić

POST / todo_services / abc123 / sync /

Ale czy w idei „czasowniki są złe” istnieje lepszy sposób na przedstawienie tego działania?

Edward M. Smith
źródło
4
Myślę, że dobry projekt API jest całkowicie zależny od bardzo konkretnego zrozumienia tego, co rozumiesz przez synchronizację. „Synchronizacja” dwóch źródeł danych jest zwykle bardzo złożonym problemem, który jest bardzo łatwy do uproszczenia, ale bardzo trudny do przemyślenia we wszystkich jego implikacjach. Uczyń to synchronizacją „dwukierunkową”, a nagle trudność jest znacznie większa. Zacznij od przemyślenia bardzo trudnych pytań.
Adam Crossland
Racja - załóżmy, że algorytm synchronizacji został zaprojektowany i działa w interfejsie API „na poziomie kodu” - w jaki sposób mogę to ujawnić za pomocą usługi REST. Synchronizacja jednokierunkowa wydaje się o wiele łatwiejsza do wyrażenia: ja GET /todo/1/i POSTto do /todo_services/abc123/ Ale, dwukierunkowa - nie biorę zestawu danych i nie umieszczam go w zasobie, działanie, które podejmuję, powoduje potencjalną modyfikację dwóch zasobów. Wydaje mi się, że mógłbym polegać na tym, że same „todo syncronizations” są zasobami POST /todo_synchronizations/ {"todos":["/todo/1/","/todo_services/abc123/1"],"schedule":"now"}
Edward M Smith,
Nadal mamy problem z wózkiem przed koniem. Chodziło mi o to, że nie można zakładać, że synchronizacja działa i projektuje interfejs API. Projektowanie interfejsu API będzie oparte na licznych obawach dotyczących dokładnego działania algorytmu synchronizacji.
Adam Crossland,
To potencjalnie ujawnia przydatne wyniki: GET /todo_synchronizations/1=>{"todos":["/todo/1/","/todo_services/abc123/1"],"schedule":"now","ran_at":"datetime","result":"success"}
Edward M Smith
2
Zgadzam się z @Adam. Czy wiesz, jak zamierzasz wdrożyć synchronizację? Jak sobie radzisz ze zmianami? Czy masz po prostu dwa zestawy elementów, które chcesz pogodzić, czy masz dziennik działań, które spowodowały rozbieżność tych dwóch zestawów od ostatniej synchronizacji? Powód, dla którego pytam, może być trudny do wykrycia dodawania i usuwania (niezależnie od REST). Jeśli masz obiekt po stronie serwera i nie masz go po stronie klienta, musisz zadać sobie pytanie: „Czy klient go usunął, czy serwer go utworzył?” Tylko wtedy, gdy dokładnie wiesz, jak zachowuje się „zasób”, możesz dokładnie przedstawić go w REST.
Raymond Saltrelli

Odpowiedzi:

17

Gdzie i jakie są zasoby?

REST polega na adresowaniu zasobów w sposób bezstanowy i możliwy do wykrycia. Nie musi być implementowany przez HTTP, ani nie musi polegać na JSON ani XML, chociaż zdecydowanie zaleca się stosowanie formatu danych hipermedialnych (patrz zasada HATEOAS ), ponieważ pożądane są linki i identyfikatory.

Powstaje zatem pytanie: jak ocenia się synchronizację pod względem zasobów?

Co to jest synchronizacja dwukierunkowa? **

Dwukierunkowa synchronizacja to proces aktualizowania zasobów obecnych na wykresie węzłów, tak aby pod koniec procesu wszystkie węzły zaktualizowały swoje zasoby zgodnie z regułami rządzącymi tymi zasobami. Zazwyczaj należy rozumieć, że wszystkie węzły miałyby najnowszą wersję zasobów obecnych na wykresie. W najprostszym przypadku wykres składa się z dwóch węzłów: lokalnego i zdalnego. Lokalny inicjuje synchronizację.

Tak więc kluczowym zasobem, który należy rozwiązać, jest dziennik transakcji, a zatem proces synchronizacji może wyglądać tak dla kolekcji „pozycji” w HTTP:

Krok 1 - Lokalny pobiera dziennik transakcji

Lokalny: GET /remotehost/items/transactions?earliest=2000-01-01T12:34:56.789Z

Zdalny: 200 OK z treścią zawierającą dziennik transakcji zawierający pola podobne do tego.

  • itemId - UUID w celu udostępnienia wspólnego klucza podstawowego

  • updatedAt - znacznik czasu zapewniający skoordynowany punkt ostatniej aktualizacji danych (przy założeniu, że historia zmian nie jest wymagana)

  • fingerprint- skrót SHA1 zawartości danych do szybkiego porównania, jeśli updateAtupłynie kilka sekund

  • itemURI - pełny identyfikator URI elementu, aby umożliwić późniejsze pobranie

Krok 2 - Lokalny porównuje zdalny dziennik transakcji z własnym

Jest to zastosowanie reguł biznesowych dotyczących synchronizacji. Zazwyczaj itemIdidentyfikuje zasób lokalny, a następnie porównuje odcisk palca. Jeśli istnieje różnica, dokonuje się porównania updatedAt. Jeśli są zbyt blisko, aby zadzwonić, wówczas trzeba będzie podjąć decyzję o pobraniu w oparciu o drugi węzeł (być może jest to ważniejsze) lub o przepchnięciu do drugiego węzła (ten węzeł jest ważniejszy). Jeśli zdalny zasób nie jest obecny lokalnie, wprowadzany jest wpis wypychany (zawiera on rzeczywiste dane do wstawienia / aktualizacji). Zakłada się, że wszelkie zasoby lokalne nieobecne w zdalnym dzienniku transakcji pozostają niezmienione.

Żądania ściągania są wysyłane do zdalnego węzła, aby dane istniały lokalnie przy użyciu itemURI. Nie są stosowane lokalnie do później.

Krok 3 - Wciśnij dziennik transakcji synchronizacji lokalnej do zdalnego

Lokalny: PUT /remotehost/items/transactions z treścią zawierającą dziennik transakcji synchronizacji lokalnej.

Węzeł zdalny może przetwarzać to synchronicznie (jeśli jest mały i szybki) lub asynchronicznie (pomyśl 202 ZAAKCEPTOWANO ), jeśli może to spowodować znaczne obciążenie. Zakładając synchroniczną operację, wynik będzie wynosił 200 OK lub 409 KONFLIKT, w zależności od powodzenia lub niepowodzenia. W przypadku KONFLIKTU 409 proces musi zostać ponownie uruchomiony, ponieważ w zdalnym węźle wystąpił optymistyczny błąd blokowania (ktoś zmienił dane podczas synchronizacji). Zdalne aktualizacje są przetwarzane w ramach własnej transakcji aplikacji.

Krok 4 - Zaktualizuj lokalnie

Dane pobrane w kroku 2 są stosowane lokalnie w ramach transakcji aplikacji.

Chociaż powyższe nie jest idealne (istnieje kilka sytuacji, w których lokalny i zdalny może wpaść w kłopoty, a zdalne pobieranie danych z lokalnego jest prawdopodobnie bardziej wydajne niż umieszczanie go w dużym PUT), pokazuje jednak, w jaki sposób można użyć REST podczas bi- proces synchronizacji kierunkowej.

Gary Rowe
źródło
6

Rozważę operację synchronizacji jako zasób, do którego można uzyskać dostęp (GET) lub utworzyć (POST). Mając to na uwadze, adres URL interfejsu API może być:

/todo_services/abc123/synchronization

(Nazywając to „synchronizacją”, a nie „synchronizacją”, aby było jasne, że nie jest to czasownik)

Następnie wykonaj:

POST /todo_services/abc123/synchronization

Aby zainicjować synchronizację. Ponieważ operacja synchronizacji jest zasobem, to wywołanie może potencjalnie zwrócić identyfikator, którego można następnie użyć do sprawdzenia statusu operacji:

GET /todo_services/abc123/synchronization?id=12345
Laurent
źródło
3
Ta prosta odpowiedź jest odpowiedzią. Zamień swoje czasowniki w rzeczowniki i
idź
5

To trudny problem. Nie sądzę, aby REST był odpowiednim poziomem do wdrożenia synchronizacji. Solidna synchronizacja musiałaby zasadniczo być transakcją rozproszoną. REST nie jest narzędziem do tego zadania.

(Założenie: przez „synchronizację” sugerujesz, że jeden zasób może się zmienić niezależnie od drugiego w dowolnym momencie i chcesz mieć możliwość wyrównywania ich bez utraty aktualizacji.)

Możesz rozważyć ustawienie jednego z nich jako „głównego”, a drugiego „podrzędnego”, abyś mógł pewnie okresowo blokować dane podrzędne danymi nadrzędnymi.

Możesz również rozważyć Microsoft Sync Framework, jeśli absolutnie potrzebujesz obsługi niezależnie zmieniających się magazynów danych. To nie działałoby przez REST, ale za kulisami.

codingoutloud
źródło
5
+1 za „trudny problem”. Synchronizacja dwukierunkowa jest jedną z tych rzeczy, o których nie zdajesz sobie sprawy, jak ciężko jest, dopóki nie zanurzysz się w błocie.
Dan Ray
2

Apache CouchDB to baza danych oparta na REST, HTTP i JSON. Programiści wykonują podstawowe operacje CRUD przez HTTP. Zapewnia również mechanizm replikacji, który jest peer-to-peer przy użyciu tylko metod HTTP.

Aby zapewnić tę replikację, CouchDB musi mieć pewne konwencje specyficzne dla CouchDB. Żadne z nich nie jest przeciwne REST. Zapewnia każdemu dokumentowi (który jest zasobem REST w bazie danych) numer wersji . Jest to część reprezentacji JSON tego dokumentu, ale znajduje się również w nagłówku HTTP ETag. Każda baza danych ma również numer sekwencyjny, który umożliwia śledzenie zmian w bazie danych jako całości.

W celu rozwiązania konfliktu po prostu zauważają, że dokument jest w konflikcie, i zachowują wersje będące w konflikcie, pozostawiając go programistom korzystającym z bazy danych w celu zapewnienia algorytmu rozwiązywania konfliktów.

Możesz użyć CouchDB jako interfejsu API REST, który zapewni synchronizację po wyjęciu z pudełka, lub przyjrzyj się, w jaki sposób zapewnia replikację, aby zapewnić punkt wyjścia do stworzenia własnego algorytmu.

David V.
źródło
Uwielbiam CouchDB i jego następcę CouchBase + SyncGateway. +1
Leonid Usow
-1

Możesz rozwiązać problem „czasowniki są złe” za pomocą prostej zmiany nazwy - użyj „aktualizacji” zamiast „synchronizacji”.

Proces synchronizacji faktycznie wysyła listę lokalnych aktualizacji wykonanych od ostatniej synchronizacji i odbiera listę aktualizacji wykonanych na serwerze w tym samym czasie.

Tom Clarkson
źródło