REST API - Tạo hoặc Cập nhật hàng loạt trong một yêu cầu duy nhất [đã đóng]


92

Giả sử có hai tài nguyên BinderDocvới mối quan hệ kết hợp có nghĩa là Docvà tự Binderđứng. Doccó thể thuộc về hoặc có thể không BinderBindercó thể trống.

Nếu tôi muốn thiết kế REST API cho phép người dùng gửi một bộ sưu tập các Docs, TRONG MỘT YÊU CẦU DUY NHẤT , như sau:

{
  "docs": [
    {"doc_number": 1, "binder": 1}, 
    {"doc_number": 5, "binder": 8},
    {"doc_number": 6, "binder": 3}
  ]
}

Và đối với mỗi tài liệu trong docs,

  • Nếu doctồn tại thì gán nó choBinder
  • Nếu dockhông tồn tại, hãy tạo nó và sau đó gán nó

Tôi thực sự bối rối không biết điều này nên được triển khai như thế nào:

  • Phương thức HTTP nào để sử dụng?
  • Mã phản hồi nào phải được trả lại?
  • Điều này thậm chí còn đủ điều kiện cho REST?
  • URI sẽ trông như thế nào? /binders/docs?
  • Xử lý yêu cầu hàng loạt, điều gì sẽ xảy ra nếu một vài mục phát sinh lỗi nhưng các mục khác lại vượt qua. Mã phản hồi nào phải được trả lại? Hoạt động hàng loạt có nên là nguyên tử không?

Câu trả lời:


58

