Jak zaprojektować zasób listy uporządkowanej w usłudze restful?

11

W kółko napotykałem ten sam problem i nie znalazłem rozwiązania, które moim zdaniem byłoby optymalne.

Powiedz w aplikacji, że masz uporządkowaną listę i pozwalasz użytkownikowi zmieniać tę kolejność, przeciągając i upuszczając lub coś w tym stylu. Chcesz, aby zmiany w kolejności były zachowane. Jak to modelujesz?

Jak zaprojektować usługę odpoczynku dla uporządkowanego zasobu listy?

W szczególności, jak powinienem zaprojektować listi itemmodel spokojnego zasobu? Najczęstszym projektem, jaki widziałem, jest itemistota posiadająca właściwość orderlub positionwłaściwość. Innym podejściem, które słyszałem, jest podwójnie połączona lista przedmiotów.

Jakie jest podejście, które nie zapisuje zbyt wiele w bazie danych i jest na ogół szybkie w aktualizowaniu i czytaniu dla klientów? Jak należy eksponować punkty końcowe?

Rico Kahler
źródło
Z ciekawości, dlaczego ważne jest, aby zwrócić specjalnie uporządkowaną listę?
Adam Wells,
Wydaje mi się, że nie chcę specjalnie zwracać uporządkowanego zasobu listy, ale po prostu utrzymuję kolejność / pozycję zasobu, który może być częścią rzeczywistego zasobu listy lub po prostu niejawnie częścią listy. Chcę powiedzieć, że użytkownik może zmienić kolejność czynności do wykonania na liście czynności do wykonania. Ale bez względu na to, co wciąż jest lista, w której kolejność ma znaczenie. Nie mogę znaleźć dobrego sposobu na zaprojektowanie tego
Rico Kahler,

Odpowiedzi:

14

Reprezentacja uporządkowanej listy jest jednym z trudnych problemów z relacyjnymi bazami danych. Dodanie właściwości pozycji do relacji lista-członkostwo jest najczęstszym sposobem, aby to zrobić, ponieważ można łatwo odzyskać uporządkowaną listę, dodając ORDER BY positiondo zapytania SQL oraz ponieważ można łatwo wstawiać elementy na środku listy, uśredniając wartość wartości poprzedniego i następnego elementu listy, przy założeniu, że pozycja jest liczbą zmiennoprzecinkową, a nie liczbą całkowitą.

Należy unikać korzystania z podwójnie połączonych list, ponieważ łatwo jest przypadkowo sprawić, że linki będą niespójne, a zamiast tego powstanie cykliczny wykres lub drzewo.

Jednak interfejsy API RESTful nie podlegają ograniczeniom relacyjnych baz danych. Możesz po prostu zrobić coś, co wydaje się naturalne, zamiast używać hacka, takiego jak właściwość position.

Jeśli masz tylko kilkaset elementów na liście, po prostu przenieś całą listę w żądaniu. Zakładając, że chcemy zmienić kolejność [1, 2, 3, 4]identyfikatorów członków listy, moglibyśmy

POST /url/of/the/list
Content-type: application/json
...

[1, 2, 4, 3]

Backend może następnie przetłumaczyć to na dowolną używaną technologię baz danych, ale użytkownik interfejsu API nie musi brać pod uwagę tych szczegółów.

Jeśli lista jest duża, a elementy zwykle są zamawiane osobno, możesz zezwolić na indeks w adresie URL:

GET /page/7

Jeśli lubisz HATEOAS, odpowiedź może zawierać linki poprzedni / następny, aby uprościć nawigację, jeśli zasób byłby zwykle tak konsumowany. Nie musi to jednak oznaczać, że baza danych zawiera również podwójnie połączoną listę.

Jeśli lista jest bardzo duża, możesz chcieć ujawnić ArrayListoperacje podobne do ujawnienia , takie jak insertlub push/ append. Mogę sobie wyobrazić takie połączenie

POST /url/of/the/list?at=1357;mode=insert
...

description of the item to insert

Jeśli zmiana kolejności jest częstym przypadkiem użycia, a zmiana kolejności powinna zostać wykonana natychmiast, możesz zaoferować odpowiedni punkt końcowy w interfejsie API:

POST /url/of/the/list/reorder-item?from=783;to=1357

Jeśli lista uporządkowana zostanie wyraźnie zatwierdzona, łatwiej będzie przenieść nowe zamówienie jako dokument JSON, patrz wyżej.

Teraz nie jest do końca prawdą, że możesz postrzegać swój interfejs API jako całkowicie odrębny od używanej technologii baz danych. Jednak najlepiej zachować zewnętrzny interfejs API tak wolny, jak to możliwe od szczegółów implementacji. Jeśli jakakolwiek zmiana kolejności dotyka około 30 wierszy tylko w celu zaktualizowania kolumny zamówienia z liczbą całkowitą, to nic wielkiego. Po prostu zrób najprostszą możliwą rzecz i zawsze aktualizuj całą listę. Jeśli Twoja skala wymaga bardziej zaawansowanego użycia bazy danych, wolisz przechwytywać to wyrafinowanie w backendie, gdzie łatwiej jest zachować spójność.

amon
źródło
1
Należy pamiętać, że podejście „zamień całą listę” może stać się problematyczne, jeśli wielu klientów wprowadza zmiany. Jeśli nie podejmiesz żadnych środków ostrożności, możesz w końcu nadpisać czyjeś zmiany („wygrywa ostatni zapis”).
oefe
Operacje podobne do list nie powinny mieć tego problemu, pod warunkiem, że parametry (at, from, to) są identyfikatorami, a nie indeksami list
oefe
2

Myślę, że opłacalność różnych podejść zależy w dużej mierze od wykorzystywanej bazy danych.

Podejście to sugeruje przeniesienie elementu w porządku:

POST / url / of / the / list / reorder-item? Od = 783; do = 1357

Może to podlegać warunkom wyścigowym, chyba że masz SERIALIZOWALNĄ transakcję. Domyślny poziom READ_COMMITTED w większości baz danych nie wyeliminuje warunków wyścigu!

Właściwie uważam, że w większości przypadków - o ile lista jest stosunkowo niewielka - wtedy podejście zastępowania pełnej listy ma mniej problemów z warunkami wyścigu i nie może uszkodzić danych. Jeśli zestaw przedmiotów zmienił się od czasu zgłoszenia żądania przez klienta, możesz zwrócić 409 (konflikt).

Cierpi na wygrane z ostatniego zapisu, ale dotyczy to dosłownie KAŻDEGO interfejsu API. Bez względu na to, jaki interfejs API wdrażasz, inny klient mógł go zaktualizować podczas przeglądania strony.

Charles Capps
źródło