Tải trọng phản hồi phân trang từ API RESTful


83

Tôi muốn hỗ trợ phân trang trong API RESTful của mình.

Phương thức API của tôi sẽ trả về danh sách sản phẩm JSON qua /products/index. Tuy nhiên, có thể có hàng nghìn sản phẩm và tôi muốn lướt qua chúng, vì vậy yêu cầu của tôi sẽ giống như sau:

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

Nhưng phản hồi JSON của tôi cần trông như thế nào? Người tiêu dùng API thường mong đợi dữ liệu meta phân trang trong phản hồi? Hay chỉ một mảng sản phẩm cần thiết? Tại sao?

Có vẻ như API của Twitter bao gồm dữ liệu meta: https://dev.twitter.com/docs/api/1/get/lists/members (xem Yêu cầu mẫu).

Với dữ liệu meta:

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

Chỉ là một mảng sản phẩm (không có dữ liệu meta):

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

Câu trả lời:


110

Các API ReSTful được sử dụng chủ yếu bởi các hệ thống khác, đó là lý do tại sao tôi đặt dữ liệu phân trang trong các tiêu đề phản hồi. Tuy nhiên, một số người tiêu dùng API có thể không có quyền truy cập trực tiếp vào các tiêu đề phản hồi hoặc có thể đang xây dựng một UX qua API của bạn, vì vậy việc cung cấp một cách để truy xuất (theo yêu cầu) siêu dữ liệu trong phản hồi JSON là một lợi thế.

Tôi tin rằng việc triển khai của bạn nên bao gồm siêu dữ liệu có thể đọc được bằng máy làm mặc định và siêu dữ liệu có thể đọc được của con người khi được yêu cầu. Siêu dữ liệu mà con người có thể đọc được có thể được trả lại với mọi yêu cầu nếu bạn thích hoặc tốt hơn là theo yêu cầu thông qua tham số truy vấn, chẳng hạn như include=metadatahoặc include_metadata=true.

Trong trường hợp cụ thể của bạn, tôi sẽ bao gồm URI cho từng sản phẩm có bản ghi. Điều này giúp người tiêu dùng API dễ dàng tạo liên kết đến các sản phẩm riêng lẻ. Tôi cũng sẽ đặt một số kỳ vọng hợp lý theo giới hạn yêu cầu phân trang của mình. Việc triển khai và ghi lại các cài đặt mặc định cho kích thước trang là một thực tiễn có thể chấp nhận được. Ví dụ: API của GitHub đặt kích thước trang mặc định thành 30 bản ghi với tối đa 100 bản ghi, đồng thời đặt giới hạn tỷ lệ về số lần bạn có thể truy vấn API. Nếu API của bạn có kích thước trang mặc định, thì chuỗi truy vấn có thể chỉ định chỉ mục trang.

Trong trường hợp con người có thể đọc được, khi điều hướng đến /products?page=5&per_page=20&include=metadata, phản hồi có thể là:

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

Đối với siêu dữ liệu có thể đọc được bằng máy, tôi sẽ thêm tiêu đề Liên kết vào phản hồi:

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

(giá trị tiêu đề Liên kết phải được mã hóa nhanh)

... và có thể là total-counttiêu đề phản hồi tùy chỉnh , nếu bạn chọn:

total-count: 521

Dữ liệu phân trang khác được tiết lộ trong siêu dữ liệu tập trung vào con người có thể không cần thiết đối với siêu dữ liệu tập trung vào máy móc, vì tiêu đề liên kết cho tôi biết tôi đang ở trang nào và số lượng mỗi trang và tôi có thể nhanh chóng truy xuất số lượng bản ghi trong mảng . Do đó, tôi có thể sẽ chỉ tạo một tiêu đề cho tổng số. Bạn luôn có thể thay đổi quyết định sau đó và thêm nhiều siêu dữ liệu hơn.

Ngoài ra, bạn có thể nhận thấy tôi đã xóa /indexkhỏi URI của bạn. Một quy ước chung được chấp nhận là để điểm cuối ReST của bạn hiển thị các bộ sưu tập. Có /indexmột chút lầy lội ở cuối.

Đây chỉ là một vài thứ tôi muốn có khi sử dụng / tạo một API. Hy vọng rằng sẽ giúp!


per_page không tuân theo quy ước page_size
Alexandros Spyropoulos

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

1
điều gì sẽ xảy ra nếu số lượng sản phẩm tăng đột ngột trong khi tìm nạp tất cả các bản ghi từ trang 1 đến trang x?
MeV

3
@MeV điều tương tự cũng xảy ra trong bất kỳ trường hợp phân trang dựa trên con trỏ nào: tổng số sẽ tăng và số lượng trang có thể tăng nếu trang cuối cùng đã đầy, không hơn không kém. Đây là một tình huống rất phổ biến trên mọi ứng dụng sử dụng kiểu phân trang này. Nó sẽ phụ thuộc vào việc phân loại đang được sử dụng nếu sản phẩm mới xuất hiện trên trang đầu tiên hoặc trang cuối cùng.
Pablo Pazos