Tôi nghĩ rằng bạn có thể sử dụng phương thức POST hoặc PATCH để xử lý điều này vì chúng thường thiết kế cho điều này.

  • Sử dụng một POSTphương thức thường được sử dụng để thêm một phần tử khi được sử dụng trên tài nguyên danh sách nhưng bạn cũng có thể hỗ trợ một số hành động cho phương thức này. Xem câu trả lời này: Cách Cập nhật Bộ sưu tập Tài nguyên REST . Bạn cũng có thể hỗ trợ các định dạng biểu diễn khác nhau cho đầu vào (nếu chúng tương ứng với một mảng hoặc một phần tử đơn lẻ).

    Trong trường hợp này, không cần thiết phải xác định định dạng của bạn để mô tả bản cập nhật.

  • Sử dụng một PATCHphương thức cũng phù hợp vì các yêu cầu tương ứng tương ứng với một bản cập nhật. Theo RFC5789 ( http://tools.ietf.org/html/rfc5789 ):

    Một số ứng dụng mở rộng Giao thức truyền siêu văn bản (HTTP) yêu cầu một tính năng để thực hiện sửa đổi một phần tài nguyên. Phương thức HTTP PUT hiện tại chỉ cho phép thay thế hoàn toàn một tài liệu. Đề xuất này thêm một phương thức HTTP mới, PATCH, để sửa đổi tài nguyên HTTP hiện có.

    Trong trường hợp này, bạn phải xác định định dạng của mình để mô tả bản cập nhật từng phần.

Tôi nghĩ rằng trong trường hợp này, POSTPATCHkhá giống nhau vì bạn không thực sự cần phải mô tả hoạt động cần làm cho từng phần tử. Tôi muốn nói rằng nó phụ thuộc vào định dạng của đại diện để gửi.

Trường hợp của PUTlà một chút ít rõ ràng hơn. Trên thực tế, khi sử dụng một phương pháp PUT, bạn nên cung cấp toàn bộ danh sách. Trên thực tế, đại diện được cung cấp trong yêu cầu sẽ thay thế cho tài nguyên danh sách.

Bạn có thể có hai tùy chọn liên quan đến đường dẫn tài nguyên.

  • Sử dụng đường dẫn tài nguyên cho danh sách tài liệu

Trong trường hợp này, bạn cần cung cấp rõ ràng liên kết của các tài liệu với chất kết dính trong phần trình bày mà bạn cung cấp trong yêu cầu.

Đây là một tuyến đường mẫu cho việc này /docs.

Nội dung của cách tiếp cận như vậy có thể là phương pháp POST:

[
    { "doc_number": 1, "binder": 4, (other fields in the case of creation) },
    { "doc_number": 2, "binder": 4, (other fields in the case of creation) },
    { "doc_number": 3, "binder": 5, (other fields in the case of creation) },
    (...)
]
  • Sử dụng đường dẫn tài nguyên phụ của phần tử chất kết dính

Ngoài ra, bạn cũng có thể xem xét sử dụng các tuyến con để mô tả mối liên kết giữa tài liệu và chất kết dính. Các gợi ý về mối liên kết giữa tài liệu và chất kết dính bây giờ không cần phải được chỉ định trong nội dung yêu cầu.

Đây là một tuyến đường mẫu cho việc này /binder/{binderId}/docs. Trong trường hợp này, gửi danh sách tài liệu có một phương thức POSThoặc PATCHsẽ đính kèm tài liệu vào trình kết dính với số nhận dạng binderIdsau khi tạo tài liệu nếu tài liệu đó không tồn tại.

Nội dung của cách tiếp cận như vậy có thể là phương pháp POST:

[
    { "doc_number": 1, (other fields in the case of creation) },
    { "doc_number": 2, (other fields in the case of creation) },
    { "doc_number": 3, (other fields in the case of creation) },
    (...)
]

Về phản hồi, tùy thuộc vào bạn để xác định mức độ phản hồi và các lỗi cần trả lại. Tôi thấy hai cấp độ: cấp độ trạng thái (cấp độ toàn cầu) và cấp độ tải trọng (cấp độ mỏng hơn). Bạn cũng phải xác định xem tất cả các phần chèn / cập nhật tương ứng với yêu cầu của bạn có phải là nguyên tử hay không.

  • Nguyên tử

Trong trường hợp này, bạn có thể tận dụng trạng thái HTTP. Nếu mọi thứ suôn sẻ, bạn sẽ có được một trạng thái 200. Nếu không, một trạng thái khác như 400nếu dữ liệu được cung cấp không chính xác (ví dụ: id liên kết không hợp lệ) hoặc một cái gì đó khác.

  • Phi nguyên tử

Trong trường hợp này, một trạng thái 200sẽ được trả lại và tùy thuộc vào biểu diễn phản hồi để mô tả những gì đã được thực hiện và cuối cùng lỗi xảy ra ở đâu. ElasticSearch có một điểm cuối trong API REST của nó để cập nhật hàng loạt. Điều này có thể cung cấp cho bạn một số ý tưởng ở cấp độ này: http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/bulk.html .

  • Không đồng bộ

Bạn cũng có thể triển khai xử lý không đồng bộ để xử lý dữ liệu được cung cấp. Trong trường hợp này, trả về trạng thái HTTP sẽ là 202. Khách hàng cần kéo thêm một nguồn để xem điều gì sẽ xảy ra.

Trước khi kết thúc, tôi cũng muốn lưu ý rằng đặc tả OData giải quyết vấn đề liên quan đến mối quan hệ giữa các thực thể với tính năng có tên liên kết điều hướng . Có lẽ bạn có thể nhìn vào cái này ;-)

Liên kết sau cũng có thể giúp bạn: https://templth.wordpress.com/2014/12/15/designs-a-web-api/ .

Hy vọng nó sẽ giúp bạn, Thierry


Tôi có theo dõi câu hỏi. Tôi đã chọn các tuyến đường bằng phẳng mà không có tài nguyên phụ lồng ghép. Để nhận tất cả tài liệu, tôi gọi GET /docsvà truy xuất tất cả tài liệu trong một chất kết dính cụ thể , GET /docs?binder_id=x. Để xóa một tập hợp con của các tài nguyên, tôi sẽ gọi DELETE /docs?binder_id=xhay tôi nên gọi DELETE /docsvới một {"binder_id": x}trong phần thân yêu cầu? Bạn có bao giờ sử dụng PATCH /docs?binder_id=xđể cập nhật hàng loạt hay chỉ PATCH /docsvà vượt qua các cặp không?
Andy Fusniak

