API RESTful. Tôi có nên trả lại đối tượng đã được tạo / cập nhật không?


36

Tôi đang thiết kế một dịch vụ web RESTful bằng WebApi và đang tự hỏi những phản hồi và nội dung phản hồi HTTP nào sẽ trả về khi cập nhật / tạo đối tượng.

Ví dụ, tôi có thể sử dụng phương thức POST để gửi một số JSON đến dịch vụ web và sau đó tạo một đối tượng. Cách tốt nhất là sau đó đặt trạng thái HTTP thành đã tạo (201) hoặc ok (200) và chỉ cần trả về một thông báo như "Nhân viên mới được thêm vào" hoặc trả lại đối tượng đã được gửi ban đầu?

Điều tương tự cũng xảy ra với phương pháp PUT. Trạng thái HTTP nào là tốt nhất để sử dụng và tôi có cần trả về đối tượng đã được tạo hoặc chỉ là một tin nhắn không? Xem xét thực tế rằng người dùng biết đối tượng họ đang cố gắng tạo / cập nhật bằng cách nào.

Có suy nghĩ gì không?

Thí dụ:

Thêm nhân viên mới:

POST /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Name" : "Joe Bloggs",
        "Department" : "Finance"
    }
}

Cập nhật nhân viên hiện có:

PUT /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Phản hồi:

Phản hồi với đối tượng được tạo / cập nhật

HTTP/1.1 201 Created
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Trả lời chỉ bằng tin nhắn:

HTTP/1.1 200 OK
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Message": "Employee updated"
}

Phản hồi chỉ với mã trạng thái:

HTTP/1.1 204 No Content
Content-Length: 39
Date: Mon, 28 Mar 2016 14:32:39 GMT

2
Câu hỏi hay, nhưng sử dụng thuật ngữ "thực hành tốt nhất" là một điều cấm kị trên trang web này meta.programmers.stackexchange.com/questions/2442/ Lỗi Bạn có thể chỉ muốn viết lại câu hỏi. meta.programmers.stackexchange.com/questions/6967/ từ
Snoop

3
Theo dõi một chút, có nên có một cờ trong yêu cầu để (ví dụ) một ứng dụng di động có thể lấy lại toàn bộ đối tượng khi ở trên WiFi, nhưng chỉ có ID khi sử dụng dữ liệu di động? Có một tiêu đề nào nên được sử dụng cho điều đó để tránh gây ô nhiễm JSON không?
Andrew nói phục hồi Monica

@AndrewPiliser Ý tưởng thú vị, mặc dù cá nhân tôi nghĩ rằng tốt nhất là chọn một cách tiếp cận và tuân theo nó. Sau đó, khi ứng dụng của bạn phát triển hoặc trở nên phổ biến hơn, hãy tối ưu hóa nó
iswinky

@AndrewPiliser ý tưởng của bạn rất giống với UPDATE/INSERT ... RETURNINGbiến thể Postgresql cho SQL. Nó cực kỳ tiện dụng, đặc biệt là nó giữ việc gửi dữ liệu mới và yêu cầu cho phiên bản nguyên tử cập nhật.
beldaz

Câu trả lời:


31

Như với hầu hết mọi thứ, nó phụ thuộc. Sự đánh đổi của bạn là dễ sử dụng so với kích thước mạng. Nó có thể rất hữu ích cho khách hàng để xem tài nguyên đã tạo. Nó có thể bao gồm các trường được điền bởi máy chủ, chẳng hạn như lần tạo trước. Vì bạn dường như bao gồm cả idthay vì sử dụng hateoas, khách hàng có thể sẽ muốn xem id cho tài nguyên họ vừa POSTed.

Nếu bạn không bao gồm tài nguyên đã tạo, vui lòng không tạo một thông báo tùy ý. Các trường 2xx và Vị trí là đủ thông tin để khách hàng tự tin rằng yêu cầu của họ đã được xử lý đúng cách.


+1 Mục tiêu ghét của việc không cho phép khách hàng soạn thảo của bạn cũng có thể đạt được bằng cách cho phép khách hàng điền vào các mẫu URL được cung cấp bởi máy chủ với ID cụ thể. Có, khách hàng "sáng tác" nhưng chỉ theo kiểu "điền vào chỗ trống". Mặc dù không phải HATEOAS thuần túy, nó đạt được mục tiêu và làm việc với các đối tượng có số lượng "hành động" lớn hơn một chút về độ nhạy băng thông, chưa kể khi bạn đặt các đối tượng đó vào danh sách (lớn).
Marjan Venema

3
+1 về lời khuyên "vui lòng không tạo tin nhắn tùy ý"
HairOfTheDog

Có phải "không có thông báo tùy ý" tập trung vào các thông báo chuỗi hoặc bất kỳ giá trị trả về nào không phải là tài nguyên đã tạo không? Tôi đang tập trung vào các trường hợp chúng tôi trả lại id của tài nguyên đã tạo (chứ không phải chính tài nguyên đó) và đang tự hỏi nơi này phù hợp.
Flater

12

Cá nhân tôi luôn trở về 200 OK.

