-
Notifications
You must be signed in to change notification settings - Fork 890
Refactoring in preparation for JSON API v1.0rc1 #234
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
Conversation
I merged one or two of the other PRs, can you rebase this real quick 😭 |
Also, @dgeb maybe put a link to https://github.com/dgeb/json-api/blob/v1rc1/format/index.md at the top so we can see the rendered version more easily? |
Disclaimer: I came here specifically because I'm invested in the error API.
I am super happy with the work here except for the above. I seriously believe these are 4 ways of saying the same thing. Though I do admit
And I've just never liked repeating HTTP Status Codes in the body of my message. It's in the header already. |
I can certainly see that. In my mind,
Does that make sense? Then again, you're right, the client could
But that seems excessive.
Right, you'd have something more specific. '409 conflict' comes with a body that says 'you forgot the required value foo'. Does that make sense? I can certainly see it your way, just laying out some of the why. |
Oh, also cc @mjallday and crew (it won't let me @balanced/engineers here for some reason), since Balanced is one of the bigger deployments of JSON API. AFAIK, they should still be compatible, just using some |
So it's HTTP status codes for individual leaves (fields in Rails case)? Bleergh, like most status codes don't apply for that sort of thing. Seems like shoe horning, but alright, maybe that's a thing that needs to happen. That said, throwing an MAY on the Eh, maybe it's no big deal, but I err on the side of cut these days. |
I'm using the HTTP codes as an example, not that they should be HTTP status codes. They're application-specific codes, as I think the spec mentions.
+1, I wouldn't mind MAYing them. |
Yeah, okay, then I revert to my original |
I'm a little concerned with the change that a singular resource still would have a plural "key". This seems inconsistent with the fact that one to one relationships have a singular key. It also seems inconsistent that an error resource should be an array always where as normal resources should be an object. |
This is basically for backwards compatibility with previous versions. You
Hmmmmm. |
Backwards compatibility as part of a BREAKING CHANGE of having the singular resource be an object? Can you please expand on that future as to what compatibility exactly this maintains?
For all resources? My reading made it sound like only variable type resources should have the "data" as a key. Singular type resources SHOULD have either the type as the key OR data as the key (with type based key listed as the first option) |
@krainboltgreene Thanks for your feedback re: errors. All of the members of an error object have a unique purpose and all are optional.
While a general |
@steveklabnik - @wycats and I backtracked on SHOULD for using |
@kjg oh, the heartburn that this minor issue has given me over the past few months.... We tried every manner of approach to resolving the singular / plural inflection issue and every solution had a problem with it (which I should write up thoroughly). We came to the conclusion that this is not about singulars and plurals as it is a string that represents a "resource type". For some APIs this might be singular; for some plural. Because most URLs reference plural collections, all the examples use the plural form. One of the solutions was to simply use |
Independent of the use of the |
@dgeb Alright, those explanations worked for me, though I'm not sure I'm cool about |
@andrewsardone Wouldn't this be |
@krainboltgreene: just like in Variable Type Resource Representations, I think plain ‘ol resource objects should include a {
"posts": {
"id": "1",
"title": "Rails is Omakase",
"type": "posts"
}
} (I would also always include the |
* New introduction explains the basics requirements and goals of JSON API. It also broadly explains the new optional PATCH support and how "JSON API represents all of a domain's resources as a single JSON document that can act as the target for operations". * New "Conventions" section explains SHOULD, MUST, etc. keywords * New "Document Structure" section describes the JSON API media type (application/vnd.api+json). This media type is used for both request and response documents (except for PATCH requests/responses). * Introduces the option to key the primary resource by the generic `data` key. This key SHOULD be used for variable type (i.e. heterogenous) resource collections and MAY be used for constant type (i.e. homogenous) resource collections. * Clarify different representations allowed for singular and plural resources. * BREAKING CHANGE: Singular resource objects SHOULD now be be represented with JSON objects instead of arrays. This allows for symmetrical representations in request and response documents, as well as PUT/POST requests and PATCH operations. It also simplifies implementations which do not support batch operations (i.e. they can allow an object and not an array). * Define URLs for resources, collections of resources and resource relationships. Allow for alternative URL definitions to be specified in responses. * BREAKING CHANGE: Allow the baseline implementation of JSON API to operate via POST, PUT and DELETE alone (no PATCH required). This introduces brand new specs for updating resources via PUT and updating relationships via POST and DELETE requests to newly specified relationship URLs. It also specifies how resources can be created, updated and deleted in bulk (but only per type). * Introduce alternative JSON Patch syntax for all operations. This builds off the current spec's approach to updating relationships. JSON Patch bulk operations are discussed. * Introduce a "Filtering" section in "Fetching". Since we encourage keeping all resources accessible at the root level, it corresponds that root level filtering should be encouraged instead of filtering via nested routes. Furthermore, root level filtering is more flexible because it allows for more than one filter to be applied to a collection. * Introduce a `clientid` key that can be used to correlate a resource on the client with a newly created resource on the server. * Introduce error objects, which are specialized resource objects that MAY be returned in a response to provide additional information about problems encountered while performing an operation.
Instead of hiding `a` elements except on hover, display the ¶ tag as :after content on :hover. This keeps the elements always on the page and allows the links to work in all scenarios.
{ | ||
"data": [{ | ||
"id": "9", | ||
"type": "people", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might not be looking in a correct place, but it seems like object types are named in the plural? Is that a JSON Api convention?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The resource type should match the name of the collection to which it belongs as defined by its URL. It's conventional to use the plural name of resources in URLs, which is why all the examples in this document follow that convention. However, if you accessed "people" at the /person
URL, then the resource type would be "person".
I'm philosophically opposed to forcing people to include a type unless it's required to resolve an ambiguity. HTML doesn't require you to type its responses, nor does regular old JSON. If we have to, we have to, but I think it's best if it's optional. |
@steveklabnik I agree. To be clear: in this PR, Of course, individual implementations may choose to include |
Eh, really disappointed to see reversion back to polymorphism. Is there rationale besides "It matches requests"? Strongly-typed APIs are really nice to work with. As for the errors, I note that we've stripped the ability to say "this part of your request is bad", leaving us just with "your request is bad and you should feel bad." Kind of disappointed about that, too. Not sure why we wouldn't point to a specific field or parameter using JSON pointers to say "here's what's wrong". Otherwise, has anyone tried actually building--for example--a simple registration form with these error constraints? How do you display to the user that the "email address" field is not a valid email format? How do you display that the username has unacceptable characters? Are we really suggesting that codes should be unique to errors and fields? I've tuned out a bit, so I'm probably missing a lot, but we seem to have some compromises here that just... don't make sense. The errors object specifically looks like design-by-committee instead of an actual purpose-driven design. |
@paddyforan this is exactly why this PR is open. Let's work through that stuff! The previous stab at errors was too simple, and instead of not causing bikeshedding, caused bikeshedding. Nothing here is sacred! |
After a bit more consideration, my reservations on errors is that it tries to do a great many things and does none of them well. The data being returned as "an error", as far as I can tell, is meant to:
Has anyone actually tried building an application that consumes an API that would meet these specifications? It seems very wishy-washy to me and hard to get right at this point, but we're going to pin a 1.0 on it? If I had to make a suggestion for the errors, I'd say let's pick one or at most two purposes for the error returned and make sure it does a great job of them. Which means not everyone will get what they want, I imagine, but making errors good at something is probably better than making them good at nothing. |
@paddyforan - thanks for the feedback!
The default approach in this spec is to use strongly typed resources. However, I see polymorphic collections and relationships as a reality for many APIs, so it's important for the spec to address them. A common example is an activity stream that comprises multiple types of activities.
Actually, each error resource can provide specificity to the developer and the end user regarding the error: The developer can use the The The The The I really should expand this section with examples, which I think could make these choices more clear. I'll try to come up with some soon - I think they'd be worthwhile in the spec.
I share your reservations about hardening this spec immediately after so many changes. My preference would be to treat this as a release candidate. If this looks like it's 90% of the way there, let's merge it, kick the tires over a set period, and incorporate feedback. Of course, any feedback before a merge is welcome too :) |
@paddyforan Your point is well taken regarding the tension between these design goals. I've added a clarification. @ahacking I've also added a clarification regarding resource URLs as well. The same rules apply to determining URLs and PATCH paths - they are complementary when forming JSON Pointers to identify a resource. |
Yes those discussions are exactly what I was getting at. You need patch compatible structures. This seems to require client assigned ids such as v4 uuids since the client can't invent a server assigned id unless a special client id value space is reserved which hopefully does not clash with existing ids. If anyone has ids based on say name or title this can never be workable without an escaping/encoding mechanism eg prefix client ids with '_' and if any id client or server has leading underscores then use double underscore for all leading underscores. This always means an odd number of leading underscores for client ids. The use of an api which allows client assigned ids has different usage than one that uses server assigned ids. With v4 uuids its the cleanest possible solution, with cids you have an additional mapping for whatever the server assigns and it can make web clients that have browser urls for resources behave in unexpected ways as cid's are typically ephemeral and unlikley to be persisted. So where does JSON API draw the line? |
@ahacking please see the sections on IDs and client-generated IDs. |
@dgeb yes that confirms the issue for me. If collections are represented in a patch compatible fashion then both 'clientid' and 'id' share the same value space as the property names on the object representing the collection. There needs to be a way to distinguish and separate clientids from server ids so clashes never occur when using patch compatible structures. |
@ahacking This seems to be an pretty narrow problem that should only come up when performing a bulk patch operation in which a resource is both created and then updated or deleted in the same request. The creation / deletion scenario seems really rare and slightly nuts:
The creation / update scenario seems more likely as part of a bulk sequential update:
The far more common scenario is that multiple resources are created and linked in the same request:
In the last scenario, So, what do we do about the first two scenarios?
I do think that this is a pretty narrow problem best solved in its own issue and not a showstopper. |
This thread is growing quite long. My preference would be to merge this sooner than later, treat it as an RC1, and commence discussions in more focused issues. I have a very busy week coming up, starting now, and won't be able to be as active here as I have been. I'm fine putting this on ice for a bit as well if everyone wants to give it more time / consideration. I still want to consider some of the finer points myself, like the clientid issue above, which I don't see as a blocker. @steveklabnik @wycats - thoughts? |
@gdeb I am thinking the simplest use case of adding a resource with a related resource using PATCH with patch compatible structures (ie no collection arrays, just objects with ids as keys).
Note the use of JSON Pointers with a patch compatible collection approach using clientid values as keys, instead of a collection array. Also the use of links with a JSON pointer. When you use the 'id' or 'clientid' value as a key in the JSON Pointer then you have to worry about clashes between real ids and client ids and need a mechanism to separate and distinguish them. I suggested an example prefixing (somewhat private) client ids with underscore to distinguish from real ids in JSON Pointers. However you need to handle cases where ids actually have a leading underscore, hence suggesting use of double underscores on leading underscores as an escape mechanism. By this logic a client id will always have an odd number of leading underscores. I don't particularly care what the escaping mechanism is, just that you need one if you want to use JSON Pointers with combinations of real ids and clientids. I don't like any of this really, but JSON Pointer and JSON Patch are what they are and using array indexes to reference collection items is unworkable outside of a single resource and is still a problem if the resource has changed. PATCH needs to work against a reference model where collections are objects, which means collections should be objects and not arrays, and links should be JSON Pointers too. The other option is borrow what you can where sensible from those RFC's but do what actually works best and report findings into the RFC process. From where I stand JSON Patch and JSON Pointer have some severe assumptions and limitations and we are trying to work around it but is not exactly clean. I am witnessing all the XML things being repeated in JSON... here we go again... |
@ahacking You raise good points.
Yes, we are bending JSON Patch and Pointer to our purposes but aren't completely compliant if we are adding to
Yep - the three options above |
@dgeb Another thing that I wasn't clear on was at what point does the If the response returns objects, do those objects use the My thinking is that if I do a POST/PUT/PATCH and the created/updated objects are returned in the response and then I later do a GET, I would like the response to be consistent aside from carrying a |
@ahacking Any time
Objects that don't have ids should never be referenced as members of a collection. |
hey @gdeb - sorry that you got dragged into this :) |
The more I look at this the more I am tempted to say EVERY object has a v4 UUID and go and play somewhere else if you want to make life hard. But I realise that such a hard nosed approach requires the database to use v4 UUIDs as their primary keys or to carry (v4) UUIDs. You can't easily add JSON-API support to existing datasets but if JSON-API is setting a good example maybe that is a sensible line in the sand. If I was to profile JSON-API without a doubt I would mandate (v4) UUIDs everywhere because of the clear advantages at all levels of the stack, they are invariant, clients can build complex object graphs, servers can easily shard for scalability. I think JSON-API should either say all resources have a (v4) UUIDs, there are no client ids, OR we bear the cost of maintaining two profiles, JSON-API and JSON-API-LIFE-IS-HARD. I think it is important if you want to support the life-is-hard approach then structure the spec as two separate profiles to keep the easy way clean and the all the ifs buts and maybes in the life-is-hard spec. |
@gdeb sorry... was messaging on my phone without the nice auto-complete. |
@ahacking I agree that v4 UUIDs are simpler conceptually and provide many advantages. I was trying to provide a bridge for implementations that don't support UUIDs, but this approach does have its own costs for implementation. If this non-UUID clientid solution is seen by you and others as too half-baked and/or complicated for inclusion, I'm fine with removing it from the PR and we could discuss it separately. |
As far as the earlier discussion re: PATCH / Pointer, there really is no good alternative to allowing |
@dgeb. Agreed. I will always use uuids but I really would like a simple one way of doing things. Nail the plural singular thing (I prefer plural always as it matches endpoint names) I always want a collection object keyed by id as the definitive reference model given we want to use JSON pointer. I want this even for singular resources. Uniform structure always regardless of cardinality. I am happy to have links in a nested object so that generic processing logic can wire things up. I think there is a need for meta info in objects however and that is how I have carried errors to date in my APIs but I'll go along with problems and JSON pointers. I do want the ability to have custom attributes in my errors/problems which could be useful depending on the type of error (eg could be suggestions for alternative names or other hints for validation errors). I am really wanting simplicity, one way for everything and minimum repetition hence not wanting id in the object but on the collection key since it has to be put there for JSON pointer to work generally. Then we have a uniform structure, just resources housed in collection objects which are housed in a root object and JSON pointers to link up the object graph. As for polymorphic concerns I think let users define whatever named collections they like and don't worry about prescribing types. Its only when you describe a generic data collection in the spec that it became a concern. I would stay silent on it. If people want to use a brown bag let them and let them decide how to distinguish types. The id will need to be unique across types because all types will share the same key space when they are all in the same object and that is again another win for (v4) uuids and a whole lot of stuff JSON API can stay out of. |
Thanks for everyone's feedback. I'm going to explore a simplification of this PR in a new branch, which may not be complete for another week or more. I do think that there's some fat to be trimmed from the spec and some ideas that could be more fully baked. I'm primarily concerned with polymorphism and client ids. Please feel free to continue to comment here or on other issues. As I mentioned above, I've got a busy week ahead and may not be as responsive as I've been. |
@dgeb Cheer's, I look forward to seeing the updated spec |
I need an example of what an error response looks like. I wasn't able to reason with the given description. |
Please review #237, which represents the simplification of this PR that I promised. The differences are contained in dgeb@8f831e9 |
Closing in favor of #237 |
Note: see the revised format page to review the heart of this PR.
This is a fairly thorough rewrite of the spec that formalizes its structure and addresses many outstanding issues.
The goal of this rewrite is to prepare JSON API for 1.0 versioning with a release candidate phase. We would like to shake out any breaking changes during this RC phase to provide implementers the degree of confidence they deserve from this spec.
Any and all feedback is welcome, either in this PR or in the specific issues that it addresses.
Changes include:
also broadly explains the new optional PATCH support and how "JSON API
represents all of a domain's resources as a single JSON document that can act
as the target for operations".
(application/vnd.api+json). This media type is used for both request and
response documents (except for PATCH requests/responses).
data
key.This key SHOULD be used for variable type (i.e. heterogenous) resource
collections and MAY be used for constant type (i.e. homogenous) resource
collections.
JSON objects instead of arrays. This allows for symmetrical representations in
request and response documents, as well as PUT/POST requests and PATCH
operations. It also simplifies implementations which do not support batch
operations (i.e. they can allow an object and not an array).
relationships. Allow for alternative URL definitions to be specified in
responses.
POST, PUT and DELETE alone (no PATCH required). This introduces brand new
specs for updating resources via PUT and updating relationships via POST and
DELETE requests to newly specified relationship URLs. It also specifies how
resources can be created, updated and deleted in bulk (but only per type).
the current spec's approach to updating relationships. JSON PATCH bulk
operations are discussed.
resources accessible at the root level, it corresponds that root level
filtering should be encouraged instead of filtering via nested routes.
Furthermore, root level filtering is more flexible because it allows for more
than one filter to be applied to a collection.
clientid
key that can be used to correlate a resource on theclient with a newly created resource on the server.
returned in a response to provide additional information about problems
encountered while performing an operation.