feat(data-service): add json-patch support#1023
Conversation
573dfa9 to
aeda8dd
Compare
|
FYI: I placed a few comments before that referred to my original implementation and @Mohammer5 replied to one of these comments. By now I've gone with a different type of implementation and the comments did not make much sense anymore. So I removed my comments and the one from Jan-Gerke as well. |
mediremi
left a comment
There was a problem hiding this comment.
The data engine's validation helpers need to be updated so that jsonPatch is a valid type:
|
Is this intentionally still a draft ? |
|
@varl should be good to rereview now |
| | Property | Type | | Description | | ||
| | :----------: | :----------------------------: | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| | **type** | _string_ | **required** | The type of mutation to perform, must be one of `create`, `update`, or `delete`. | | ||
| | **type** | _string_ | **required** | The type of mutation to perform, must be one of `create`, `update`, `jsonPatch`\*, or `delete`. | |
There was a problem hiding this comment.
These types were intentionally kept abstract here (CRUD vs. POST/PUT/PATCH/DELETE/GET) and jsonPatch goes against that intent, as it goes beyond saying what to do by saying how to do it.
The "update" type could be either a PUT or a PATCH under the hood, or, in this case, a "PATCH + json-patch+json".
So conceptually I'm not sure about the jsonPatch type. It doesn't belong as a type on the same level as the others.
There was a problem hiding this comment.
I keep flip-flopping on if I consider json-patch a type of mutation in the same vein as CRUD operations.
Conceptually I want it under the "update" mutation, however the implementation is convincing in its simplicity.
There was a problem hiding this comment.
So you would prefer useDataQuery to remain at its current level of abstraction and hide the format of the data being sent to the server? And I guess if/when we support graphql we also wouldn't want a type of graphql so makes sense.
There was a problem hiding this comment.
I can see why you wouldn't want jsonPatch as its own type as it doesn't really match with the CRUD types.
But the CRUD types are declarative, which means that the actual implementation is hidden/inaccessible/uncontrollable.
jsonPatch on the other hand is an imperative instruction that tells the server what exactly it should do, so my preference would be to not "hide" it behind a declarative type, I think that it would even violate consistency as we'd be mixing declarative CRUD types with an imperative "handle".
When it comes to translating this to the graphql world, then there's nothing that'd stop us from using jsonPatch as a type. Graphql only knows query and mutation. What kind of mutations exist depends on the schema. Normally there'd be a create/update/delete mutation for every type (e.g. createDataElement, updateDataElement and deleteDataElement; actually there can be a lot more, like linkDataElementWithDataSet for example). There's nothing that prevents us from adding patchDataElement/jsonPatchDataElement.
There was a problem hiding this comment.
Right. I think where we see things differently is if the request payload is part of the Data Engine's (our) API or the Server's (their) API.
My perspective is that the payload itself is not part of our API. It's determined by the server in all cases.
So on our side, the developer will declare what they want to do (e.g. update), and pass along a payload. That's declarative as far as our API goes.
If that payload is an "sequential instruction set" we pass that along to the server in the proper way.
If that payload is something else (e.g. GQL), we pass that along in the proper way without the user having to think about the specifics of how a request is sent to the server.
In this case, the trade-off is that a developer has to know that we support json-patch instruction sets when using the update type. This is consistent with what the developer currently needs to know when s/he works with the API through the Data Engine so I am OK with the trade-off.
| if (type === 'delete' && query.data) { | ||
| errors.push("Mutation type 'delete' does not support property 'data'") | ||
| } | ||
| if (type === 'jsonPatch' && !Array.isArray(query.data)) { |
There was a problem hiding this comment.
Are there other introspective checks we can do on the data to make sure that we are dealing with a json-patch request body if the type is 'update' ?
There was a problem hiding this comment.
I guess we know the shape of a patch array, so we could make sure that the children are sane.
There was a problem hiding this comment.
yep we could check if query.data is an array, whose values are objects with an op key containing one of the following values: "add", "remove", "replace", "move", "copy", "test" (see https://datatracker.ietf.org/doc/html/rfc6902/#section-4)
There was a problem hiding this comment.
Nice, that seems like a solid validation to do.
|
@HendrikThePendric thanks for putting this together! Our mutation types are all CRUD verbs ( |
I thought this had been discussed already and that we preferred an explicit approach. Perhaps I have misremembered. I would be on board with this alternative approach too, but I do see some potential downside with determining the request-type based on the shape of the The type of thing I outline above would not be possible if we expect the data to already be a patch array when it is sent to the data-service. But I guess we could always expose other hooks/helpers to aid the creation of patch arrays.... |
Why would it not be possible? If we see an |
Just so it doesn't look like I simply ignored Austin's question.... Austin and I had a chat about this on Slack and there was a slight misunderstanding on my side: If we assume all supported DHIS2-core versions have working JSON Patch support then automatically converting the |
|
I'll take a stab at this while @mediremi is off - if anyone has opinions on my comments above let me know |
|
Possible alternative name... instead of a mutation with |
|
I thought the API design choice for using "CRUD" (create, read, update, delete) is that it is very easy for the consumer to understand what they do, and therefor when they should be used. Adding "patch" into that makes it less so. "Patch" is basically "Edit", which is covered under "Update", so it pushes the complexity to the user for the sake of less complexity in the implementation. If less complexity in the implementation is the design goal, then "CRUD" isn't the optimal convention; instead we can pass through the HTTP verbs and deprecate the CRUD pattern. |
|
I actually think it makes *less* complexity for the user. Right now we
have CRUD (create, read, update, delete) but `update` has two modes of
operation - complete (replace, default) or partial (patch, currently with
`partial: true` which we want to get rid of). We're considering replacing
that with a subtle behavior change based on whether the user passes an
object (replace) or an array (patch). I think it's actually much cleaner
to have separate verbs for replacing versus patching (modifying, editing,
partially updating)?
…On Mon, Dec 6, 2021 at 1:07 PM Viktor Varland ***@***.***> wrote:
I thought the API design choice for using "CRUD" (create, read, update,
delete) is that it is very easy for the consumer to understand what they
do, and therefor when they should be used. Adding "patch" into that makes
it less so.
"Patch" is basically "Edit", which is covered under "Update", so it pushes
the complexity to the user for the sake of less complexity in the
implementation.
If less complexity in the implementation is the design goal, then "CRUD"
isn't the optimal convention; instead we can pass through the HTTP verbs
and deprecate the CRUD pattern.
—
You are receiving this because you were assigned.
Reply to this email directly, view it on GitHub
<#1023 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAHHNMCJGV4NZ5SKVB7S673UPSYPZANCNFSM5EQTNW3Q>
.
|
|
The problem is that "update" as a term covers both "full updates" and "partial updates" so having "update" as a verb and having "patch" doesn't respect the abstraction we have chosen. That level of abstraction is "create - read - update - delete", and "patch" is a lower level of abstraction because "patch" is a kind of update. I don't see how they can live on the same abstraction level conceptually. If we go with a new type for json-patch, I think the original proposal of using The Some examples below for how it can fit into our abstraction level, and how it can look if we decide to lower the abstraction level. With our current level of abstraction:Default (replace): {
type: 'update',
partial: false, // PUT + json
data: {},
}Standard PATCH + json (edit): {
type: 'update',
partial: true, // PATCH + json
data: {},
}JSON Patch + json (edit): {
type: 'update',
partial: 'json-patch', // PATCH + json-patch+json
data: [{}, {}, {}],
}Lower level of abstractionDefault: {
type: 'put',
data: {},
}Standard PATCH + json: {
type: 'patch',
data: {},
}JSON Patch + json: {
type: 'json-patch',
data: [{}, {}, {}],
} |
|
What about deprecating `update` in favor or `replace` and `patch`?
…On Tue, Dec 7, 2021 at 9:32 AM Viktor Varland ***@***.***> wrote:
The problem is that "update" as a term covers both "full updates" and
"partial updates" so having "update" as a verb *and* having "patch"
doesn't respect the abstraction we have chosen.
That level of abstraction is "create - read - update - delete", and
"patch" is a lower level of abstraction because "patch" is a kind of update.
I don't see how they can live on the same abstraction level conceptually.
If we go with a new type for json-patch, I think the original proposal of
using jsonPatch is a good choice with the least surprises from a consumer
perspective.
The partial concept is not bad to me either. Since updates can be done in
many ways, having a specifier for how to update is warranted. A boolean for
partial isn't ideal though.
Some examples below for how it can fit into our abstraction level, and how
it can look if we decide to lower the abstraction level.
With our current level of abstraction:
Default (replace):
{
type: 'update',
partial: false, // PUT + json
data: {},}
Standard PATCH + json (edit):
{
type: 'update',
partial: true, // PATCH + json
data: {},}
JSON Patch + json (edit):
{
type: 'update',
partial: 'json-patch', // PATCH + json-patch+json
data: [{}, {}, {}],}
Lower level of abstraction
Default:
{
type: 'put',
data: {},}
Standard PATCH + json:
{
type: 'patch',
data: {},}
JSON Patch + json:
{
type: 'json-patch',
data: [{}, {}, {}],}
—
You are receiving this because you were assigned.
Reply to this email directly, view it on GitHub
<#1023 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAHHNMADUJQ32D2Y6C7TJ43UPXID5ANCNFSM5EQTNW3Q>
.
|
|
Related to dhis2/notes#330 |
# [3.4.0](v3.3.0...v3.4.0) (2022-03-15) ### Features * **data-service:** add json-patch support ([#1023](#1023)) ([cdcdf24](cdcdf24))
|
🎉 This PR is included in version 3.4.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
This introduces JSON Patch support for the
useDataMutationhook, via an explicit type. Thedataproperty needs to be a valid patch array. It is entirely up to the user to ensure this patch is valid, we do no form of validation in app-runtime.A JSON Patch mutation object would as follows: