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.
PUT means replace the entire resource with given data (so null out fields if they are not provided in therequest), while PATCH means replace only specified fields. For the Table API, however, PUT and PATCH mean the same thing.
The PATCH method is the correct choice here as you're updating an existing resource - the group ID. PUT should only be used if you're replacing a resource in its entirety. Further information on partial resource modification is available in RFC 5789.
Difference between PUT and PATCH On the other hand, the PATCH method updates individual fields without overwriting existing fields. PUT uses more bandwidth because it handles full resources, so PATCH was introduced to reduce the bandwidth usage.
Another important difference between the methods is that PUT is an idempotent method, while POST isn't. For instance, calling the PUT method multiple times will either create or update the same resource. In contrast, multiple POST requests will lead to the creation of the same resource multiple times.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With