REST API Design: Nested Collection vs. New Root
Asked Answered
E

5

39

This question is about optimal REST API design and a problem I'm facing to choose between nested resources and root level collections.

To demonstrate the concept, suppose I have collections City, Business, and Employees. A typical API may be constructed as follows. Imagine that ABC, X7N and WWW are keys, e.g. guids:

GET Api/City/ABC/Businesses                       (returns all Businesses in City ABC)
GET Api/City/ABC/Businesses/X7N                   (returns business X7N)
GET Api/City/ABC/Businesses/X7N/Employees         (returns all employees at business X7N)
PUT Api/City/ABC/Businesses/X7N/Employees/WWW     (updates employee WWW)

This appears clean because it follows the original domain structure - business are in a city, and employees are at a business. Individual items are accessible via key under the collection (e.g. ../Businesses returns all businesses, while ../Businesses/X7N returns the individual business).

Here is what the API consumer needs to be able to do:

  • Get businesses in a city (GET Api/City/ABC/Businesses)
  • Get all employees at a business (GET Api/City/ABC/Businesses/X7N/Employees)
  • Update individual employee information (PUT Api/City/ABC/Businesses/X7N/Employees/WWW)

That second and third call, while appearing to be in the right place, use a lot of parameters that are actually unnecessary.

  • To get employees at a business, the only parameter needed is the key of the business (X7N).
  • To update an individual employee, the only parameter needed it the key of the employee (WWW)

Nothing in the backend code requires non-key information to look up the business or update the employee. So, instead, the following endpoints appear better:

GET Api/City/ABC/Businesses                       (returns all Businesses in City ABC)
GET Api/Businesses/X7N                            (returns business X7N)
GET Api/Businesses/X7N/Employees                  (returns all employees at business X7N)
PUT Api/Employees/WWW                             (updates employee WWW)

As you can see, I've created a new root for businesses and employees, even though from a domain perspective they are a sub/sub-sub-collection.

Neither solution appears very clean to me.

  • The first example asks for unnecessary information, but is structured in a way that appears "natural" to the consumer (individual items from a collection are retrieved via lower leafs)
  • The second example only asks for necessary information, but isn't structured in a "natural" way - subcollections are accessible via roots
  • The individual employee root would not work when adding a new employee, as we need to know which business to add the employee to, which means that call would at least have to reside under the Business root, such as POST Api/Businesses/X7N7/Employees, which makes everything even more confusing.

Is there a cleaner, third way that I'm not thinking of?

Electrotherapy answered 13/10, 2013 at 0:8 Comment(0)
J
28

I don't see how REST adds a constraint that two resources could not have the same value. The resourceType/ID is just an example of the easiest use case rather than the best way to go from a RESTful point of view.

If you read paragraph 5.2.1.1 of Roy Fielding's dissertation carefully, you will notice that Fielding makes the disctinction between a value and a resource. Now a resource should have a unique URI, that's true. But nothing prevents two resources from having the same value:

For example, the "authors' preferred version" of an academic paper is a mapping whose value changes over time, whereas a mapping to "the paper published in the proceedings of conference X" is static. These are two distinct resources, even if they both map to the same value at some point in time. The distinction is necessary so that both resources can be identified and referenced independently. A similar example from software engineering is the separate identification of a version-controlled source code file when referring to the "latest revision", "revision number 1.2.7", or "revision included with the Orange release."