Để trích dẫn câu hỏi của bạn

Xem xét thực tế rằng người dùng biết đối tượng họ đang cố gắng tạo / cập nhật bằng cách nào.

Tại sao thêm băng thông bổ sung (có thể phải trả tiền) để nói với khách hàng những gì họ đã biết?


1
Đó là những gì tôi đã nghĩ, nếu nó không hợp lệ, bạn có thể trả về các thông báo xác thực, nhưng nếu nó hợp lệ và được tạo / cập nhật thì hãy kiểm tra mã trạng thái HTTP và hiển thị cho người dùng một thông báo, ví dụ như "Hoan hô" dựa trên đó
iswinky vào

3
Xem stackoverflow.com/a/827045/290182 về 200/ 204 No Contentđể tránh nhầm lẫn jQuery và tương tự.
beldaz

10

@iswinky Tôi sẽ luôn gửi lại tải trọng trong trường hợp cả POST và PUT.

Trong trường hợp POST, bạn có thể tạo thực thể với ID nội bộ hoặc UUID. Do đó, nó có ý nghĩa để gửi lại tải trọng.

Tương tự như vậy trong trường hợp PUT, bạn có thể bỏ qua một số trường của người dùng (giả sử giá trị không thay đổi) hoặc trong trường hợp B PATNG, dữ liệu cũng có thể bị thay đổi bởi người dùng khác.

Gửi lại dữ liệu vì nó vẫn tồn tại luôn là một ý tưởng hay và chắc chắn không gây hại. Nếu người gọi không có nhu cầu về dữ liệu trả về này, thì anh ta / cô ta sẽ không xử lý nó mà sẽ chỉ sử dụng statusCode. Khác họ có thể sử dụng dữ liệu này như một cái gì đó để cập nhật giao diện người dùng.

Chỉ trong trường hợp XÓA, tôi sẽ không gửi lại tải trọng và sẽ thực hiện 200 với nội dung phản hồi hoặc 204 không có nội dung phản hồi.

Chỉnh sửa: Nhờ một số ý kiến ​​từ bên dưới, tôi đang viết lại câu trả lời của mình. Tôi vẫn đứng trước cách tôi thiết kế API của mình và gửi phản hồi lại nhưng tôi nghĩ sẽ hợp lý khi đủ điều kiện cho một số suy nghĩ thiết kế của tôi.

a) Khi tôi nói gửi lại tải trọng, tôi thực sự muốn nói gửi lại dữ liệu của tài nguyên, không phải cùng một tải trọng trong yêu cầu. Ví dụ: nếu bạn gửi một tải trọng tạo, tôi có thể trong phần phụ trợ tạo các thực thể khác như dấu thời gian UUID và (có thể) và một số kết nối (biểu đồ). Tôi sẽ gửi lại tất cả điều này trong phản hồi (không chỉ là tải trọng yêu cầu - điều này là vô nghĩa).

b) Tôi sẽ không gửi lại phản hồi trong trường hợp tải trọng rất lớn. Tôi đã thảo luận điều này trong các ý kiến, nhưng điều tôi muốn cảnh báo là tôi sẽ cố gắng hết sức để thiết kế API hoặc tài nguyên của mình sao cho nó không phải có tải trọng rất lớn. Tôi sẽ cố gắng chia tài nguyên thành các thực thể nhỏ hơn và có thể quản lý sao cho mỗi tài nguyên được xác định bởi 15-20 thuộc tính JSON và không lớn hơn.

Trong trường hợp đối tượng rất lớn hoặc đối tượng cha mẹ đang được cập nhật, thì tôi sẽ gửi lại các cấu trúc lồng nhau dưới dạng href.

Điểm mấu chốt là tôi chắc chắn sẽ cố gắng gửi lại dữ liệu hợp lý cho người tiêu dùng / UI xử lý ngay lập tức và được thực hiện bằng hành động API nguyên tử thay vì phải đi và tìm thêm 2-5 API chỉ để cập nhật giao diện người dùng tạo / cập nhật dữ liệu.

API máy chủ đến máy chủ có thể nghĩ khác về điều này. Tôi đang tập trung vào các API hướng đến trải nghiệm người dùng.


Tôi có thể thấy nhiều tình huống gửi lại toàn bộ tải trọng là một ý tưởng tồi, khi tải trọng lớn.
beldaz

2
@beldaz hoàn toàn đồng ý. YMMV dựa trên thiết kế của API REST. Tôi thường tránh các đối tượng rất lớn và chia nó thành một loạt các tài nguyên phụ / PUT. Nếu tải trọng rất lớn, có nhiều cách tốt hơn để làm điều này và ở đó bạn sẽ muốn thực hiện HATEOAS (như Marjan nói ở trên) nơi bạn trả lại tham chiếu cho đối tượng thay vì chính đối tượng.
ksprashu

