2016-10-01 69 views
6

Tôi sắp bắt đầu phát triển trên một api còn lại mới trong Java. Câu hỏi của tôi là về việc sử dụng PATCH - Tại sao?Trong một API REST java, sử dụng PATCH vs PUT để cập nhật một thực thể

phép nói rằng, chúng tôi có một thực thể có tên Address.java

public class Address { 

    @Id 
    private Long id 

    @NotNull 
    private String line1; 

    private String line2;  //optional 

    @NotNull 
    private String city; 

    @NotNull 
    private String state; 
} 

Để tạo một địa chỉ mới, tôi sẽ làm theo yêu cầu này http:

POST http://localhost:8080/addresses 

với yêu cầu sau:

{ 
    "line1" : "mandatory Address line 1", 
    "line2" : "optional Address line 2", 
    "city" : "mandatory City", 
    "state" : "cd" 
} 

Giả sử bản ghi được tạo có id 1

Các tương ứng @RestController AddressResource.java sẽ có phương pháp này:

@PostMapping(value = "/addresses") 
public ResponseEntity<Address> create(@valid Address newAddress) { 
    addressRepo.save(newAddress); 
} 

@valid sẽ đảm bảo các thực thể có giá trị trước khi lưu trữ dữ liệu vào bảng.

Bây giờ giả sử, tôi chuyển từ căn hộ của tôi ở trên xuống một căn nhà trên phố. Nếu tôi sử dụng một bản vá, nó trở nên

PATCH http://localhost:8080/addresses/1 

với yêu cầu tải trọng:

{ 
    "line1" : "1234 NewAddressDownTheStreet ST", 
    "line2" : null 
} 

Phương pháp @RestController tương ứng sẽ là:

@PatchMapping(value = "/addresses/{id}") 
public ResponseEntity<Address> patchAddress(@PathVariable Long id, Address partialAddress) 
{ 
    Address dbAddress = addressRepo.findOne(id); 
    if (partialAddress.getLine1() != null) { 
     dbAddress.setLine1(partialAddress.getLine1()); 
    } 
    if (partialAddress.getLine2() != null) { 
     dbAddress.setLine2(partialAddress.getLine2()); 
    } 
    if (partialAddress.getCity() != null) { 
     dbAddress.setCity(partialAddress.getCity()); 
    } 
    if (partialAddress.getState() != null) { 
     dbAddress.setState(partialAddress.getState()); 
    } 

    addressRepo.save(dbAddress) 
} 

Bây giờ nếu bạn truy vấn bảng, thắng' địa chỉ của tôi là gì?

"line1" : "1234 NewAddressDownTheStreet ST", 
"line2" : "optional Address line 2",  <-- INCORRECT. Should be null. 
"city" : "mandatory City", 
"state" : "cd" 

Như có thể thấy, các cập nhật ở trên dẫn đến giá trị không chính xác cho dòng 2. Điều này là bởi vì trong java tất cả các biến mẫu trong lớp Địa chỉ được khởi tạo thành null (hoặc giá trị mặc định ban đầu nếu chúng là nguyên thủy) khi một lớp được khởi tạo. Vì vậy, không có cách nào để phân biệt giữa line2 được thay đổi thành null từ giá trị mặc định.

Câu hỏi 1) Có cách nào tiêu chuẩn để giải quyết vấn đề này không?


Một nhược điểm nữa là, tôi không thể sử dụng chú thích @Valid để xác nhận yêu cầu tại điểm nhập cảnh - coz nó chỉ là một phần. Vì vậy, dữ liệu không hợp lệ có thể xâm nhập vào hệ thống.

Ví dụ, hãy tưởng tượng là có trường bổ sung với định nghĩa sau đây:

@Min(0) 
@Max(100) 
private Integer lengthOfResidencyInYears, 

Và người dùng vô tình gõ 190 (khi họ thực sự có nghĩa là 19 năm), nó sẽ không thất bại.


Thay vì PATCH, nếu tôi đã sử dụng PUT, khách hàng sẽ cần phải gửi đối tượng địa chỉ đầy đủ. này có lợi thế mà tôi có thể sử dụng @Valid để đảm bảo rằng địa chỉ thực sự có giá trị


Nếu một làm tiền đề rằng một GET luôn phải được thực hiện trước khi thực hiện bất kỳ bản cập nhật, tại sao không phải là một sử dụng PUT hơn PATCH? Tôi có thiếu gì đó không?

Ngoài

Kết luận của tôi là các nhà phát triển sử dụng ngôn ngữ kiểu động là những người ủng hộ của việc sử dụng PATCH như tôi không thể nhìn thấy bất kỳ lợi ích để sử dụng nó từ một dòng ngôn ngữ tĩnh đánh máy Java hoặc C#. Nó chỉ có vẻ thêm phức tạp hơn.