So nothing prevents you from, as you say, changing the root. In your example, a Business is a value not a resource. It is perfectly RESTful to create a resource which is a list of "every business located in a city" (just like Roy's example, "revisions included with the Orange release"), while having a "business which ID is x" resource as well (like "revision number x").

For Employees, I would keep API/Businesses/X7N/Employees as the relation between a business and its employees is a composition relationship, and thus as you say, Employees can and should only be accessed through the Businesses class root. But this is not a REST requirement, and the other alternative is perfectly RESTful as well.


Note that this goes in pair with the application of the HATEAOS principle. In your API, the list of Businesses located in a city could (and perhaps should from a theoretical point of view) be just a list of links to the API/Businesses. But this would mean that the clients would have to do one round-trip to the server for each of the items in the list. This is not efficient and, to stay pragmatic, what I do is embed the representation of the business in the list along with the self link to the URI that would be in this example API/Businesses.

Jaconet answered 15/10, 2013 at 17:6 Comment(0)
E
16

You should not confuse REST with the application of a specific URI naming convention.

HOW the resources are named is entirely secondary. You are trying to use HTTP resource naming conventions - this has nothing to do with REST. Roy Fielding himself states so repeatedly in the documents quoted above by others. REST is not a protocol, it is an architectural style.

In fact, Roy Fielding states in his 2008 blog comment (http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven 6/20/2012):

"A REST API must not define fixed resource names or hierarchies (an obvious coupling of client and server). Servers must have the freedom to control their own namespace. Instead, allow servers to instruct clients on how to construct appropriate URIs, such as is done in HTML forms and URI templates, by defining those instructions within media types and link relations."

So in essence:

The problem you describe is not actually a problem of REST - conceptually, it is a problem of HIERARCHY STRUCTURES versus RELATIONAL STRUCTURES.

While a business is "in" a city and so can be considered to be part of the city "hierarchy" - what about international companies which have offices in 75 cities. Then the city suddenly becomes the junior element in a hierarchy with the business name at the senior level of the structure.

The point is, you can view data from various angles, and depending on the viewpoint you take, it may be simplest to see it as a hierarchy. But the same data can be seen as a hierarchy with different levels. When you are using HTTP type resource names, then you have entered a hierarchy structure defined by HTTP. This is a constraint, yes, but it's not a REST constraint, it's a HTTP constraint.

From that angle, you can chose the solution which fits better to your scenario. If your customer cannot supply the city name when he supplies the company name (he may not know), then it would be better to have the key with only city name. As I said, it's up to you, and REST won't stand in your way ...

More to the point:

The only real REST constraints you have, if you have already decided to use HTTP with GET PUT and so on, are:

    1. Thou shalt not presumeth any prior ("out of band") knowledge between client and servers. *

Look at your proposal #1 above in that light. You assume that customers know the keys for the cities which are contained in your system? Wrong - that's not restful. So the server has to give the list of cities as a list of choices in some way. So are you going to list every city in the world here? I guess not, but then you'll have to do some work on how you are planning to do this, which brings us to:

    1. A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state ...

I think, reading the mentioned Roy Fielding blog will help you out considerably.

Exemplum answered 17/10, 2013 at 15:46 Comment(0)
C
6

In a RESTful-API URL design should be quite unimportant - or at least a side issue since the discoverability is encoded in the hypertext and not in the URL path. Have a look at the resources linked in the REST tag wiki here on StackOverflow.

But if you want to design human readable URLs for your UC, I would suggest the following:

  1. Use the resource type you are creating/updating/querying as the first part of the URL (after your API prefix). So when somebody sees the URL he immediately knows to which resources this URL points. GET /Api/Employees... is the only only way to receive Employee resources from the API.

  2. Use Unique IDs for each resource independent of the relations they are in. So GET /Api/<CollectionType>/UniqueKey should return a valid resource representation. Nobody should have to worry where the Employee is located. (But the returned Employee should have the links to the Business (and for convenience sake City) he belongs to.) GET /Api/Employees/Z6W returns the Employee with this ID no matter where is is located.

  3. If you want to get a specific resource: Put your query parameter at the end (instead in the hierarchical order described in the question). You can use the URL query string (GET /Api/Employees?City=X7N) or a matrix parameter expression (GET /Api/Employees;City=X7N;Business=A4X,A5Y). This will allow you to easily express a collection of all Employees in a specific City - independent of the Business they are in.

Side node:

In my experience an initial hierarchical domain data model seldom survives additional requirements that come up during a project. In your case: Consider a business located in two Cities. You could create a workaround by modelling it as two separate businesses but what about the employee who works half his time in one place and the other half at the other location? Or even worse: It's only clear for which business he works but it's undefined, in which city?

Condom answered 18/10, 2013 at 8:17 Comment(0)
M
3

The third way that I see is to make Businesses and Employees root resources and use query parameters to filter collections:

GET Api/Businesses?city=ABC                       (returns all Businesses in City ABC)
GET Api/Businesses/X7N                            (returns business X7N)
GET Api/Employees?businesses=X7N                  (returns all employees at business X7N)
PUT Api/Employees/WWW                             (updates employee WWW)

Your both solutions use concept of REST sub-resources which requires that subresource is included in parent resource so:

GET Api/City/ABC/Businesses

in response should also return data provided by:

  GET Api/City/ABC/Businesses/X7N                 
  GET Api/City/ABC/Businesses/X7N/Employees 

similar for:

GET Api/Businesses/X7N

which should return data provided by:

GET Api/Businesses/X7N/Employees

It will make size of the response huge and time required to generate will increase.

To make REST API clean each resource should have only one bounded URI which fallow below patterns:

 GET  /resources
 GET  /resources/{id}
 POST /resources
 PUT  /resources/{id}

If you need to make links between resources use HATEOAS

Megen answered 14/10, 2013 at 5:29 Comment(0)
M
1

Go with example 1. I wouldn't worry about unnecessary information from the point of view of the server. A URL should clearly identify a resource in a unique fashion from the point of view of the client. If the client would not know what /Employee/12 means without first knowing that it is actually /Businesses/X7N/Employees/12 then the first URL seems redundant.

The client should be dealing with URLs rather than the individual parameters that make up the URLs, so there is nothing wrong with long URLs. To the client they are just strings. The server should be telling the client the URL to do what it needs to do, not the individual parameters that then require the client to construct the URL.

Mahogany answered 16/10, 2013 at 8:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.