tags:

views:

256

answers:

2

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:

  • Order: The group of products to be shipped. [has one Shipment]
  • Destination: The location to ship to. [has many Shipments]
  • Shipment: The act of sending a Product to a Customer. [belongs to Destination, Order, and Packer]
  • Packer: The employee physically preparing the Order for Shipment. [has many Shipments]

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:

  1. POST to /shipments using the Shipment media type. The Shipment media type has three fields: "order_uri"; "packer_uri"; and "destination_uri". Each URI is being used as a unique identifier for the Order, Packer, and Destination involved in the Shipment, respectively.
  2. POST to /orders/{order_id}/packers/{packer_id}/destinations/{destination_id}/shipments using the Shipment media type.
  3. Add a new resource to the system called "ShipmentBuilder". POST to /shipment_builders using "packer_uri", "destination_uri", and "order_uri" contained within a ShipmentBuilder media type.

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.

+1  A: 

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.

S.Lott
Why do you believe that "The point of REST is (generally) that a URI is an absolute, final and eternal thing"? My understanding is that one of the reasons for hypermedia is to be able to allow the server to change Urls without breaking clients. Isn't that also why HTTP has the 301 response code.Sure "Cool Urls don't change" but sometimes we make mistakes in defining our virtual paths.
Darrel Miller
I like your description of URIs as paths not hierarchies. It is quite a different way of looking at things. The metaphor doesn't quite hold when a user clicks a "home" link though.
Darrel Miller
@Daniel Miller: Cool URI's can't change or everything breaks. A 301 is a real error, and a well-designed site doesn't do this (except as a browser-focused "redirect-after-POST" hack). You can think of "home" as anything you want; but don't let it confuse your RESTful design of URI's.
S.Lott
@SLott There is no such thing as "RESTful design of URIs".
Darrel Miller
@S.Lott, thanks for the input. Re Option 1: You may be right and the idea offered by Darrel might be the way to go. The repetition was caused by the way the original design separates Shipment attributes from Shipment Links, which is in turn seemed like the simplest way to do it in my framework (Rails). Might be time to re-think that. Re Option 2: it sounds like we're saying the same thing - all URI permutations are equivalent. I would be more inclined to pick one rather than maintain all of them, but the choice would be arbitrary.
Rich Apodaca
@S.Lott, Re: Option 3, this would be a Web API, so developers are the end users and their use cases matter the most. All options handle the business use case (creating a Shipment), but they differ in the amount of mental overhead needed to go from the API to a client to an application used by the shipping dept. Simply POSTing to /shipments may be the least surprising thing in this case.
Rich Apodaca
@Darrel Miller: Interesting point. Take it up with http://jwyseur.blogspot.com/2008/12/uri-design-for-rest.html, http://horicky.blogspot.com/2009/05/restful-design-patterns.html. It sure sounds like RESTful URI design to me. Perhaps you could clarify your comment with some explanation of why there's not RESTful design of URI's.
S.Lott
@S.Lott ...because Roy's dissertation specifies no constraints with regards to what an URL should look like. http://{F9B550A3-9C80-41a5-9118-2495A6A35FED} could be a perfectly RESTful url. The uniform interface constraint implies that if you are using HTTP you should use the http verbs consistently. By making your URLs look like they are referencing "nouns", it guides you towards using the HTTP verbs properly. It does not guarantee it. I could have URLS that look consistent with a way a RESTful application might do it, but then completely misuse the verbs.
Darrel Miller
+1  A: 

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.

Darrel Miller
Looks like the parts the server is adding are the "name" and "media_type" fields for the Links "order", "packer", and "destination", right? If so, this solution removes the duplication of URI references. In the above, Links are contained inside the "shipment" attributes hash, whereas in the original media type definition, links are external to the attributes hash. Still, it could work.
Rich Apodaca
This approach introduces a small bit of complexity in that clients need to be aware that setting the "media_type", or "name" attributes of the "order", "packer", and "destination" links isn't allowed (creates an error?) - only the "uri" attribute of each Link can be set.
Rich Apodaca
Yes you are correct. One of the reasons I prefer XML is that I can use the distinction between Attributes and Elements to distinguish between data and metadata in certain circumstances. The meta data attributes can help the client know what can and what can not be changed.
Darrel Miller