34

Bạn có thể sẽ cần phải sử dụng POST hoặc PATCH, vì không chắc rằng một yêu cầu duy nhất cập nhật và tạo nhiều tài nguyên sẽ không có lợi.

Làm PATCH /docschắc chắn là một lựa chọn hợp lệ. Bạn có thể thấy việc sử dụng các định dạng bản vá tiêu chuẩn là khó khăn cho trường hợp cụ thể của bạn. Không chắc chắn về điều này.

Bạn có thể sử dụng 200. Bạn cũng có thể sử dụng 207 - Đa trạng thái

Điều này có thể được thực hiện một cách RESTful. Chìa khóa, theo ý kiến ​​của tôi, là có một số tài nguyên được thiết kế để chấp nhận một bộ tài liệu để cập nhật / tạo.

Nếu bạn sử dụng phương pháp PATCH, tôi nghĩ rằng hoạt động của bạn phải là nguyên tử. tức là tôi sẽ không sử dụng mã trạng thái 207 và sau đó báo cáo thành công và thất bại trong cơ quan phản hồi. Nếu bạn sử dụng thao tác POST thì cách tiếp cận 207 là khả thi. Bạn sẽ phải thiết kế cơ quan phản hồi của riêng mình để thông báo hoạt động nào thành công và hoạt động nào không thành công. Tôi không biết về một tiêu chuẩn.


Cảm ơn bạn rất nhiều. Bằng cách This can be done in a RESTful waynào bạn có nghĩa là cập nhật và tạo phải được thực hiện riêng rẽ?
Sam R.

1
@norbertpy Thực hiện một số loại thao tác ghi trên một tài nguyên có thể khiến các tài nguyên khác được cập nhật và tạo từ một yêu cầu duy nhất. REST không có vấn đề gì với điều đó. Lựa chọn cụm từ của tôi là do một số khuôn khổ triển khai các hoạt động hàng loạt bằng cách tuần tự hóa các yêu cầu HTTP thành các tài liệu nhiều phần và sau đó gửi các yêu cầu HTTP được tuần tự hóa dưới dạng một lô. Tôi nghĩ rằng cách tiếp cận đó vi phạm giới hạn REST xác định tài nguyên.
Darrel Miller

19

PUT ing

PUT /binders/{id}/docs Tạo hoặc cập nhật và liên kết một tài liệu với một chất kết dính

ví dụ:

PUT /binders/1/docs HTTP/1.1
{
  "docNumber" : 1
}

PATCH ing

PATCH /docs Tạo tài liệu nếu chúng không tồn tại và liên hệ chúng với chất kết dính

ví dụ:

PATCH /docs HTTP/1.1
[
    { "op" : "add", "path" : "/binder/1/docs", "value" : { "doc_number" : 1 } },
    { "op" : "add", "path" : "/binder/8/docs", "value" : { "doc_number" : 8 } },
    { "op" : "add", "path" : "/binder/3/docs", "value" : { "doc_number" : 6 } }
] 

Tôi sẽ bao gồm các thông tin chi tiết bổ sung sau, nhưng trong thời gian chờ đợi, nếu bạn muốn, hãy xem RFC 5789 , RFC 6902Vui lòng của William Durand . Đừng vá như một mục blog ngốc .


2
Đôi khi khách hàng cần hoạt động hàng loạt và nó không muốn quan tâm xem tài nguyên có ở đó hay không. Như tôi đã nói trong câu hỏi, khách hàng muốn gửi một nhóm docsvà liên kết với họ binders. Khách hàng muốn tạo chất kết dính nếu chúng không tồn tại và tạo liên kết nếu chúng có. Trong một yêu cầu SỐ LƯỢNG DUY NHẤT.
Sam R.

