-
Notifications
You must be signed in to change notification settings - Fork 925
Multiple/Hierarchical Types #862
Description
Sine the beginning, JSON API has treated each resource as having one, and only one, definitive type, which is part of the resource’s identity.
I didn’t see much debate about this design decision, and I know I never questioned it, but I think some problems with it have started to bubble up recently. I want to lay those out here, as I see them, and explore some solutions.
The first problem is that a resource can, conceptually, belong to multiple types (likely a sub-type and one or more parent types), but the spec has never defined a standard way for a resource object to communicate this multiplicity. This issue was first raised a couple years ago, but was closed without any resolution; more recently, it’s come up in issue #571.
The value of having a first-class type field is that the client can use it for a contract. But if the client doesn’t know all the types that a resource belongs to, it can’t do this. For example, if I have organizations and, as a subtype, schools resources, the client must know that schools are organizations, in order to accept them in places where organizations are accepted. (And simply returning the schools as organizations won’t work, because then there’s no way for me to tell the client that a given relationship can only hold schools.) So, the client needs to know all of a resource’s types, and the only way for that to happen right now is with manual configuration on the client-side, which is couples the client and the server much more than necessary. (E.g. the server can’t introduce a new subtype without all clients breaking.)
A second, related issue is that a resource might want to change types (in a way that wouldn’t violate any interface contract, like changing from Type A to Subtype of A), and this is impossible when type is part of resource identity. That difficulty was at the heart of #481. The resolution there was to always identify the resource by it’s parent type, and then to expose the sub-type and its fields, when they apply, in some api-specific way. This works ok except, of course, that the method for indicating the sub-type is still non-standard and, as discussed in that issue, the options for exposing the sub-type-specific fields aren’t particularly elegant.
The second issue in particular gets me thinking that type really shouldn’t have been part of resource identity. (We could have still had a type field for clients, but identity could have been established just by id; databases that scope their ids to the table/collection could have created the json-api id field just by concatenating the table/collection name with the record’s id.) But, alas, that ship has sailed. So, what can we do now?
Holding aside backward-compatibility for a second (I have some ideas for that below), I can think of two basic approaches:
- Continue to list each resource as having one type, but then separately (at the response’s top-level, or at a separate endpoint) define the relationships between different types, such that the resource’s single
typelabel can be used to derive all the other types that apply to it. So, for example, the resource would be listed as aschools, and some part of the response would say that allschoolsareorganizations. This approach might be more compact than the one below, but it seems a bit brittle (i.e. I don’t think it works easily if the type system isn’t a strict hierarchy) and it requires the client to do some reasoning. - For each resource object, list in some array all the types that apply to it. This seems like the simplest and most robust solution. It’s a bit redundant, maybe, but that’s why we have gzip. Note that this is the approach that Siren takes with it’s
classmember. It’s also the basis of the neo4j data model, which has undifferentiated graph nodes that can be grouped into sets, for easier querying, through the (mutable) application of labels.
If we use the second approach, one way to implement it would be to add a "labels" member to the top-level of resource objects, which would list of all the types that apply to the resource (and would be mutable to whatever extent the server allows). This would coexist directly with the existing type attribute, which would still be valuable for making some interface guarantees to the client.
So two resource objects might look like this:
{
"type": "schools",
"labels": ["organizations", "schools", "kindergartens"],
//...
}Above, the api is guaranteeing that the resource will always be a school through type, so the client doesn't need (for example) to GET it and rely on (currently non-existing) hypermedia controls to know how to send a patch. However, the labels field lets the server specify the other types—both organizations, which (hypothetically) existed as a type at the time the resource was created, and kindergartens, which is a type the server added later as its database of schools grew.
Below, the server solves the problem from #481 by adding/removing "presenters" from the labels as the person's status changes:
{
"type": "people",
"labels": ["people", "presenters"],
"attributes": { } //there are more fields here when `presenters` is among the labels
}Thoughts?
cc @json-api/owners