6

I'm developing a REST service, and I'm trying to adhere to Doctor Roy Fielding's conventions and guidelines.

I picture my service as an endpoint that exposes a set of resources. A resource is identified by an URI, and api clients can manipulate resources by using using HTTP semantics (i.e., different HTTP verbs map to the corresponding operations over URIs).

Guidelines state that these URIs should be defined in an hierarchical way, reflecting object hierarchy. This renders useful in resource creating, because on the backend we need the data to perform the create operation. However, on further manipulations, a lot of the information included in the URI is not even going to be used by the service, because generally the resource Id alone is enough to uniquely identify the operation target.

An example: Consider an Api that exposes the creation and management of products. Consider also that a product is associated with a brand. On creation it makes sense that the following action is performed: HTTP POST /Brand/{brand_id}/Product [Body containing the input necessary to the creation of the product]

The creation returns an HTTP 201 created with a location header that exposes the newly created product's location.

On further manipulations, clients could access the product by doing: HTTP PUT /Brand/{brand_id}/Product/{product_id} HTTP DELETE /Brand/{brand_id}/Product/{product_id} etc

However, since the product Id is universal in the product scope, the following manipulations could be performed like this: /Product/{product_id} I am only keeping the /Brand/{brand_id} prefix for coherence reasons. In fact, the brand id is being ignored by the service. Do you thing that this is a good practice, and is reasonable for the sake of maintaining a clear, unambiguous ServiceInterface definition? What are the benefits of doing this, and is this the way to go at all?

Also any pointers on URI definition best practices would be appreciated.

Thanks in advance

Cristóvão
  • 255
  • 1
  • 2
  • 11

2 Answers2

9

You are saying:

Guidelines state that these URIs should be defined in an hierarchical way, reflecting object hierarchy.

While it is often done this way it is not really relevant for a RESTful API design. Roy Fielding has a nice article addressing common misconceptions about REST. There he even says:

A REST API must not define fixed resource names or hierarchies (an obvious coupling of client and server).

and

A REST API should be entered with no prior knowledge beyond the initial URI …

So don't encode information in your URL that should be passed inside the resource. A RESTful API should work even if you replace all your URLs with artificial and non-sensical URIs. (I like understandable URIs as anybody but as an mental exercise to check your "RESTfullness" it's quite good.)

The problem to model an URI for an object "hierarchy" is that the hierarchy isn't very often as obvious as it seems. (What is the object hierarchy between teacher, course, and student?). Often objects are in a web of relations and belong not clearly beneath another object. A product might belong to a brand but you might have multiple supplier (covering a subset of products for multiple brands). And REST is wonderful to express complex nets of relations. The whole internet/web works this way.

Instead of encoding the relationship in the hierarchy just define a hyperlink in your resource pointing to the related objects.

For your specific example I would use POST /product/ to create a new product and have a link to your /brand/xzy in the resource representation when creating the product.

If you want to know which products are defined for a specific brand, just include a list of links in the returned representation for GET /brand/xzy. If you want to have an explicit resource representing for this relationship you still could define GET /brand/{id}/products as an URL (or /brandproducts/xzy or /34143453453) and return it as a link in your brand resource.

Don't think so much about the design of your URIs, think more about the information you provide in your resources. Make sure it provides links to all the resource representations your client might want to view or manipulate after receiving it from your API.

xwoker
  • 3,105
  • 1
  • 30
  • 42
2

I think this is the key comment:

a product is associated with a brand.

The word associated tells me you need to link resources together. So, let's say that there is an association between brands and products. Each resource has would have their own set of methods (GET, PUT, etc) as you described, but the representations should have links to other resources that describe their associations. Where the links are depends on the type of association (one-to-one, one-to-many, many-to-one, many-to-many) and direction.

For example, let's say there is a canonical request for this product to api.example.com:

GET /product/12345

It returns some representation of that product. For simplicity, I am going to use XML for the representation, but it can be XHTML, JSON or whatever you need. So, a simple representation of product 12345:

HTTP/1.1 200 OK
Content-Type: application/xml; charset=utf-8
Content-Length: ...

<?xml version="1.0"?>
<Product href="http://api.example.com/product/12345" rel="self" type="application/xml"/>
  <Name>Macaroni and Cheese</Name>
  <Brand href="http://api.example.com/brand/7329" type="application/xml"/>
  <Manufacturer href="http://api.example.com/manufacturer/kraft" rel="parent" type="application/xml"/>
</Product>

As you can see, I am embedding links into the representation of product 12345 describing each relationship. Whenever possible, I try to follow HATEOAS constraint as much as I can:

  • There is explicit linking between the current resource and associated resources.
  • An optional relationship description ("rel"). "self" and "parent" are descriptions about the relationship the current resource and the resource the link references.
  • An optional preferred MIME type that should be requested. This describes the type of document that the client should expect if a followup request is made.
  • Opaque URLs instead of raw identifiers. Clients can just "navigate" to the URL without building them using some convention. Note that URLs do not always need to contain a unique database identifier or key (like "/brands/kraft").

To expand on some advance concepts, let's say that products have other relationships. Maybe products have hierarchical relationships or products supersedes other products. All of these complex relationships can represented by links. So, an advanced representation of product 12345:

HTTP/1.1 200 OK
Content-Type: application/xml; charset=utf-8
Content-Length: ...

<?xml version="1.0"?>
<Product href="http://api.example.com/product/12345" rel="self" type="application/xml"/>
  <Name>Macaroni and Cheese</Name>
  <Brand href="http://api.example.com/brand/7329" rel="parent" type="application/xml"/>
  <Manufacturer href="http://api.example.com/manufacturer/kraft" type="application/xml"/>
  <!-- Other product data -->
  <Related>
    <Product href="http://api.example.com/product/29180" rel="prev" type="application/xml"/>
    <Product href="http://api.example.com/product/39201" rel="next" type="application/xml"/>
  </Related>      
</Product>

In this example, I am using "prev" and "next" to represent a chain of products. The "prev" can be interpreted as "superseded" and "next" be interpreted as "superseded-by". You can use "superseded" and "superseded-by" as rel values, but "prev" and "next" are commonly used. It is really up to you.

  • Seems that your proposal is: keep your resource path simple, and include links in each of the resource members. You are only dealing with the Get case. How would you create a new product? I.e., who would you indicate on creation time, what manufacturer is associated with the new product? Include it in the request body? That doesn't sound right. Sounds like that information is to be included in the resource URI – Cristóvão Jan 12 '14 at 16:24
  • The links would be included in the request body, because that is the representation of that resource. You would have to perform the proper checks against the request body to ensure that the Manufacturer and the Brand do exist. – Ralph Allan Rice Jan 13 '14 at 05:21