One of the key concepts of RESTful webservices is the use of resources, and in particular how the resource URIs should look like. One should not underestimate the value of giving domain objects good names and structuring their identifiers in a proper way. In many cases, this will naturally lead to a hierarchy of resources. Indeed, a domain model is often composed of a rather limited set of key root objects, with the other objects seemingly depending on them. However, unless the depending content objects really cannot exist outside their container objects, and even can’t be switched from one container object to another, they should be modeled as root objects in the URI hierarchy.

Let’s have a look at a simple example. Assume we want to create a RESTful application to create and manage presentations. Presentations have a title, a date and a set of slides, and they belong to a user. Here’s a first attempt at writing down the URIs for our resources:

  • /users – a resource with all the users in the application
  • /users/{userId} – a resource with the information for a single user in the application
  • /users/{userId}/presentations – a resource with all the presentations of a user
  • /users/{userId}/presentations/{presentationId} – a resource for a particular presentation
  • /users/{userId}/presentations/{presentationId}/slides – a resource with all the slides in a particular presentation
  • /users/{userId}/presentations/{presentationId}/slides/{slideId} – a resource for a particular slide

Here’s the first problem with hierarchical resources: the URIs for the objects at the bottom of the tree start to become very long. This isn’t –or shouldn’t– be a problem for the computer, but it’s clear that the further down one gets in the tree, the less human readable the URIs will become.

The next problem is URI validation. With a URI like /users/734975456/presentations/13464654/slides/59267932, it doesn’t suffice to check that slide #59267932 exists: the system also has to check that presentation #13464654 exists, that user #734975456 exists, and that slide #59267932 belongs to presentation #13464654, and presentation #13464654 to user #734975456. All these checks have to be implemented—and thoroughly tested.

A more subtle problem is what such a hierarchical URI means for authorization and confidentiality. Say that user #734975456 wants to share slide #59267932 with someone else, should the other user then also get access to presentation #13464654 and the information stored on user #734975456, even in a filtered form? And should the other user even be informed at all that there exists a presentation #13464654 created by user #734975456? In some cases, that may already be a security issue in itself.

But what if a slide can be moved from one presentation to another? Should it change URI as a consequence of that? And what if it turns out that in a new version of your application, users should be allowed to reuse the same slide in multiple presentations? All of a sudden, the composition pattern between the container object presentation and its content object slide is broken, and all slides have to be relocated from the URI pattern /users/{userId}/presentations/{presentationId}/slides/{slideId} to the new URI pattern /users/{userId}/slides/{slideId}. Hopefully, the slide IDs were unique within the application, not just within each presentation. But while you’re at it, can slides be owned by more than one user, or is that a feature further down in the backlog? In that case, the URI pattern for slides should really be /slides/{slideId}. If the same applies to presentations, they should get the URI pattern /presentations/{presentationId}.

So where does all this leave us? Basically, all our resources have become root objects, and their URIs now look like this:

  • /users – a resource with all the users in the system
  • /users/{userId} – a resource with the information for a single user in the system
  • /presentations – a resource with all the presentations in the system
  • /presentations/{presentationId} – a resource for a particular presentation
  • /slides – a resource with all the slides of a presentation
  • /slides/{slideId} – a resource for a particular slide

Now the slides can exist outside the presentations, and the presentations outside the users. The URIs have become much shorter, and URI validation is much simpler. Access control and confidentiality has been solved too: access to a single slide doesn’t imply access to (some information about) a presentation, and even doesn’t reveal in which presentation(s) a slide appears—unless that information is included as a hypermedia link. And as a bonus, we got two global resources that can be used to search for presentations and slides across all users, and at the same time can still be filtered by a certain user and/or presentation through a parameter.

Occasionally, there are uses for hierarchical URIs, but I’ve grown skeptical about them over the years. I see them mostly as a future change request, and try to challenge them as much as possible when I see them in an early design.

Om Filip Van Laenen

Filip er en meget erfaren arkitekt og rådgiver som har jobbet med et bredt spekter av arkitekturmessige problemstillinger. Han er en av våre fremste teknologiske rådgivere og leder for teknologiske forum, et rådgivningsorgan for Computas` linje- og prosjektorganisasjon.

2 thoughts on “Five Inconvenient Truths about REST: 5 – Hierarchical URIs will kill you in the end

  1. Gode betraktninger.

    Etter min mening er Trello sitt API et godt eksempel på praktisk bruk av URLer. Individuelle kort ligger f.eks. under /cards/{id} noe som er fint når de flyttes. Det er noen få steder der dette APIet avviker fra din standard, både trivielt som /boards/{id}/cards og /cards/{id}/checklistItems/{id} (spesielt nyttig ved DELETE).

    Hva synes du om det?

  2. Jo, tok en titt på Trello sitt REST API, og det virker å være mye i tråd med hva jeg beskrev her oppe.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *