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 Api/City/ABC/Businesses)
(GET Api/City/ABC/Businesses/X7N/Employees)
(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.
X7N
).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.
POST Api/Businesses/X7N7/Employees
, which makes everything even more confusing.Is there a cleaner, third way that I'm not thinking of?
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
.