Please do not read any of this with an "absolute" tone. These are merely my thoughts on a problem based in my current understanding and some experience. To be honest, I would not be surprised if this is already being done by individuals more experienced than I at building REST APIs, but I think it is worth discussing as I have yet to find anything like this blogged about. I present this with utmost humility in order to foster discussion; not to create devolving argument.
I believe that usage of media types in examples on the web demonstrating "how to build" RESTful APIs is too narrowly focused. In my experience, much of the discussion around them seems to be solely focused on their usage either in content-negotiation or applying versioning to an API.
As an aside, versioning can be "controversial" itself as an "anti-pattern" of REST. I heard it argued that HATEOAS should be the answer to the problem that versioning works to solve. This is an aside and not the focus of this ponderance so it's not worth discussion here. Versioning techniques currently discussed at-large are a practical way of solving a technical problem when strategies for offering RESTful API discoverability are not ubiquitous, yet.
Forgetting about versioning, Accept
headers are commonly used to specify expected (accepted) representation of a resource when a response is made to a client. This is reasonable and makes sense, but I think it is still too narrowly focused because most examples stop at "Use this to render in json, xml, yaml, whatever."
A resource is a >>uniquely identifiable abstraction<< exposed through an API that may allow various operations to be performed on it through HTTP verbs.
The resource itself is an idea separate from any technical implementation. It's an abstract concept that has some attributes and behaviour. When I ask a server for the application/json
representation of a particular resource, the key word is representation. The server is transforming the abstract (or internal) concept of a specific resource into a concrete format that matches a client's expectations for consumption.
Consider a human in the world: /human/6P6KC7jfMk6CIO7J
. This human has a name
and a birth_date
. Immediately after birth, this human matures and at some point attends primary school. Later they will graduate and may go to University or go straight into the workforce, but they will always have a name
and a birth_date
in the context of "being a human". However, people can be represented and be interacted-with in infinitely-many ways. In this horrid example we have:
- A person in the context of being human (this is the abstract uniquely-identifiable resource)
- A student in the context of attending a primary school
- A student in the context of attending University
- A laborer in the context of having a job
- ...
- A taxpayer in the context of being a citizen of some nation
- A funeral attendee in the context of being deceased (morbid, I know)
When thinking this way, the representations of a single resource in any of these contexts will vary in attributes and perhaps, textual formatting. In addition to response representation, they will vary in the sub-resources they contain and, possibly, the commands that are executable on them (use-cases based on semantics of the custom media-type representation).
All of the following are requested against /human/6P6KC7jfMk6CIO7J
"Being Human (application/vnd.api+json)"
When approaching rich media-types, this becomes a "raw data model" that is less useful. I'll explain why below all these.
{
"name": "Dustin",
"birth_date": "04-09-1902"
}
"Having a Job" (application/vnd.api.worker+json)
{
"name": "Dustin",
"salary": "chips",
"work_orders": [
...
],
...
}
"Being a student" (application/vnd.api.student+json)
{
"name": "Dustin",
"class": "Senior",
"major": "Computer Science",
"courses": [
...
]
...
}
So, when I stop thinking about response formatting and start thinking about semantics, I'm lead to identifying possible differences in the semantics of how I interact with resources and my ultimately my question:
Is it reasonable to separate use-cases on resources in different contexts through the use of rich media-types? This would include the API definition itself being contextually driven by the semantics of specific requests.
In addition, is there anything "being RESTful" has to say against something like this?
Under this type of usage, custom media-types can almost be thought of as a "third dimension" of a typical (as represented by examples on the web) "RESTful HTTP message".
Small example: A student adds a course to their schedule.
> POST /human/6P6KC7jfMk6CIO7J/courses HTTP/1.1
> Accept: application/vnd.api.student+json
> --
> {
> "name": "How to Go+Ruby+PHP+Rust 101"
> }
--- Resource server determines that this is a request to execute
--- a use-case on a student and handles appropriately.
< HTTP/1.1 200 OK
< Content-type: application/vnd.api.student+json
< --
< ...
The combination of a POST
to the application/vnd.api.student
representation of the /human/6P6KC7jfMk6CIO7J/courses
sub-resource starts the use-case: "adds a course to the student's schedule". The resource server will have a specific use-case flow for this; complete with request validation, execution and response.
However, if we attempt the same use-case on a different representation (aspect/context) of a resource, we get an appropriate 4xx
-level response. This happens because when we use a different representation, we're operating on a specific resource in a completely different context.
> POST /human/6P6KC7jfMk6CIO7J/courses HTTP/1.1
> Accept: application/vnd.api.worker+json
> --
> {
> "name": "How to Go+Ruby+PHP+Rust 101"
> }
--- Middleware would determine that the worker representation has no courses sub-resource
< HTTP/1.1 404 Not Found
...
Other 4xx
status codes could be justifiably used. In this case, I'm using 404
because the sub-resource doesn't exist but that's kind-of a cop-out honestly. We could probably find a way of responding in a way that is semantic to the requested use-case. Maybe another idea would be to respond with a 409
and a reasonble message explaining "why" there is a conflict ("Client is trying to add a course to a worker's class schedule. Doesn't make sense, buddy. Workers don't have class schedules.").
415
might make sense, but starts to speak about specifics you'd consider when reasoning about the HTTP specification in the context of a technology use to serve physical resources / assets through the web. This is where I think there's future discussion to be had and where I cut the initial dialogue. The first thing to discuss is whether or not it is "standard" or reasonable for resource representations to be used as a way of partitioning use-cases.
HTTP Status Reference
406 Not Acceptable
The resource identified by the request is only capable of generating response
entities which have content characteristics not acceptable according to the accept
headers sent in the request.
Unless it was a HEAD request, the response SHOULD include an entity containing a
list of available entity characteristics and location(s) from which the user or
user agent can choose the one most appropriate. The entity format is specified
by the media type given in the Content-Type header field. Depending upon the format
and the capabilities of the user agent, selection of the most appropriate choice
MAY be performed automatically. However, this specification does not define any
standard for such automatic selection.
409 Conflict
The request could not be completed due to a conflict with the current state of the
resource. This code is only allowed in situations where it is expected that the user
might be able to resolve the conflict and resubmit the request. The response body
SHOULD include enough
information for the user to recognize the source of the conflict. Ideally, the response
entity would include enough information for the user or user agent to fix the problem;
however, that might not be possible and is not required.
Conflicts are most likely to occur in response to a PUT request. For example, if
versioning were being used and the entity being PUT included changes to a resource which
conflict with those made by an earlier (third-party) request, the server might use the
409 response to indicate that it can't complete the request. In this case, the response
entity would likely contain a list of the differences between the two versions in a
format defined by the response Content-Type.
415 Unsupported Media Type
The server is refusing to service the request because the entity of the request is
in a format not supported by the requested resource for the requested method.