Ładunek odpowiedzi paginacji z interfejsu API RESTful


84

Chcę obsługiwać paginację w moim RESTful API.

Moja metoda API powinna zwrócić listę produktów w formacie JSON za pośrednictwem /products/index. Jednak potencjalnie są tysiące produktów i chcę je przejrzeć, więc moja prośba powinna wyglądać mniej więcej tak:

/products/index?page_number=5&page_size=20

Ale jak musi wyglądać moja odpowiedź JSON? Czy konsumenci API zazwyczaj oczekują w odpowiedzi metadanych dotyczących stronicowania? A może potrzebny jest tylko szereg produktów? Czemu?

Wygląda na to, że API Twittera zawiera metadane: https://dev.twitter.com/docs/api/1/get/lists/members (zobacz przykładowe żądanie).

Z metadanymi:

{
  "page_number": 5,
  "page_size": 20,
  "total_record_count": 521,
  "records": [
    {
      "id": 1,
      "name": "Widget #1"
    },
    {
      "id": 2,
      "name": "Widget #2"
    },
    {
      "id": 3,
      "name": "Widget #3"
    }
  ]
}

Tylko tablica produktów (bez metadanych):

[
  {
    "id": 1,
    "name": "Widget #1"
  },
  {
    "id": 2,
    "name": "Widget #2"
  },
  {
    "id": 3,
    "name": "Widget #3"
  }
]

Odpowiedzi:


110

ReSTful API są używane głównie przez inne systemy, dlatego dane stronicowania umieszczam w nagłówkach odpowiedzi. Jednak niektórzy konsumenci API mogą nie mieć bezpośredniego dostępu do nagłówków odpowiedzi lub mogą budować UX za pośrednictwem interfejsu API, więc zapewnienie sposobu pobierania (na żądanie) metadanych w odpowiedzi JSON jest plusem.

Uważam, że Twoja implementacja powinna domyślnie zawierać metadane do odczytu maszynowego oraz metadane czytelne dla człowieka na żądanie. Czytelne dla człowieka metadane mogą być zwracane z każdym żądaniem, jeśli chcesz, a najlepiej na żądanie za pośrednictwem parametru zapytania, takiego jak include=metadatalub include_metadata=true.

W twoim konkretnym scenariuszu dołączyłbym identyfikator URI dla każdego produktu z rekordem. Ułatwia to konsumentowi API tworzenie linków do poszczególnych produktów. Ustawiłbym również rozsądne oczekiwania, jeśli chodzi o limity moich żądań stronicowania. Wdrażanie i dokumentowanie domyślnych ustawień rozmiaru strony jest dopuszczalną praktyką. Na przykład interfejs API GitHub ustawia domyślny rozmiar strony na 30 rekordów z maksymalnie 100, a także ustawia limit szybkości dotyczący liczby zapytań interfejsu API. Jeśli Twój interfejs API ma domyślny rozmiar strony, ciąg zapytania może po prostu określać indeks strony.

W scenariuszu czytelnym dla człowieka, podczas nawigowania do /products?page=5&per_page=20&include=metadata, odpowiedzią może być:

{
  "_metadata": 
  {
      "page": 5,
      "per_page": 20,
      "page_count": 20,
      "total_count": 521,
      "Links": [
        {"self": "/products?page=5&per_page=20"},
        {"first": "/products?page=0&per_page=20"},
        {"previous": "/products?page=4&per_page=20"},
        {"next": "/products?page=6&per_page=20"},
        {"last": "/products?page=26&per_page=20"},
      ]
  },
  "records": [
    {
      "id": 1,
      "name": "Widget #1",
      "uri": "/products/1"
    },
    {
      "id": 2,
      "name": "Widget #2",
      "uri": "/products/2"
    },
    {
      "id": 3,
      "name": "Widget #3",
      "uri": "/products/3"
    }
  ]
}

W przypadku metadanych do odczytu maszynowego dodałbym do odpowiedzi nagłówki linków :