@ksprashu: "Do đó, việc gửi lại tải trọng trở nên hợp lý" - Tôi thấy đây là một ý tưởng tồi, vì vậy, tài nguyên có thể được lấy theo nhiều cách: thông qua GET, như một phản hồi của POST, như một phản hồi của PUT. Nó có nghĩa là khách hàng có được 3 tài nguyên với cấu trúc có khả năng khác nhau . Trong trường hợp nếu bạn chỉ trả về URI (vị trí), không có phần thân, thì cách duy nhất để có được tài nguyên sẽ là NHẬN. Điều này sẽ đảm bảo rằng khách hàng luôn nhận được phản hồi nhất quán.
mentallurg

@mentallurg Tôi nhận ra mình có thể không nói từ này đúng. Cảm ơn đã chỉ ra điều đó. Tôi đã chỉnh sửa câu trả lời của mình. Hãy cho tôi biết nếu điều này có ý nghĩa hơn.
ksprashu

Miễn là bạn triển khai một dịch vụ cho công việc tại nhà của bạn, điều đó không thực sự quan trọng. Làm điều đó như bạn muốn. Tiết kiệm thời gian của bạn.
mentallurg

9

Tham khảo các tiêu chuẩn RFC liên kết , bạn nên trả về trạng thái 201 (đã tạo) khi lưu trữ thành công tài nguyên yêu cầu bằng Post. Trong hầu hết các ứng dụng, id của tài nguyên được tạo bởi chính máy chủ, vì vậy tốt nhất là trả về id của tài nguyên đã tạo. Trả lại toàn bộ đối tượng là chi phí chung cho yêu cầu Đăng. Thực hành lý tưởng là trả về vị trí URL của tài nguyên mới được tạo.

Ví dụ: bạn có thể tham khảo ví dụ sau để lưu Đối tượng nhân viên vào cơ sở dữ liệu và trả về URL của đối tượng tài nguyên mới được tạo dưới dạng phản hồi.

@RequestMapping(path = "/employees", method = RequestMethod.POST)
public ResponseEntity<Object> saveEmployee(@RequestBody Employee employee) {
        int id = employeeService.saveEmployee(employee);
        URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(id).toUri();
        return ResponseEntity.created(uri).build();
}

Điểm cuối còn lại này sẽ trả về phản hồi là:

Trạng thái 201 - TẠO

Vị trí tiêu đề → http: // localhost: 8080 / nhân viên / 1


Đẹp và sạch sẽ - sẽ theo dõi điều này từ bây giờ và hơn thế nữa
Hassan Tareq

0

Tôi sẽ tạo một tải trọng trong phần trả về có điều kiện cho tham số HTTP.

Thường xuyên hơn không phải là tốt nhất để trả lại một số loại nội dung cho người tiêu dùng API để tránh các chuyến đi khứ hồi không cần thiết (một trong những lý do khiến GraphQL tồn tại.)

Như một vấn đề thực tế, khi các ứng dụng của chúng tôi trở nên chuyên sâu và phân tán dữ liệu hơn, tôi thử tuân thủ hướng dẫn này:

Hướng dẫn của tôi :

Bất cứ khi nào có trường hợp sử dụng yêu cầu NHẬN ngay sau POST hoặc PUT, đó là trường hợp tốt nhất là trả lại một cái gì đó trong phản hồi POST / PUT.

Làm thế nào điều này được thực hiện và loại nội dung nào được trả lại từ PUT hoặc POST, đó là ứng dụng cụ thể. Bây giờ, sẽ rất thú vị nếu ứng dụng có thể tham số hóa loại "nội dung" trong phần phản hồi (chúng ta chỉ muốn vị trí của đối tượng mới, hoặc một số trường hoặc toàn bộ đối tượng, v.v.)

Một ứng dụng có thể xác định một tập các tham số mà POST / PUT có thể nhận để kiểm soát loại "nội dung" để trả về trong phần thân phản hồi. Hoặc nó có thể mã hóa một số loại truy vấn GraphQL dưới dạng tham số (tôi có thể thấy điều này hữu ích nhưng cũng trở thành cơn ác mộng bảo trì.)

Dù bằng cách nào, dường như với tôi rằng:

  1. Sẽ ổn (và rất có thể là mong muốn) để trả lại một cái gì đó trong cơ thể phản hồi POST / PUT.
  2. Làm thế nào điều này được thực hiện là ứng dụng cụ thể và gần như không thể khái quát.
  3. Theo mặc định, bạn không muốn trả về "bối cảnh" kích thước lớn (tiếng ồn giao thông đánh bại toàn bộ lý do di chuyển khỏi POST-follow-by-GETs.)

Vì vậy, 1) làm điều đó, nhưng 2) giữ cho nó đơn giản.

Một tùy chọn khác mà tôi đã thấy là mọi người tạo các điểm cuối thay thế (giả sử / khách hàng cho POST / PUT không trả lại gì trong phần thân và / customer_with_details cho POST / PUT cho / khách hàng, nhưng trả lại một cái gì đó trong phần phản hồi.)

Tôi sẽ tránh cách tiếp cận này, mặc dù. Điều gì xảy ra khi bạn cần trả lại loại nội dung khác nhau một cách hợp pháp? Một điểm cuối cho mỗi loại nội dung? Không thể mở rộng hoặc duy trì.

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.