Skip to content

Resource Versioning Profile #1333

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 27 commits into
base: gh-pages
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
df9838a
Create resource-versioning index.md.
gabesullice Dec 12, 2018
e815ccd
Add a "Resource Versioning" profile category
gabesullice Dec 13, 2018
c302a8c
Add all editors to this profile
gabesullice Dec 13, 2018
84d4840
Fix spacing
gabesullice Dec 13, 2018
ea9c13c
Use editor format from #1349
gabesullice Dec 20, 2018
9d9e263
move 501 to relative version section
gabesullice Dec 20, 2018
9368cbc
disambiguate bad version negotiator vs. argument errors
gabesullice Dec 20, 2018
d3c1d13
add trailing slash to error type URIs
gabesullice Dec 20, 2018
cac4baf
add linking and version history section
gabesullice Dec 20, 2018
56bc05e
fixup link and version history commit
gabesullice Dec 20, 2018
75659ce
add to and fix link example table
gabesullice Dec 20, 2018
2d5346e
typos and clarification
gabesullice Dec 20, 2018
c351d9c
change the query parameter from snake-case to camel-case
gabesullice Dec 20, 2018
3e4b67e
clarify that a one-segment version identifier is valid
gabesullice Dec 21, 2018
515aba9
invert definition of a version
gabesullice Dec 21, 2018
0feddd6
clarify the example narrative of a version history
gabesullice Dec 21, 2018
b0a1e3d
relax `type` link requirement
gabesullice Dec 21, 2018
f0d73db
clarify interoperability and optionality of version negotiators
gabesullice Dec 21, 2018
a4329d5
the first or only segment of a version identifier must be interpreted…
gabesullice Dec 21, 2018
7a352dc
restrict usage of the rel negotiator in resource objects' `self` links
gabesullice Dec 21, 2018
9129eb2
reduce redundancy
gabesullice Dec 21, 2018
ba9bf0b
use the `source` error object member in error details
gabesullice Dec 21, 2018
6bc95be
disallow custom rel-based version arguments
gabesullice Dec 21, 2018
1a1395d
reorder two lines
gabesullice Dec 21, 2018
e4adbd7
fix some ascii art
gabesullice Dec 21, 2018
5c5b0f4
fix italicization
gabesullice Dec 21, 2018
c82ecbe
update mateu's contact info
gabesullice Dec 21, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
change the query parameter from snake-case to camel-case
  • Loading branch information
gabesullice committed Dec 20, 2018
commit c351d9ceee9327bf10e6d48417b90a1792bab44d
22 changes: 11 additions & 11 deletions _profiles/drupal/resource-versioning/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ schemes.

## Usage

An endpoint **MAY** support a `resource_version` query parameter to allow a
An endpoint **MAY** support a `resourceVersion` query parameter to allow a
client to indicate which version(s) of a resource should be returned.

If an endpoint does not support the `resource_version` parameter, it **MUST**
If an endpoint does not support the `resourceVersion` parameter, it **MUST**
respond with `400 Bad Request` to any requests that include it.

If an endpoint supports the `resource_version` parameter and a client supplies
If an endpoint supports the `resourceVersion` parameter and a client supplies
it:

- The server’s response **MUST** contain the most appropriate version of the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "the most appropriate" is unnecessarily vague. Can't we change the most appropiate version to the requested version?

Expand All @@ -95,7 +95,7 @@ it:

## Format

The value of the `resource_version` parameter **MUST** be a colon-separated
The value of the `resourceVersion` parameter **MUST** be a colon-separated
(U+003A COLON, “:”) string. The first segment of the string **SHOULD** be
interpreted as an identifier for a _version negotiation mechanism_. A version
negotiation mechanism defines how a server will locate an appropriate resource
Expand All @@ -113,7 +113,7 @@ parameter value is known as the _version identifier_.
version-identifier
_______|_________
/ \
?resource_version=rel:latest-version
?resourceVersion=rel:latest-version
\_/ \____________/
| |
version-negotiator |
Expand All @@ -138,13 +138,13 @@ cannot be located, the server **MUST** respond with a `404 Not Found`.