+2

dường như trạng thái hợp lệ, sau khi PATCH yêu cầu truy vấn db của bạn sẽ hiển thị dòng: "tùy chọn Dòng địa chỉ 2", vì bạn đang kiểm tra, partialAddress.getLine2()! = Null. – kuhajeyan

+0

Một yêu cầu vá phải chứa các chỉ lệnh được máy khách tính toán mà máy chủ có thể sử dụng để chuyển đổi trạng thái A của một số tài nguyên thành trạng thái B và không chỉ là một phần cập nhật đơn giản. Đọc thêm: [SO tài liệu] (http://stackoverflow.com/documentation/http/3423/http-for-apis/11812/edit-a-resource#t=201610031245323472567) và [bài đăng blog tốt] (http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/) –

+0

Đây là một câu hỏi hay. Liên quan đến null trong dòng 2 là chính xác bởi vì bạn tự ánh xạ các trường của bạn và bạn kiểm tra các giá trị không null để ghi đè lên. Hiện tại, tôi đang làm việc trên một dự án mà chúng tôi sử dụng [Dozer] (http://dozer.sourceforge.net/documentation/usage.html) và bạn có thể chọn ánh xạ các giá trị rỗng. Dù sao, chúng tôi sử dụng POST để thêm/tạo tài nguyên và PUT để sửa đổi chúng, tuy nhiên với trình ánh xạ này, nó có thể thực hiện ánh xạ một phần. Vì vậy, im tranh luận nếu đây là cách ưa thích hoặc tôi nên sử dụng POST (tạo), PUT (cập nhật đầy đủ), PATCH (cập nhật một phần). –

Trả lời

7

Sử dụng PATCH để tải lên phiên bản đã sửa đổi của đối tượng hiện có hầu như luôn có vấn đề với chính xác lý do bạn đã nêu. Nếu bạn muốn sử dụng PATCH với JSON, I mạnh mẽ khuyên bạn nên theo dõi hoặc RFC 7396. Tôi sẽ không nói chuyện với 7396 bởi vì tôi không quen thuộc với nó, nhưng để làm theo 6902 bạn sẽ xác định một nguồn lực riêng biệt cho hoạt động PATCH. Trong ví dụ bạn đưa ra, nó sẽ trông giống như:

PATCH http://localhost:8080/addresses/1 
[ 
    { "op": "replace", "path": "/line1", "value": "1234 NewAddressDownTheStreet ST" }, 
    { "op": "remove", "path": "/line2" } 
] 

Sau đó, bạn sẽ xử lý này, làm cho một đối tượng thực thể mới bắt đầu ở trạng thái máy chủ hiện tại và áp dụng những thay đổi trong PATCH. Chạy xác thực trên đối tượng thực thể mới. Nếu nó trôi qua, hãy đẩy nó vào lớp dữ liệu. Nếu không thành công, hãy trả lại mã lỗi.

Nếu PUT không thêm quá nhiều chi phí, đó là một ý tưởng hay. Idempotency là một điều tốt đẹp để có. Sự cân bằng là bạn đang đẩy nhiều dữ liệu hơn dây. Nếu tài nguyên của bạn không lớn và không được truy cập thường xuyên, đó có thể không phải là một vấn đề lớn. Nếu tài nguyên của bạn lớn và được truy cập thường xuyên, điều đó có thể bắt đầu thêm chi phí đáng kể. Tất nhiên, chúng tôi không thể nói cho bạn biết điểm bùng phát.

Bạn cũng dường như đã gắn hoàn toàn mô hình tài nguyên của mình vào mô hình cơ sở dữ liệu. Thiết kế bảng cơ sở dữ liệu tốt và thiết kế tài nguyên tốt thường trông rất khác với các dự án không tầm thường. Tôi hiểu rằng nhiều khung công tác thúc đẩy bạn theo hướng đó, nhưng nếu bạn không nghiêm túc xem xét việc tách chúng, bạn có thể muốn.

+0

Ví dụ tôi đã thấy với SpringDataRest khiến tôi nghĩ đó là một tải trọng JSON đơn giản, nhưng định dạng yêu cầu bạn liệt kê có ý nghĩa hơn với tôi. Nếu tôi sử dụng @service của tôi làm ranh giới giao dịch, thì địa chỉ này dbData = getId() và áp dụng các thay đổi trên đầu có lẽ sẽ cần phải xảy ra trong ServiceImpl (và không phải trong bộ điều khiển). Câu trả lời thú vị. Nhưng sẽ tiếp tục mở câu hỏi này ngay bây giờ để xem các đề xuất bổ sung. – SGB

Các vấn đề liên quan