Chciałbym trochę pomóc w rozwiązaniu dziwnego przypadku przy tworzeniu stronicowanego API.
Podobnie jak wiele interfejsów API, ten paginuje duże wyniki. Jeśli zapytasz / foos, otrzymasz 100 wyników (tj. Foo # 1-100) i link do strony / foos? = 2, które powinny zwrócić foo # 101-200.
Niestety, jeśli foo # 10 zostanie usunięte z zestawu danych, zanim konsument API wykona następne zapytanie, / foos? Page = 2 spowoduje przesunięcie o 100 i zwróci foos # 102-201.
Jest to problem dla klientów API, którzy próbują wyciągnąć wszystkie foos - nie otrzymają foo # 101.
Jaka jest najlepsza praktyka, aby sobie z tym poradzić? Chcielibyśmy, aby był tak lekki, jak to możliwe (tj. Unikanie sesji obsługi żądań API). Przykłady z innych interfejsów API byłyby bardzo mile widziane!
źródło
Odpowiedzi:
Nie jestem całkowicie pewien, w jaki sposób przetwarzane są twoje dane, więc to może, ale nie musi działać, ale czy zastanawiałeś się nad paginowaniem za pomocą pola sygnatury czasowej?
Kiedy pytasz / foos dostajesz 100 wyników. Twój interfejs API powinien następnie zwrócić coś takiego (przy założeniu JSON, ale jeśli potrzebuje XML, należy przestrzegać tych samych zasad):
Tylko uwaga, użycie tylko jednego znacznika czasu zależy od domyślnego „limitu” wyników. Możesz dodać wyraźny limit lub też użyć
until
właściwości.Znacznik czasu może być dynamicznie określany przy użyciu ostatniego elementu danych na liście. To wydaje się mniej więcej tak, jak Facebook paginuje w swoim API Graph (przewiń w dół, aby zobaczyć linki do stronicowania w formacie, który podałem powyżej).
Jednym z problemów może być dodanie elementu danych, ale na podstawie opisu wygląda na to, że zostaną dodane na końcu (jeśli nie, daj mi znać, a zobaczę, czy mogę to poprawić).
źródło
Masz kilka problemów.
Po pierwsze, masz przytoczony przykład.
Podobny problem występuje również w przypadku wstawienia wierszy, ale w tym przypadku użytkownik otrzymuje zduplikowane dane (prawdopodobnie łatwiejsze do zarządzania niż brakujące dane, ale nadal problem).
Jeśli nie tworzysz migawki oryginalnego zestawu danych, to tylko fakt.
Możesz poprosić użytkownika o wykonanie wyraźnej migawki:
Co powoduje:
Następnie możesz przeglądać strony przez cały dzień, ponieważ są one teraz statyczne. Może to być stosunkowo niewielka waga, ponieważ można po prostu uchwycić rzeczywiste klucze dokumentu, a nie całe wiersze.
Jeśli przypadek użycia polega na tym, że użytkownicy chcą (i potrzebują) wszystkich danych, możesz im je po prostu przekazać:
i po prostu wyślij cały zestaw.
źródło
Jeśli masz paginację, sortujesz dane według klucza. Dlaczego nie pozwolić klientom API dołączyć klucz ostatniego elementu poprzednio zwróconej kolekcji do adresu URL i dodać
WHERE
klauzulę do zapytania SQL (lub coś równoważnego, jeśli nie używasz SQL), aby zwracał tylko te elementy, dla których klucz jest większy niż ta wartość?źródło
Mogą istnieć dwa podejścia w zależności od logiki serwera.
Podejście 1: Gdy serwer nie jest wystarczająco inteligentny, aby obsłużyć stany obiektów.
Możesz wysłać wszystkie unikalne identyfikatory zapisywane w pamięci podręcznej na serwer, na przykład [„id1”, „id2”, „id3”, „id4”, „id5”, „id6”, „id7”, „id8”, „id9”, „id10”] i parametr boolowski, aby dowiedzieć się, czy żądasz nowych rekordów (ściągnij, aby odświeżyć), czy starych rekordów (załaduj więcej).
Twój serwer powinien być odpowiedzialny za zwracanie nowych rekordów (ładowanie większej liczby rekordów lub nowych rekordów poprzez ściąganie w celu odświeżenia), a także identyfikatorów usuniętych rekordów z [„id1”, „id2”, „id3”, „id4”, „id5”, „ id6 ”,„ id7 ”,„ id8 ”,„ id9 ”,„ id10 ”].
Przykład: - Jeśli chcesz załadować więcej, Twoje żądanie powinno wyglądać mniej więcej tak:
Załóżmy teraz, że żądasz starych rekordów (załaduj więcej) i załóżmy, że rekord „id2” został przez kogoś zaktualizowany, a rekordy „id5” i „id8” zostały usunięte z serwera, a następnie odpowiedź serwera powinna wyglądać mniej więcej tak:
Ale w tym przypadku, jeśli masz wiele lokalnych zapisanych w pamięci podręcznej danych, przypuśćmy, że 500, to łańcuch żądania będzie zbyt długi:
Podejście 2: Gdy serwer jest wystarczająco inteligentny, aby obsłużyć stany obiektów zgodnie z datą.
Możesz wysłać identyfikator pierwszego rekordu oraz ostatniego rekordu i czasu epoki poprzedniego żądania. W ten sposób Twoje żądanie jest zawsze małe, nawet jeśli masz dużą liczbę zapisanych w pamięci podręcznej rekordów
Przykład: - Jeśli chcesz załadować więcej, Twoje żądanie powinno wyglądać mniej więcej tak:
Twój serwer jest odpowiedzialny za zwrócenie identyfikatora usuniętych rekordów, które są usuwane po ostatnim czasie żądania, a także zwrócenie zaktualizowanego rekordu po czasie ostatniego żądania między „id1” a „id10”.
Pociągnij by odświeżyć:-
Załaduj więcej
źródło
Znalezienie najlepszych praktyk może być trudne, ponieważ większość systemów z interfejsami API nie obsługuje tego scenariusza, ponieważ jest to skrajna przewaga lub zazwyczaj nie usuwają rekordów (Facebook, Twitter). Facebook faktycznie twierdzi, że każda „strona” może nie mieć żądanej liczby wyników z powodu filtrowania wykonanego po paginacji. https://developers.facebook.com/blog/post/478/
Jeśli naprawdę potrzebujesz zmieścić tę skrzynkę, musisz „pamiętać”, gdzie ją przerwałeś. Sugestia jandjorgensen jest na miejscu, ale użyłbym pola, które gwarantowałoby unikalność, jak klucz podstawowy. Może być konieczne użycie więcej niż jednego pola.
Po przepływie Facebooka możesz (i powinieneś) buforować strony, które już zamówiłeś, i po prostu zwróć te z filtrowanymi usuniętymi wierszami, jeśli zażądają strony, o którą już poprosiły.
źródło
Podział na strony jest na ogół operacją „użytkownika” i aby zapobiec przeciążeniu zarówno na komputerach, jak i ludzkim mózgu, zwykle podaje się podzbiór. Jednak zamiast myśleć, że nie otrzymamy całej listy, lepiej zapytać, czy to ma znaczenie?
Jeśli potrzebny jest dokładny widok przewijania na żywo, interfejsy API REST, które mają charakter żądania / odpowiedzi, nie są odpowiednie do tego celu. W tym celu należy rozważyć zdarzenia wysyłane przez serwer WebSockets lub HTML5, aby poinformować interfejs użytkownika o zmianach.
Teraz, jeśli istnieje potrzeba uzyskania migawki danych, po prostu zapewniłbym wywołanie API, które zapewnia wszystkie dane w jednym żądaniu bez podziału na strony. Pamiętaj, że potrzebujesz dużego strumienia danych wyjściowych bez tymczasowego ładowania go do pamięci.
W moim przypadku domyślnie wyznaczam niektóre wywołania API, aby umożliwić uzyskanie całej informacji (przede wszystkim danych tabeli referencyjnej). Możesz także zabezpieczyć te interfejsy API, aby nie zaszkodziły Twojemu systemowi.
źródło
Opcja A: Paginacja zestawu kluczy ze znacznikiem czasu
Aby uniknąć wspomnianych wad stronicowania offsetowego, można zastosować paginację opartą na zestawie kluczy. Zwykle jednostki mają znacznik czasu, który określa czas ich utworzenia lub modyfikacji. Tego znacznika czasu można użyć do podziału na strony: wystarczy przekazać znacznik czasu ostatniego elementu jako parametr zapytania dla następnego żądania. Serwer z kolei używa znacznika czasu jako kryterium filtru (np.
WHERE modificationDate >= receivedTimestampParameter
)W ten sposób nie przegapisz żadnego elementu. To podejście powinno być wystarczające dla wielu przypadków użycia. Pamiętaj jednak, że:
Możesz zmniejszyć te wady, zwiększając rozmiar strony i stosując znaczniki czasu z milisekundową precyzją.
Opcja B: Rozszerzone paginowanie zestawu kluczy z tokenem kontynuacji
Aby poradzić sobie ze wspomnianymi wadami zwykłego podziału na strony zestawu kluczy, możesz dodać przesunięcie do znacznika czasu i użyć tak zwanego „tokena kontynuacji” lub „kursora”. Przesunięcie jest pozycją elementu względem pierwszego elementu z tym samym znacznikiem czasu. Zwykle token ma format podobny do
Timestamp_Offset
. W odpowiedzi jest przekazywany klientowi i może zostać przesłany z powrotem na serwer w celu pobrania następnej strony.Token „1512757072_2” wskazuje na ostatni element strony i stwierdza „klient już dostał drugi element ze znacznikiem czasu 1512757072”. W ten sposób serwer wie, gdzie kontynuować.
Pamiętaj, że musisz obsługiwać przypadki, w których elementy zostały zmienione między dwoma żądaniami. Zazwyczaj odbywa się to poprzez dodanie sumy kontrolnej do tokena. Ta suma kontrolna jest obliczana na podstawie identyfikatorów wszystkich elementów z tym znacznikiem czasu. Więc skończyć z tokena formacie jak poniżej:
Timestamp_Offset_Checksum
.Aby uzyskać więcej informacji o tym podejściu, sprawdź post na blogu „ Paginacja interfejsu API sieci Web za pomocą tokenów kontynuacji ”. Wadą tego podejścia jest trudna implementacja, ponieważ istnieje wiele przypadków narożnych, które należy wziąć pod uwagę. Właśnie dlatego biblioteki takie jak token kontynuacji mogą być przydatne (jeśli używasz języka Java / JVM). Uwaga: Jestem autorem postu i współautorem biblioteki.
źródło
Myślę, że obecnie Twój interfejs API działa tak, jak powinien. Pierwsze 100 rekordów na stronie w ogólnej kolejności obsługiwanych obiektów. Twoje wyjaśnienie mówi, że używasz jakiegoś identyfikatora zamówienia, aby zdefiniować kolejność obiektów do stronicowania.
Teraz, jeśli chcesz, aby strona 2 zawsze zaczynała się od 101, a kończyła na 200, musisz wprowadzić liczbę wpisów na stronie jako zmienną, ponieważ podlegają one usunięciu.
Powinieneś zrobić coś takiego jak poniższy pseudokod:
źródło
Aby dodać do tej odpowiedzi Kamilk: https://www.stackoverflow.com/a/13905589
źródło
Długo się nad tym zastanawiałem i ostatecznie znalazłem rozwiązanie, które opiszę poniżej. To dość duży wzrost złożoności, ale jeśli to zrobisz, skończysz z tym, czego naprawdę szukasz, co jest deterministycznymi wynikami dla przyszłych żądań.
Twój przykład usuwania elementu to tylko wierzchołek góry lodowej. Co jeśli filtrujesz według,
color=blue
ale ktoś zmienia kolory elementów między żądaniami? Rzetelne pobranie wszystkich elementów w sposób stronicowany jest niemożliwe ... chyba że ... wprowadzimy historię zmian .Wdrożyłem to i jest to właściwie mniej trudne niż się spodziewałem. Oto co zrobiłem:
changelogs
z kolumną z identyfikatorem automatycznego przyrostuid
pole, ale nie jest to klucz podstawowychangeId
pole, które jest zarówno kluczem podstawowym, jak i kluczem obcym do dzienników zmian.changelogs
, pobiera identyfikator i przypisuje go do nowej wersji encji, którą następnie wstawia do bazy danychchangeId
stanowi unikatową migawkę danych bazowych w momencie, gdy zmiana została utworzona.changeId
w nich na zawsze. Wyniki nigdy nie wygasną, ponieważ nigdy się nie zmienią.źródło
Inną opcją paginacji w interfejsach API RESTFul jest użycie wprowadzonego tutaj nagłówka łącza . Na przykład Github używa go w następujący sposób:
Możliwe wartości
rel
to: first, last, next, previous . Ale używającLink
nagłówka, może nie być możliwe określenie total_count (całkowita liczba elementów).źródło