# Links

When a server processes a request with a `resource_version` query parameter and
When a server processes a request with a `resourceVersion` query parameter and
a `self` link is provided for a top-level links object, the link's `href`
**MUST** include the `resource_version` query parameter with the same version
**MUST** include the `resourceVersion` query parameter with the same version
identifier that was requested.

When a server processes a request with a `resource_version` query parameter
all resource object `self` links **SHOULD** contain a `resource_version` query
When a server processes a request with a `resourceVersion` query parameter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Design] My biggest piece of feedback about this profile is that it should probably also consider how to update resources (e.g., how to create a new working copy based of an existing one, or change the default version, or reject a PATCH if the resource's working copy has changed from the working copy that the client is trying to apply its PATCH to, etc). When resource versions have come up in the past, these updating use cases were actually the primary motivation, so not addressing them here would seem like a major missed opportunity that might be hard to remedy once this is published.

I've put this comment on this paragraph, though, because I think the considerations about updating bear directly on what the self link should be. If the self link is URL of a specific revision (as it seems to be here), then a PATCH to that link would be interpreted as updating the revision -- which should probably usually be immutable -- and creating a new revision would become a POST (or a patch to the default version's URL). So that's something to think about.

Also, if each revision is it's own HTTP resource (i.e., has its own URL), then the JSON:API id would usually be different for each revision too (e.g., so a PATCH actually could target the revision), though I suppose it wouldn't have to be. Distinct ids could cause lots of problems, though, because presumably only the default version's type/id would want to be used in relationship objects.

So this is all stuff to think about... I don't have any conclusions at the moment, but I'll mull it over, and hopefully you guys can too. I think, as usual, it goes back to the weird relationship that JSON:API resource objects have with HTTP's concepts of resources + entities.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which should probably usually be immutable

I think it makes sense to treat revisions as immutable.


I agree with all that was said in the comment above, but I want to highlight another implicit way of creating revisions that we may need to be explicit about.

Many frameworks handle revision creation as part of the store update. In Drupal when you save an entity that supports revisioning by PATCHing it (no resourceVersion used here), then a new revision will be created for you. You cannot do much about it in most of the cases. This new revision will be a working-copy or a version depending on some custom business logic (usually a published: true / false flag).

I guess that what I'm saying is that strict regulation of how revisions are created may become hard. However, we can put language on how to create revisions through JSON:API.

Where I'm going is, do we want this profile to include revision creation or it can be a separate profile?

Copy link
Contributor Author

@gabesullice gabesullice Dec 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Not] addressing [PATCH/DELETE] here would seem like a major missed opportunity that might be hard to remedy once this is published.

I think I agree.

Where I'm going is, do we want this profile to include revision creation or it can be a separate profile?

As above, I think I would like this profile to define how update/delete should be handled.


