Nutzdaten der Paginierungsantwort von einer RESTful-API

83

Ich möchte die Paginierung in meiner RESTful-API unterstützen.

Meine API-Methode sollte eine JSON-Produktliste über zurückgeben /products/index. Es gibt jedoch möglicherweise Tausende von Produkten, und ich möchte sie durchblättern, daher sollte meine Anfrage ungefähr so ​​aussehen:

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

Aber wie muss meine JSON-Antwort aussehen? Würden API-Konsumenten normalerweise Paginierungs-Metadaten in der Antwort erwarten? Oder ist nur eine Reihe von Produkten notwendig? Warum?

Die API von Twitter enthält anscheinend Metadaten: https://dev.twitter.com/docs/api/1/get/lists/members (siehe Beispielanforderung).

Mit Metadaten:

{
  "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"
    }
  ]
}

Nur eine Reihe von Produkten (keine Metadaten):

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

Antworten:

110

ReSTful-APIs werden hauptsächlich von anderen Systemen verwendet, weshalb ich Paging-Daten in die Antwortheader eingefügt habe. Einige API-Konsumenten haben jedoch möglicherweise keinen direkten Zugriff auf die Antwortheader oder erstellen möglicherweise eine UX über Ihre API. Daher ist es von Vorteil, eine Möglichkeit zum Abrufen (bei Bedarf) der Metadaten in der JSON-Antwort bereitzustellen.

Ich bin der Meinung, dass Ihre Implementierung standardmäßig maschinenlesbare Metadaten und auf Anfrage lesbare Metadaten enthalten sollte. Die für Menschen lesbaren Metadaten können bei jeder Anforderung zurückgegeben werden, wenn Sie möchten, oder vorzugsweise bei Bedarf über einen Abfrageparameter, zinclude=metadata oder include_metadata=true.

In Ihrem speziellen Szenario würde ich den URI für jedes Produkt in den Datensatz aufnehmen. Dies erleichtert dem API-Konsumenten das Erstellen von Links zu den einzelnen Produkten. Ich würde auch einige vernünftige Erwartungen gemäß den Grenzen meiner Paging-Anfragen setzen. Das Implementieren und Dokumentieren von Standardeinstellungen für die Seitengröße ist eine akzeptable Vorgehensweise. Zum Beispiel die API von GitHub legt die die Standardseitengröße auf 30 Datensätze mit maximal 100 fest und legt ein Ratenlimit für die Häufigkeit fest, mit der Sie die API abfragen können. Wenn Ihre API eine Standardseitengröße hat, kann die Abfragezeichenfolge nur den Seitenindex angeben.

In dem für Menschen lesbaren Szenario /products?page=5&per_page=20&include=metadatakönnte die Antwort beim Navigieren zu:

{
  "_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"
    }
  ]
}

Für maschinenlesbare Metadaten würde ich der Antwort Link-Header hinzufügen :

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

(Der Link-Header-Wert sollte urlencodiert sein.)

... und möglicherweise einen benutzerdefinierten total-countAntwortheader, wenn Sie dies wünschen:

total-count: 521

Die anderen Paging-Daten, die in den menschenzentrierten Metadaten angezeigt werden, sind für maschinenzentrierte Metadaten möglicherweise überflüssig, da die Link-Header mir mitteilen, auf welcher Seite ich mich befinde und wie viele Seiten pro Seite vorhanden sind, und ich die Anzahl der Datensätze im Array schnell abrufen kann . Daher würde ich wahrscheinlich nur einen Header für die Gesamtzahl erstellen. Sie können Ihre Meinung später jederzeit ändern und weitere Metadaten hinzufügen.

Abgesehen davon können Sie feststellen, dass ich /indexvon Ihrer URI entfernt wurde. Eine allgemein akzeptierte Konvention besteht darin, dass Ihr ReST-Endpunkt Sammlungen verfügbar macht. Am /indexEnde trübt das etwas.

Dies sind nur einige Dinge, die ich beim Konsumieren / Erstellen einer API gerne habe. Hoffentlich hilft das!

Codeprogression
quelle
per_page folgt nicht der Konvention page_size
Alexandros Spyropoulos
1
"page_count": 20und {"last": "/products?page=26&per_page=20"}?
Jérôme
1
Was würde passieren, wenn sich die Anzahl der Produkte plötzlich erhöht, während alle Datensätze von Seite 1 auf Seite x abgerufen werden?
MeV
3
@MeV das gleiche wie in jedem Cursor-basierten Paginierungsszenario: Die Gesamtzahl wird erhöht, und die Seitenzahl kann sich erhöhen, wenn die letzte Seite voll ist, nicht mehr und nicht weniger. Es ist ein sehr häufiges Szenario in jeder App, die diese Art der Paginierung verwendet. Es hängt von der verwendeten Sortierung ab, ob das neue Produkt auf der ersten oder letzten Seite angezeigt wird.
Pablo Pazos
2
"ReSTful-APIs werden hauptsächlich von anderen Systemen verwendet, weshalb ich Paging-Daten in die Antwortheader einfüge." Das heißt, draußen ist es sonnig, weshalb ich ein blaues Hemd trage. Was lässt Sie denken, dass Überschriften von Menschen nicht gelesen werden können?
Ein besserer Oliver
29

