RESTful API의 페이지 매김 응답 페이로드


84

RESTful API에서 페이지 매김을 지원하고 싶습니다.

내 API 메서드는 .NET을 통해 제품의 JSON 목록을 반환해야합니다 /products/index. 그러나 잠재적으로 수천 개의 제품이 있으며 페이지를 넘기고 싶으므로 내 요청은 다음과 같아야합니다.

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

하지만 내 JSON 응답은 어떤 모습이어야합니까? API 소비자는 일반적으로 응답에서 페이지 매김 메타 데이터를 기대합니까? 아니면 다양한 제품 만 필요합니까? 왜?

Twitter의 API에 메타 데이터가 포함되어있는 것 같습니다 : https://dev.twitter.com/docs/api/1/get/lists/members (예제 요청 참조).

메타 데이터 사용 :

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

제품 배열 (메타 데이터 없음) :

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

답변:


111

ReSTful API는 주로 다른 시스템에서 사용하기 때문에 응답 헤더에 페이징 데이터를 넣습니다. 그러나 일부 API 소비자는 응답 헤더에 직접 액세스 할 수 없거나 API를 통해 UX를 구축 할 수 있으므로 요청시 JSON 응답에서 메타 데이터를 검색하는 방법을 제공하는 것이 장점입니다.

구현에는 기계가 읽을 수있는 메타 데이터가 기본으로 포함되어야하고 요청시 사람이 읽을 수있는 메타 데이터가 포함되어야한다고 생각합니다. 사람이 읽을 수있는 메타 데이터는 원하는 경우 모든 요청과 함께 반환 될 수 있으며, 가급적이면 include=metadata또는 같은 쿼리 매개 변수를 통해 필요에 따라 반환 될 수 있습니다.include_metadata=true .

특정 시나리오에서는 레코드와 함께 각 제품의 URI를 포함합니다. 이렇게하면 API 소비자가 개별 제품에 대한 링크를 쉽게 만들 수 있습니다. 또한 페이징 요청의 한계에 따라 합리적인 기대치를 설정했습니다. 페이지 크기에 대한 기본 설정을 구현하고 문서화하는 것이 허용되는 방법입니다. 예를 들어 GitHub의 API 는 기본 페이지 크기를 최대 100 개의 레코드 30 개로 설정하고 API를 쿼리 할 수있는 횟수에 대한 속도 제한을 설정합니다. API에 기본 페이지 크기가있는 경우 쿼리 문자열은 페이지 색인 만 지정할 수 있습니다.

사람이 읽을 수있는 시나리오에서로 이동할 때 /products?page=5&per_page=20&include=metadata응답은 다음과 같을 수 있습니다.

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

컴퓨터에서 읽을 수있는 메타 데이터의 경우 응답에 링크 헤더 를 추가 합니다.

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

(링크 헤더 값은 urlencoded 여야합니다)

... 그리고 total-count원하는 경우 사용자 지정 응답 헤더가있을 수 있습니다.

total-count: 521

링크 헤더가 내가있는 페이지와 페이지 당 수를 알려주고 배열의 레코드 수를 빠르게 검색 할 수 있기 때문에 인간 중심 메타 데이터에 공개 된 다른 페이징 데이터는 기계 중심 메타 데이터에 불필요 할 수 있습니다. . 따라서 총 개수에 대한 헤더 만 만들 것입니다. 나중에 언제든지 마음을 바꾸고 더 많은 메타 데이터를 추가 할 수 있습니다.

제쳐두고, 내가 /index당신의 URI에서 제거되었음을 알 수 있습니다 . 일반적으로 허용되는 규칙은 ReST 엔드 포인트가 컬렉션을 노출하도록하는 것입니다. 데 /index그보다 약간 끝 muddies에.

이것들은 API를 사용 / 생성 할 때 제가 좋아하는 몇 가지 것입니다. 도움이 되었기를 바랍니다.


per_page는 컨벤션 PAGE_SIZE 따르지 않는
알렉산드로스 Spyropoulos

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

1
페이지 1에서 페이지 x로 모든 레코드를 가져 오는 동안 제품 수가 갑자기 증가하면 어떻게됩니까?
MeV

3
@MeV는 커서 기반 페이지 매김 시나리오에서 발생하는 것과 동일합니다. 합계가 증가하고 마지막 페이지가 가득 차면 페이지 수가 증가 할 수 있습니다. 이러한 유형의 페이지 매김을 사용하는 모든 앱에서 매우 일반적인 시나리오입니다. 새 제품이 첫 페이지 또는 마지막 페이지에 나타나는 경우 사용되는 정렬에 따라 다릅니다.
Pablo Pazos 2016