2
"Các API ReSTful được sử dụng chủ yếu bởi các hệ thống khác, đó là lý do tại sao tôi đặt dữ liệu phân trang trong tiêu đề phản hồi" Điều đó giống như nói rằng ngoài trời nắng, đó là lý do tại sao tôi mặc áo sơ mi xanh. Điều gì khiến bạn nghĩ rằng con người không thể đọc được tiêu đề?
một oliver tốt hơn 19/02/19

29

Là một người đã viết một số thư viện để sử dụng các dịch vụ REST, hãy để tôi cung cấp cho bạn quan điểm của khách hàng về lý do tại sao tôi nghĩ rằng gói kết quả trong siêu dữ liệu là cách để đi:

  • Nếu không có tổng số, làm thế nào khách hàng có thể biết rằng nó chưa nhận được tất cả những gì có và nên tiếp tục phân trang thông qua tập kết quả? Trong giao diện người dùng không hoạt động, hãy chuyển sang trang tiếp theo, trong trường hợp xấu nhất, điều này có thể được biểu thị dưới dạng liên kết Tiếp theo / Khác không thực sự tìm nạp thêm bất kỳ dữ liệu nào.
  • Bao gồm siêu dữ liệu trong phản hồi cho phép khách hàng theo dõi trạng thái ít hơn. Bây giờ tôi không phải đối sánh yêu cầu REST của mình với phản hồi, vì phản hồi chứa siêu dữ liệu cần thiết để tạo lại trạng thái yêu cầu (trong trường hợp này là con trỏ vào tập dữ liệu).
  • Nếu trạng thái là một phần của phản hồi, tôi có thể thực hiện đồng thời nhiều yêu cầu vào cùng một tập dữ liệu và tôi có thể xử lý các yêu cầu theo bất kỳ thứ tự nào mà chúng tình cờ đến mà không nhất thiết phải là thứ tự tôi đã thực hiện yêu cầu.

Và một gợi ý: Giống như API Twitter , bạn nên thay thế page_number bằng một chỉ mục / con trỏ thẳng. Lý do là, API cho phép khách hàng đặt kích thước trang cho mỗi yêu cầu. Số page_number được trả về có phải là số trang mà khách hàng đã yêu cầu cho đến nay hay số trang được cung cấp cho page_size được sử dụng lần cuối (gần như chắc chắn là muộn hơn, nhưng tại sao không tránh hoàn toàn sự mơ hồ như vậy)?


10
Đối với gạch đầu dòng đầu tiên của bạn, có phải là giải pháp phù hợp để bỏ qua liên kết rel = next nếu không có trang tiếp theo không? Đến gạch đầu dòng thứ hai, thông tin vẫn có sẵn trong phản hồi cho khách hàng, nó chỉ không nằm trong phần nội dung của phản hồi mà thay vào đó là trong tiêu đề. +1 trên đoạn cuối cùng của bạn.
Kyle Hayes

17

Tôi khuyên bạn nên thêm các tiêu đề cho giống nhau. Di chuyển siêu dữ liệu để tiêu đề giúp trong việc thoát khỏi phong bì như result, datahay recordsvà cơ thể phản ứng chỉ chứa các dữ liệu chúng ta cần. Bạn có thể sử dụng tiêu đề Liên kết nếu bạn cũng tạo liên kết phân trang.

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

Thông tin chi tiết tham khảo:

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

Đối với tệp swagger:

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


2
Theo RFC-6648, tiền tố "X-" phải được bỏ trong các khóa siêu dữ liệu.
Ray

1
@RayKoopa cảm ơn, tôi đã cập nhật trang github nhưng quên cập nhật câu trả lời này.
adnan kamili,

0

chỉ cần thêm thuộc tính mới của API phụ trợ của bạn vào nội dung phản hồi. từ lõi .net ví dụ:

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

Trong phản ứng của cơ thể, nó trông như thế này

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

nói chung, tôi thực hiện bằng cách đơn giản, bất cứ điều gì, tôi tạo một điểm cuối restAPI ví dụ "localhost / api / method /: lastIdObtained /: countDateToReturn" với các tham số của đề tài, bạn có thể thực hiện nó theo yêu cầu đơn giản. trong dịch vụ, ví dụ. .mạng lưới

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
}

Trong Ionic, khi tôi cuộn từ dưới lên trên, tôi chuyển giá trị 0, khi tôi nhận được câu trả lời, tôi đặt giá trị của id cuối cùng thu được và khi tôi trượt từ trên xuống, tôi chuyển id đăng ký cuối cùng mà tôi nhận được

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.