Link: </products?page=5&perPage=20>;rel=self,</products?page=0&perPage=20>;rel=first,</products?page=4&perPage=20>;rel=previous,</products?page=6&perPage=20>;rel=next,</products?page=26&perPage=20>;rel=last

(wartość nagłówka linku powinna mieć kod urlencoded)

... i ewentualnie niestandardowy total-countnagłówek odpowiedzi, jeśli wybierzesz:

total-count: 521

Inne dane stronicowania ujawnione w metadanych zorientowanych na człowieka mogą być zbędne w przypadku metadanych zorientowanych na komputer, ponieważ nagłówki linków informują mnie, na której stronie jestem i jaka jest liczba na stronie, a także mogę szybko pobrać liczbę rekordów w tablicy . Dlatego prawdopodobnie utworzyłbym tylko nagłówek dla całkowitej liczby. Zawsze możesz później zmienić zdanie i dodać więcej metadanych.

Na marginesie, możesz zauważyć, że usunąłem /indexTwój identyfikator URI. Ogólnie przyjętą konwencją jest udostępnianie kolekcji w punkcie końcowym ReST. Mając /indexna muddies końcowych, które się nieznacznie.

To tylko kilka rzeczy, które lubię mieć podczas używania / tworzenia API. Mam nadzieję, że to pomoże!


per_page nie przestrzega konwencji page_size
Alexandros Spyropoulos

1
"page_count": 20i {"last": "/products?page=26&per_page=20"}?
Jérôme

1
co by się stało, gdyby liczba produktów nagle wzrosła podczas pobierania wszystkich rekordów od strony 1 do strony x?
MeV

3
@MeV to samo, co dzieje się w każdym scenariuszu paginacji opartym na kursorach: suma wzrośnie, a liczba stron może wzrosnąć, jeśli ostatnia strona jest pełna, nic więcej, nic mniej. Jest to bardzo powszechny scenariusz w każdej aplikacji korzystającej z tego typu paginacji. Zależy to od używanego sortowania, czy nowy produkt pojawi się na pierwszej czy ostatniej stronie.
Pablo Pazos

2
„ReSTful API są używane głównie przez inne systemy, dlatego umieszczam dane stronicowania w nagłówkach odpowiedzi” To tak, jakby powiedzieć, że na zewnątrz jest słonecznie, dlatego mam na sobie niebieską koszulę. Dlaczego myślisz, że ludzie nie mogą odczytać nagłówków?
lepszy oliver

29

Jako ktoś, kto napisał kilka bibliotek do korzystania z usług REST, pozwól, że przedstawię ci perspektywę klienta, dlaczego uważam, że zawijanie wyniku w metadane jest właściwą drogą:

  • Skąd klient może wiedzieć, że nie otrzymał jeszcze wszystkiego, co jest, bez całkowitej liczby i powinien kontynuować stronicowanie zestawu wyników? W interfejsie użytkownika, który nie wykonywał funkcji wyszukiwania w przód na następną stronę, w najgorszym przypadku może to być reprezentowane jako łącze Dalej / Więcej, które w rzeczywistości nie pobierało więcej danych.
  • Dołączenie metadanych do odpowiedzi pozwala klientowi śledzić mniejszy stan. Teraz nie muszę dopasowywać mojego żądania REST do odpowiedzi, ponieważ odpowiedź zawiera metadane niezbędne do rekonstrukcji stanu żądania (w tym przypadku kursor do zestawu danych).
  • Jeśli stan jest częścią odpowiedzi, mogę jednocześnie wykonywać wiele żądań do tego samego zestawu danych i mogę obsługiwać żądania w dowolnej kolejności, w jakiej się pojawiają, która niekoniecznie jest taka, w jakiej je złożyłem.

I sugestia: podobnie jak w przypadku API Twittera , powinieneś zamienić page_number na prosty indeks / kursor. Przyczyną jest to, że interfejs API umożliwia klientowi ustawienie rozmiaru strony na żądanie. Czy zwrócony numer_strony to liczba stron, o które klient zażądał do tej pory, czy też numer strony podany jako ostatnio używany rozmiar_strony (prawie na pewno późniejszy, ale dlaczego nie uniknąć takiej niejednoznaczności w ogóle)?