2
"ReSTful API는 주로 다른 시스템에서 사용됩니다. 이것이 제가 응답 헤더에 페이징 데이터를 넣는 이유입니다." 이것은 외부 가 맑다 고 말하는 것과 같습니다. 그래서 파란색 셔츠를 입고 있습니다. 사람이 헤더를 읽을 수 없다고 생각하는 이유는 무엇입니까?
더 나은 올리버

29

REST 서비스를 사용하기 위해 여러 라이브러리를 작성한 사람으로서 메타 데이터로 결과를 래핑하는 것이 왜 좋은지에 대한 클라이언트 관점을 제공하겠습니다.

  • 총 개수가없는 경우 클라이언트는 아직 모든 항목을받지 못했고 결과 집합을 통해 계속 페이징해야한다는 것을 어떻게 알 수 있습니까? 다음 페이지로 미리보기를 수행하지 않은 UI에서 최악의 경우 실제로 더 이상 데이터를 가져 오지 않은 다음 / 추가 링크로 표시 될 수 있습니다.
  • 응답에 메타 데이터를 포함하면 클라이언트가 더 적은 상태를 추적 할 수 있습니다. 이제 응답에 요청 상태를 재구성하는 데 필요한 메타 데이터가 포함되어 있으므로 REST 요청을 응답과 일치시킬 필요가 없습니다 (이 경우 데이터 세트에 대한 커서).
  • 상태가 응답의 일부인 경우 동일한 데이터 세트에 대해 여러 요청을 동시에 수행 할 수 있으며 요청을 수행 한 순서가 아닐 수도있는 모든 순서로 요청을 처리 할 수 ​​있습니다.

그리고 제안 : Twitter API 처럼 page_number를 직선 인덱스 / 커서로 바꿔야합니다. 그 이유는 API를 통해 클라이언트가 요청 당 페이지 크기를 설정할 수 있기 때문입니다. 반환 된 page_number는 클라이언트가 지금까지 요청한 페이지 수입니까, 아니면 마지막으로 사용 된 page_size가 주어진 페이지 수입니까 (거의 확실하게 나중이지만 이러한 모호함을 모두 피하지 않는 이유)?


10
첫 번째 글 머리 기호에 다음 페이지가없는 경우 rel = next 링크를 생략하는 것이 적합한 솔루션일까요? 두 번째 글 머리 기호에게 정보는 클라이언트에 대한 응답에서 계속 사용할 수 있으며 응답 본문이 아니라 헤더에 있습니다. 마지막 단락에 +1하십시오.
Kyle Hayes

마지막에 당신의 제안에 답하고 싶습니다. 커서 페이지 매김은 페이지 / 오프셋 페이지 매김과 동일하지 않습니다. 둘 다 장단점 및 성능 고려 사항이 있습니다.
kjonsson dec

19

동일한 헤더를 추가하는 것이 좋습니다. 헤더에 메타 데이터를 이동하면 같은 봉투를 치우는 데 도움이 result, data또는 records및 응답 본문은 우리가 필요로하는 데이터가 들어 있습니다. 페이지 매김 링크도 생성하는 경우 링크 헤더 를 사용할 수 있습니다 .

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

자세한 내용은 다음을 참조하십시오.

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

swagger 파일의 경우 :

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


3
RFC-6648에 따르면 "X-"접두사는 메타 데이터 키에서 삭제되어야합니다.
Ray

1
@RayKoopa 감사합니다. github 페이지를 업데이트했지만이 답변을 업데이트하는 것을 잊었습니다.
adnan kamili

0

백엔드 API 새 속성을 응답 본문에 추가하기 만하면됩니다. .net 코어 예에서 :

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

신체 반응은 다음과 같습니다.

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

일반적으로 나는 간단한 방법으로 무엇을하든간에, 예를 들어 "localhost / api / method / : lastIdObtained / : countDateToReturn"과 같은 restAPI 끝점을 이러한 매개 변수로 생성합니다. 간단한 요청으로 수행 할 수 있습니다. 예를 들어 서비스에서. .그물

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
}

Ionic에서는 아래에서 위로 스크롤하면 0 값을 전달하고 답을 얻으면 마지막으로 얻은 ID 값을 설정하고 위에서 아래로 슬라이드하면 얻은 마지막 등록 ID를 전달합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.