Als jemand, der mehrere Bibliotheken für die Nutzung von REST-Diensten geschrieben hat, möchte ich Ihnen die Client-Perspektive geben, warum ich denke, dass das Einschließen des Ergebnisses in Metadaten der richtige Weg ist:

  • Wie kann der Client ohne die Gesamtzahl wissen, dass er noch nicht alles erhalten hat, was vorhanden ist, und sollte die Ergebnismenge weiter durchblättern? In einer Benutzeroberfläche, die keine Leistung erbracht hat, können Sie im schlimmsten Fall als Next / More-Link dargestellt werden, der keine weiteren Daten abruft.
  • Durch das Einbeziehen von Metadaten in die Antwort kann der Client weniger Status verfolgen. Jetzt muss ich meine REST-Anforderung nicht mehr mit der Antwort abgleichen, da die Antwort die Metadaten enthält, die zur Rekonstruktion des Anforderungsstatus erforderlich sind (in diesem Fall den Cursor in das Dataset).
  • Wenn der Status Teil der Antwort ist, kann ich mehrere Anforderungen gleichzeitig in demselben Dataset ausführen und die Anforderungen in beliebiger Reihenfolge bearbeiten, in der sie nicht unbedingt in der Reihenfolge eingehen, in der ich die Anforderungen gestellt habe.

Und ein Vorschlag: Wie bei der Twitter-API sollten Sie die Seitennummer durch einen geraden Index / Cursor ersetzen. Der Grund dafür ist, dass der Client mit der API die Seitengröße pro Anforderung festlegen kann. Ist die zurückgegebene Seitennummer die Anzahl der Seiten, die der Client bisher angefordert hat, oder die Anzahl der Seiten, die die zuletzt verwendete Seitengröße angegeben haben (mit ziemlicher Sicherheit die spätere, aber warum nicht eine solche Mehrdeutigkeit ganz vermeiden)?

Majix
quelle
10
Wäre es für Ihre erste Aufzählung eine geeignete Lösung, einen rel = next-Link wegzulassen, wenn es keine nächste Seite gäbe? Bei Ihrem zweiten Aufzählungszeichen sind die Informationen weiterhin in der Antwort an den Client verfügbar. Sie befinden sich nur nicht im Hauptteil der Antwort, sondern in den Kopfzeilen. +1 auf Ihrem letzten Absatz.
Kyle Hayes
19

Ich würde empfehlen, Header für das gleiche hinzuzufügen. Das Verschieben von Metadaten in Header hilft dabei, Umschläge wie oder zu entfernen result, und der Antworttext enthält nur die Daten, die wir benötigen. Sie können den Link- Header verwenden, wenn Sie auch Paginierungslinks generieren.datarecords

    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"
      }
    ]

Einzelheiten finden Sie unter:

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

Für Swagger-Datei:

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

Adnan Kamili
quelle
3
Gemäß RFC-6648 sollte das Präfix "X-" in den Metadatenschlüsseln entfernt werden.
Ray
1
@ RayKoopa danke, ich hatte die Github-Seite aktualisiert, aber vergessen, diese Antwort zu aktualisieren.
Adnan Kamili
0

Fügen Sie einfach in Ihrer Backend-API neue Eigenschaften zum Antworttext hinzu. aus Beispiel .net Kern:

[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);
}

In der Körperreaktion sieht es so aus

{
"usersToReturn": [
    {
        "userId": 1,
        "username": "[email protected]",
        "firstName": "Joann",
        "lastName": "Wilson",
        "city": "Armstrong",
        "phoneNumber": "+1 (893) 515-2172"
    },
    {
        "userId": 2,
        "username": "[email protected]",
        "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

}}

David Akobiya
quelle
-3

Im Allgemeinen mache ich auf einfache Weise, was auch immer, ich erstelle einen restAPI-Endpunkt, zum Beispiel "localhost / api / method /: lastIdObtained /: countDateToReturn" mit diesen Parametern, Sie können es eine einfache Anfrage machen. im Service, z. .Netz

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
}

Wenn ich in Ionic von unten nach oben scrolle, übergebe ich den Nullwert, wenn ich die Antwort erhalte, setze ich den Wert der zuletzt erhaltenen ID und wenn ich von oben nach unten schiebe, übergebe ich die letzte Registrierungs-ID, die ich erhalten habe

Deivison Santos
quelle