Reputation: 29004
A RESTful, hypertext-driven system needs to enable clients to create a new resource that depends on three or more resources of different types. What's the best method to expose this capability?
As an example, let's say I run an online store. The server knows about four resources:
When the Order is shipped, a client needs to record this event by creating a new Shipment on the server. The Shipment will require references to Destination, Order, and Packer.
To implement the creation of new Shipments, I can think of three approaches, and I don't like any of them:
I don't like Option 1 because the Shipment media type additionally defines links to the Order, Packer, and Destination. Here, a "link" is a JSON hash consisting of a human readable name, a URI, and a media type. Adding "order_uri", "packer_uri", and "destination_uri" to the media type doesn't seem very DRY because it duplicates the URIs for the associated resources.
Option 2 uses deeply-nested URIs, which neither look very maintainable nor capture any meaningful hierarchical information.
Option 3 places another level of abstraction between clients and the creation of Shipments, which makes the system harder to learn.
If a Shipment only depended on one other resource, Option 2 would make a lot more sense, but it doesn't in this case. As it stands, I favor Option 3, but would prefer something better.
In this example, what would be the best combination of URI and media type for creating a new Shipment? What other approaches should be considered?
Update: below is a JSON example representation of a Shipment resource showing links for order, packer, and destination. The URI duplication required by Option 1 appears in the "shipment" hash:
{
"shipment":{
"created_at": "Wed Sep 09 18:38:31 -0700 2009",
"order_uri":"http://example.com/orders/815",
"packer_uri":"http://example.com/packers/42",
"destination_uri":"http://example.com/destinations/666"
},
"order":{
"name":"the order to which this shipment belongs",
"uri":"http://example.com/orders/815",
"media_type":"application/vnd.com.example.store.Order+json"
},
"packer":{
"name":"the person who packed this shipment",
"uri":"http://example.com/packers/42",
"media_type":"application/vnd.com.example.store.Packer+json"
},
"destination":{
"name":"the destination of this shipment",
"uri":"http://example.com/destinations/666",
"media_type":"application/vnd.com.example.store.Destination+json"
}
}
The contents of the "shipment" hash (less "created_at" field) would get POSTed. When using GET, the full Shipment representation above would be be sent.
Upvotes: 7
Views: 2757
Reputation: 26129
I think option1 and option2 are fair solutions, and I would forget option3, since the previous ones are better solutions.
Your client should always decide by checking the semantics of the links (for example link relations and vendor specific MIME types) and not by checking the URL structure. You don't necessarily need vendor specific MIME types, you can use and RDF format, like JSON-LD and REST and application specific vocabs to describe your links and their input fields, your can use for example Hydra. You can use a custom solution either, for example add _fields to the _links.
There is nothing wrong by duplicated links. You can use gzip if the message size is too big. Btw you should not confuse the URLs with links, they are different things. URLs are resource identifiers, links are possible operation calls on a resource.
Upvotes: 0
Reputation: 391846
REST "hierarchies" don't mean anything. They're a convenience for navigation to show a relationship in the form of a path. Not a hierarchy per se, but a path. So, option 2 is actually sensible if you drop the "hierarchy" concept and recognize that there are many alternative paths to the same final location.
Your option 2 is a orders->packers->destination path. Theoretically, orders->destinations->packers and packers->orders->destinations, packers->destinations->orders, as well as a few others all lead the same place. Yes, it's a pain to support them all. However, it's proof that all of them are equivalent and there's no hierarchy.
"I don't like Option 1 because [it] doesn't seem very DRY."
So? Leave out the repetitive stuff. Why does a shipment have to also contain a complete repeat of the Order and Packer information? The URI references are sufficient to allow a lookup and retrieve Order and Packer. Why send Order and Packer at all?
"Option 3 makes the system harder to learn." For whom? Developers? You're designing your system around the developers, not the users and their use case? For shame.
The point of REST is (generally) that a URI is an absolute, final and eternal thing. Which alternative gives you the absolutely best URI structure? Recognize that URI's are not hierarchies but paths -- and objects can exist at the end of multiple alternative paths.
You're creating a shipment. POST to /shipment
. Simple, clear URI's are what matter.
Upvotes: 3
Reputation: 142034
Ok, now I understand where you are seeing duplication. Would it be feasible to POST the following?
{
"shipment":{
"created_at": "Wed Sep 09 18:38:31 -0700 2009",
"order":{
"uri":"http://example.com/orders/815"
},
"packer":{
"uri":"http://example.com/packers/42",
}
"destination":{
"uri":"http://example.com/destinations/666",
}
}
}
and return this
{
"shipment":{
"created_at": "Wed Sep 09 18:38:31 -0700 2009",
"order":{
"name":"the order to which this shipment belongs",
"uri":"http://example.com/orders/815",
"media_type":"application/vnd.com.example.store.Order+json"
},
"packer":{
"name":"the person who packed this shipment",
"uri":"http://example.com/packers/42",
"media_type":"application/vnd.com.example.store.Packer+json"
},
"destination":{
"name":"the destination of this shipment",
"uri":"http://example.com/destinations/666",
"media_type":"application/vnd.com.example.store.Destination+json"
}
}
}
Maybe this just does not work in JSON, but I do something similar with XML in my resources. The idea is that you can pass to the server a "reference" to a resource with just the uri filled in and the server populates the rest of the data in the object.
Upvotes: 1