Sometimes, we want to be able to update a resource partially: for example, to update the state of an element, or to atomically increment a value. In these cases, a full replacement might not be convenient. This allows a client application to submit only those changes it is interesting in, thus improving client-side code maintainability, and reducing the traffic and hence improving the network performance.
Updates in REST are typically done using PUT
. However, PUT
completely updates a resource. When a client wants to update only part of a resource, PATCH
(RFC 5789) might be used instead. The representation of the patch can be expressed in a variety of forms. Specifically, for JSON several specifications exists:
PATCH
, like POST
, is an unsafe an non-idempotent operation. Again, to prevent concurrent problems, it is advised to use conditional requests.
The body will be expressed either in any standard specification, or in a custom schema. For example, if we are to use JSON Patch
, we can express a set of changes like this:
PATCH /products/10 HTTP/1.1
If-Unmodified-Since: Sun, 25 Oct 2020 18:31:40 GMT
[
{ "op": "replace", "path": "/price", "value": 10},
{ "op": "add", "path": "/size", "value": "XL" },
]
Which will update the value of the field price
to 10
, and will add a field size
with the value XL
.
The response will be that of a regular update call.
Side-effects in GraphQL are done using mutations. Unlike HTTP, in GraphQL there is no specific routine or verb to run a partial update. A bespoke mutation can be used for this.
A popular approach for partial updates is creating a mutation whose parameters are optional, so that only those that resolve to a defined value will be updated:
type Mutation {
patchProduct(id: ID!, name:String!, price:Int!): Product
}
Which allows client-side to run something like this:
mutation patchProduct($id:ID, $name:String!, $price:Int!) {
patchProduct(id:$id, name:$name, price:$price) {
id
name
price
}
}
Variables:
{
"id": 5,
"name": "Smartphone"
}
In which case only the name
would have been updated.
This technique also allows to unset a field. For example, to unset the price
, these variables can be used:
{
"id": 5,
"price": null
}
A custom input type could have been used as well: a PatchProductInput
that accepts empty values.
Whatever of these approaches we use, they are not as powerful as JSON-PATCH: we cannot run more complex operations, like adding or removing an entry to an array. Of course, a JSON-PATCH object can be embedded into a string in a mutation, but this goes against the static-typing nature of GraphQL, although there are some attempts to get a similar experience.
Partial updates are the recommended way to run an update operation in gRPC. They are carried out using a FieldMask
, as in a Get method. In this case, in addition to the resource, the response will also contain a FieldMask
field specifying which fields will be updated.
service Blog {
rpc UpdateArticle(UpdateArticleRequest)
returns (Article);
}
message UpdateArticleRequest {
Article article = 1;
FieldMask update_mask = 2;
}
The example source code contains a pre-populated collection of articles. Fetch any of them. Run:
curl -v http://localhost:4000/articles
Pick an id
and then:
curl -v http://localhost:4000/articles/5fa96bb541a7ab6bb7f3bd65
To update it, you'll need to provide conditional headers and a patch, as in:
curl -v --header "Content-Type: application/json" \
--header "If-Unmodified-Since: Sun, 25 Oct 2020 18:31:40 GMT" \
--request PATCH \
--data '{"op": "replace", "path": "/title", "value": "This is title has been patched"}' \
http://localhost:4000/articles/5fa96bb541a7ab6bb7f3bd65
Now you can run GET
again and only the price will have been updated.