10
Do pierwszego punktu, czy dobrym rozwiązaniem byłoby pominięcie linku rel = next, gdyby nie było następnej strony? Po drugie, informacje są nadal dostępne w odpowiedzi udzielonej klientowi, po prostu nie znajdują się w treści odpowiedzi, ale w nagłówkach. +1 do ostatniego akapitu.
Kyle Hayes,

Na koniec chcę odpowiedzieć na twoją sugestię. Paginacja kursorem to nie to samo, co paginacja strony / przesunięcia. Obie mają swoje wady i zalety oraz względy wydajności.
kjonsson

19

Poleciłbym dodanie nagłówków dla tego samego. Przenoszenie metadanych do nagłówków pomaga w pozbyciu się kopert jak result, dataalbo recordsi ciało odpowiedź zawiera tylko dane, których potrzebujemy. Możesz użyć nagłówka Link, jeśli generujesz również linki do paginacji.

    HTTP/1.1 200
    Pagination-Count: 100
    Pagination-Page: 5
    Pagination-Limit: 20
    Content-Type: application/json

    [
      {
        "id": 10,
        "name": "shirt",
        "color": "red",
        "price": "$23"
      },
      {
        "id": 11,
        "name": "shirt",
        "color": "blue",
        "price": "$25"
      }
    ]

Po szczegóły patrz:

https://github.com/adnan-kamili/rest-api-response-format

Dla pliku swagger:

https://github.com/adnan-kamili/swagger-response-template


3
Zgodnie z RFC-6648 prefiks „X-” powinien zostać usunięty z kluczy metadanych.
Ray

1
@RayKoopa dzięki, zaktualizowałem stronę github, ale zapomniałem zaktualizować tę odpowiedź.
adnan kamili

0

po prostu dodaj nową właściwość interfejsu API zaplecza do treści odpowiedzi. z przykładu .net core:

[Authorize]
[HttpGet]
public async Task<IActionResult> GetUsers([FromQuery]UserParams userParams)
{
  var users = await _repo.GetUsers(userParams);
  var usersToReturn = _mapper.Map<IEnumerable<UserForListDto>>(users);


  // create new object and add into it total count param etc
  var UsersListResult = new
  {
    usersToReturn,
    currentPage = users.CurrentPage,
    pageSize = users.PageSize,
    totalCount = users.TotalCount,
    totalPages = users.TotalPages
  };

  return Ok(UsersListResult);
}

W odpowiedzi ciała wygląda to tak

{
"usersToReturn": [
    {
        "userId": 1,
        "username": "nancycaldwell@conjurica.com",
        "firstName": "Joann",
        "lastName": "Wilson",
        "city": "Armstrong",
        "phoneNumber": "+1 (893) 515-2172"
    },
    {
        "userId": 2,
        "username": "zelmasheppard@conjurica.com",
        "firstName": "Booth",
        "lastName": "Drake",
        "city": "Franks",
        "phoneNumber": "+1 (800) 493-2168"
    }
],
// metadata to pars in client side
"currentPage": 1,
"pageSize": 2,
"totalCount": 87,
"totalPages": 44

}


-3

generalnie robię w prosty sposób, cokolwiek, tworzę punkt końcowy restAPI, na przykład "localhost / api / metoda /: lastIdObtained /: countDateToReturn" z tymi parametrami, możesz to zrobić prostym żądaniem. w serwisie np. .netto

jsonData function(lastIdObtained,countDatetoReturn){
'... write your code as you wish..'
and into select query make a filter
select top countDatetoreturn tt.id,tt.desc
 from tbANyThing tt
where id > lastIdObtained
order by id
}

W Ionic, gdy przewijam od dołu do góry, mijam wartość zero, gdy dostaję odpowiedź, ustawiam wartość ostatniego uzyskanego id, a gdy przesuwam się od góry do dołu, mijam ostatni otrzymany identyfikator rejestracji

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.