12

Trong một dự án mà tôi làm việc, chúng tôi đã giải quyết vấn đề này bằng cách thực hiện một thứ mà chúng tôi gọi là yêu cầu 'Hàng loạt'. Chúng tôi đã xác định một đường dẫn /batchmà chúng tôi chấp nhận json ở định dạng sau:

[  
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 1,
         binder: 1
      }
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 5,
         binder: 8
      }
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 6,
         binder: 3
      }
   },
]

Phản hồi có mã trạng thái 207 (Đa trạng thái) và trông giống như sau:

[  
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 1,
         binder: 1
      }
      status: 200
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         error: {
            msg: 'A document with doc_number 5 already exists'
            ...
         }
      },
      status: 409
   },
   {
      path: '/docs',
      method: 'post',
      body: {
         doc_number: 6,
         binder: 3
      },
      status: 200
   },
]

Bạn cũng có thể thêm hỗ trợ cho các tiêu đề trong cấu trúc này. Chúng tôi đã triển khai một cái gì đó tỏ ra hữu ích, đó là các biến để sử dụng giữa các yêu cầu trong một lô, có nghĩa là chúng tôi có thể sử dụng phản hồi từ một yêu cầu làm đầu vào cho một yêu cầu khác.

Facebook và Google có cách triển khai tương tự:
https://developers.google.com/gmail/api/guides/batch
https://developers.facebook.com/docs/graph-api/making-multiple-requests

Khi bạn muốn tạo hoặc cập nhật một tài nguyên với cùng một lệnh gọi, tôi sẽ sử dụng POST hoặc PUT tùy trường hợp. Nếu tài liệu đã tồn tại, bạn có muốn toàn bộ tài liệu là:

  1. Được thay thế bằng tài liệu bạn gửi (tức là các thuộc tính bị thiếu trong yêu cầu sẽ bị xóa và hiện đã bị ghi đè)?
  2. Đã hợp nhất với tài liệu bạn gửi (tức là các thuộc tính bị thiếu trong yêu cầu sẽ không bị xóa và các thuộc tính đã có sẽ bị ghi đè)?

Trong trường hợp bạn muốn hành vi từ phương án 1, bạn nên sử dụng POST và trong trường hợp bạn muốn hành vi từ phương án 2, bạn nên sử dụng PUT.

http://restcookbook.com/HTTP%20Methods/put-vs-post/

Như mọi người đã gợi ý, bạn cũng có thể sử dụng PATCH, nhưng tôi muốn giữ cho API đơn giản và không sử dụng các động từ bổ sung nếu chúng không cần thiết.


5
Thích câu trả lời này cho Proof-of-Concept cũng như các liên kết Google và Facebook. Nhưng không đồng ý với phần kết thúc về POST hoặc PUT. Trong 2 trường hợp câu trả lời này được đề cập, câu đầu tiên phải là PUT và câu thứ hai nên là PATCH.
RayLuo

@RayLuo, bạn có thể giải thích tại sao chúng ta cần PATCH ngoài POST và PUT không?
David Berg

2
Bởi vì đó là thứ mà PATCH được phát minh ra. Bạn có thể đọc định nghĩa này và xem cách PUT và PATCH khớp với 2 gạch đầu dòng của bạn.
RayLuo

@DavidBerg, Có vẻ như Google đã ưa thích một cách tiếp cận khác để xử lý yêu cầu hàng loạt, tức là tách phần tiêu đề và phần nội dung của mỗi yêu cầu phụ thành phần tương ứng của yêu cầu chính, với một ranh giới như thế nào --batch_xxxx. Có một số khác biệt quan trọng giữa các giải pháp của Google và Facebook? Ngoài ra, về "sử dụng phản hồi từ một yêu cầu làm đầu vào cho một yêu cầu khác", nghe có vẻ rất thú vị, bạn có phiền chia sẻ thêm chi tiết không? hoặc loại kịch bản nào nên được sử dụng?
Yang
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.