问题
I am about to start development on a new rest api in Java. My question is about the use of PATCH - Why?
Lets say, we have an entity named 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;
}
To create a new Address, I would do this http request:
POST http://localhost:8080/addresses
with the following request:
{
"line1" : "mandatory Address line 1",
"line2" : "optional Address line 2",
"city" : "mandatory City",
"state" : "cd"
}
Assume the record created has an id 1
The corresponding @RestController AddressResource.java will have this method :
@PostMapping(value = "/addresses")
public ResponseEntity<Address> create(@valid Address newAddress) {
addressRepo.save(newAddress);
}
@valid will ensure the entity is valid before storing the data into the table.
Now assume, I move from my apartment above to a house down the street. If I use a PATCH, it becomes
PATCH http://localhost:8080/addresses/1
with request payload:
{
"line1" : "1234 NewAddressDownTheStreet ST",
"line2" : null
}
The corresponding @RestController method would be :
@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)
}
Now if you query the table, won't my address be ?
"line1" : "1234 NewAddressDownTheStreet ST",
"line2" : "optional Address line 2", <-- INCORRECT. Should be null.
"city" : "mandatory City",
"state" : "cd"
As can be seen, the above updates results in an incorrect value for line2. This is because in java all instance variables in the Address class are initialized to null (or default initial values if they are primitives) when a class is instantiated. So there is no way to distinguish between line2 being changed to null from the default value.
Question 1) Is there a standard way to work around this?
Another disadvantage is that, I cannot use @Valid annotation to validate the request at the entry point - coz it is only a partial. So, invalid data could get into the system.
For example, imagine there was additional field with the following definition:
@Min(0)
@Max(100)
private Integer lengthOfResidencyInYears,
And the user accidentally typed 190 (when they really meant 19 years), it would not fail.
Instead of PATCH, if I had used PUT, the client would need to send the complete address object. This has the advantage that I can use @Valid to ensure that the Address is indeed valid
If one makes the premise that a GET MUST always be done before doing any updates, why wouldn't one use PUT over PATCH? Am I missing something?
Aside
My conclusion is that developers using dynamically typed languages are the proponents of using PATCH as I cannot see any benefit to using it from a statically typed language line Java or C#. It just seems to add more complexity.
回答1:
Using PATCH
to upload a modified version of an existing object is almost always problematic for exactly the reason you have outlined. If you want to use PATCH
with JSON, I strongly suggest you follow either RFC 6902 or RFC 7396. I won't speak to 7396 because I'm not that familiar with it, but to follow 6902 you would define a separate resource for PATCH
operations. In the example you gave, it would look like:
PATCH http://localhost:8080/addresses/1
[
{ "op": "replace", "path": "/line1", "value": "1234 NewAddressDownTheStreet ST" },
{ "op": "remove", "path": "/line2" }
]
You would then process this, making a new entity object that started at the current server state and applied the changes in the PATCH
. Run validation on the new entity object. If it passes, push it to the data layer. If it fails, return an error code.
If PUT
doesn't add too much overhead, it is a good idea. Idempotency is a nice thing to have. The tradeoff is that you're pushing more data over the wire. If your resource is not large and not accessed often, that's maybe not such a big deal. If your resource is large and is accessed often, that can start to add significant overhead. Of course, we can't tell you the tipping point.
You also seem to have completely tied your resource model to your database model. Good database table design and good resource design often look very different for non-trivial projects. I understand that many frameworks drive you in that direction, but you if you haven't seriously considered decoupling them, you might want to.
来源:https://stackoverflow.com/questions/39807974/in-a-java-rest-api-using-patch-vs-put-to-update-an-entity