I think the considerations about updating bear directly on what the self link should be. If the self link is URL of a specific revision (as it seems to be here), then a PATCH to that link would be interpreted as updating the revision -- which should probably usually be immutable -- and creating a new revision would become a POST (or a patch to the default version's URL).

I think I may be in a minority about this, but I try to deeply appreciate the difference between a top-level self link and a resource object self link. I feel that once one groks what it means when they're different, the way JSON:API reconciles resource objects with HTTP resources + entities becomes much easier to think about.

Where I'm going with that is pretty simple: the top-level self link should be the URL used to mutate a resource object, not the resource object self link. Once you establish that, things become pretty simple.

GET /article/1?resourceVersion=rel:working-copy

{
  "data": {
    "type": "article",
    "id": 1,
    "links": {
      "self": "/article/1?resourceVersion=id:42"
    }
  },
  "links": {
    "self": "/article/1?resourceVersion=rel:working-copy"
  }
}

That means that a PATCH would update the working copy via the top-level self link, whatever it may be, like so:

PATCH /articles/1?resourceVersion=rel:working-copy

{
  "type": "article",
  "id": 1,
  "attributes": { // some changes }
}

The server response would then be:

200 OK

{
  "data": {
    "type": "article",
    "id": 1,
    "links": {
      "self": "/article/1?resourceVersion=id:43" // <- incremented
    }
  },
  "links": {
    "self": "/article/1?resourceVersion=rel:working-copy" // <- unchanged
  }
}

The working-copy link could be used instead of the top-level self link if the GET request was made directly to a resource object via an over-specific version negotiator (this would be the case if one visited /articles/1?resourceVersion=id:42 directly for some reason).

which should probably usually be immutable

I think it makes sense to treat revisions as immutable.

I think it makes sense too, but I don't think this profile needs to define that. I think it can work either way.

Drupal actually does permit updating a revision in-place (whether that's a good idea or not is beside the point 😛 ). With the scheme above, it would be possible for a server to support both in-place editing of a revision or new revision creation via PATCH depending on the self link used (top-level vs. resource object).

If a server cannot automatically create new revisions via PATCH for some reason (maybe it can't/hasn't implemented the rel negotiator), then I think the appropriate URL for creating new revisions would be at the version-history URL via POST. (this is why it makes sense for it to be a collection resource).

Copy link
Contributor Author

@gabesullice gabesullice Dec 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The language about updating resources in the spec seems to jive with what I suggested:

The URL for a resource can be obtained in the self link of the resource object. Alternatively, when a GET request returns a single resource object as primary data, the same request URL can be used for updates.

We could go a step further in that language by adding:

the same request URL or the top-level self link can be used for updates.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @ethanresnick that the fact that this profile's narrowness (only defining "read" behavior) is its biggest weakness.

(Similarly, I think #824's biggest weakness also was its narrowness, it just was different: it focused on optimistic concurrency control.)

On the other hand, I think solid read-only support and leaving modifications to a separate profile or a future iteration is preferable over having a spec that supports fewer use cases (which I think was true of #824it did not allow a particular revision to be retrieved). Especially if it's based on another established standard (RFC5829) and therefore likely to be more implementation-agnostic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fascinated by @gabesullice's proposal (and the sample response bodies really help, thanks! 🙏). I'm very curious to find out what @ethanresnick thinks about that :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or reject a PATCH if the resource's working copy has changed from the working copy that the client is trying to apply its PATCH to

I neglected to give a solid answer for this. As I said above, I think the PATCH should be sent to the top-level self link: /article/1?resourceVersion=rel:working-copy.

However, this spec could add something like an appliesToRevisionId: id:42 under the meta key of the request document.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reject a PATCH if the resource's working copy has changed from the working copy that the client is trying to apply its PATCH to

Support a conflict detection mechanism as described in initial review post wasn't discussed enough in my opinion. Something similar to AWS Amplify should be easy to achieve if already having well defined revisions. It was already discussed as part of #824 some time ago.

Maybe something like this would be everything needed:

Conflict detection when updating resources

A server SHOULD include the most specific version negotiator it supports in any resource object's meta information under revision key.
A client MAY include the revision an update is based on as under revision key of meta object on a PATCH request.
A server MUST reject 409 Conflict when processing a PATCH request to update a resource if a revision is provided as meta information, which is not the latest one.
A server MAY reject a PATCH request to update a resource if server has included a revision on retrieving the resource but the PATCH request does not include a revision.

all resource object `self` links **SHOULD** contain a `resourceVersion` query
parameter which identifies the specific revision represented by that resource
object.

Expand All @@ -157,11 +157,11 @@ same:
"type": "article",
"id": 1,
"links": {
"self": "/article/1?resource_version=id:42"
"self": "/article/1?resourceVersion=id:42"
}
},
"links": {
"self": "/article/1?resource_version=rel:latest-version"
"self": "/article/1?resourceVersion=rel:latest-version"
}
}
```
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy