When designing resource hierarchies, when should one use sub-resources?
I used to believe that when a resource could not exist without another, it should be represented as its sub-resource. I recently ran across this counter-example:
- An employee is uniquely identifiable across all companies.
- An employee's access control and life-cycle depend on the company.
I modeled this as: /companies/{companyName}/employee/{employeeId}
Notice, I don't need to look up the company in order to locate the employee, so should I? If I do, I'm paying a price to look up information I don't need. If I don't, this URL mistakenly returns HTTP 200:
/companies/{nonExistingName}/employee/{existingId}
- How should I represent the fact that a resource to belongs to another?
- How should I represent the fact that a resource cannot be identified without another?
- What relationships are sub-resources meant and not meant to model?
The issue seems to be when there is no specific company but an employee technically belongs to some company or organization otherwise they could be called bums or politicians. Being an employee implies a company/organization relationship somewhere but not a specific one. Also employees can work for more than one company/organization. When the specific company context is required then your original works
/companies/{companyName}/users/{id}
Lets say you want to know the
EmployerContribution
for your ira/rsp/pension you'd use:/companies/enron/users/fred/EmployerContribution
You get the specific amount contributed by enron (or $0).What if you want the
EmployerContribution
s from any or all companies fred works(ed) for?You don't need a concrete company for it to make sense.
/companies/any/employee/fred/EmployerContribution
Where "any" is obviously an abstraction or placeholder when the employee's company doesn't matter but being an employee does. You need to intercept the 'company" handler to prevent a db lookup (although not sure why company wouldn't be cached? how many can there be?)
You can even change the abstraction to represent something like all companies for which Fred was employed over the last 10 years.
/companies/last10years/employee/fred/EmployerContribution
This is one way you can resolve this situation:
Talking about employee makes no sense without also talking about the company, but talking about the person does make sense even without a company and even if he's hired by multiple companies. A person's existence is independent of whether he's hired by any companies, but an employment's existence does depend on the company.
A year later, I ended with the following compromise (for database rows that contain a unique identifier):
/companies/{id}
and/employees/{id}
).HTTP 307 ("Temporary redirect")
pointing at the canonical URI. This will cause clients to repeat the operation against the canonical URI./companies
but not/employees
.This approach has the following benefits:
/companies/{companyId}/employees/{employeeId}/computers/{computerId}
.Sometimes this may highlight a problem with your domain model. Why does a user belong to a company? If I change companies, am I whole new person? What if I work for two companies? Am I two different people?
If the answer is yes, then why not take some company-unique identifier to access a user?
e.g. username:
company/foo/user/bar
(where
bar
is my username that is unique within that specific company namespace)If the answer is no, then why am I not a user (person) by myself, and the
company/users
collection merely points to me:<link rel="user" uri="/user/1" />
(note: employee seems to be more appropriate)Now outside of your specific example, I think that resource-subresource relationships are more appropriate when it comes to use rather than ownership (and that's why you're struggling with the redundancy of identifying a company for a user that implicitly identifies a company).
What I mean by this is that
users
is actually a sub-resource of a company resource, because the use is to define the relationship between a company and its employees - another way of saying that is: you have to define a company before you can start hiring employees. Likewise, a user (person) has to be defined (born) before you can recruit them.Your rule to decide if a resource should be modeled as sub resource is valid. Your problem does not arise from a wrong conceptual model but you let leak your database model into your REST model.
From a conceptual view an
employee
if it can only exist within acompany
relationship is modeled as a composition. Theemployee
could be thus only identified via thecompany
. Now databases come into play and allemployee
rows get a unique identifier.My advice is don't let the database model leak in your conceptional model because you're exposing infrastructure concerns to your API. For example what happens when you decide to switch to a document oriented database like MongoDB where you could model your employees as part of the company document and no longer has this artificial unique id? Would you want to change your API?
To answer your extra questions
Composition via sub resources, other associations via URL links.
Use both id values in your resource URL and make sure not to let your database leak into your API by checking if the "combination" exists.
Sub resources are well suited for compositions but more generally spoken to model that a resource cannot exist without the parent resource and always belongs to one parent resource. Your rule
when a resource could not exist without another, it should be represented as its sub-resource
is a good guidance for this decision.if a subresource is uniquely identifiable without its owning entity, it is no subresource and should have its own namespace (i.e. /users/{user} rather than /companies/{*}/users/{user}). Most importantly: never ever ever everer uses your entity's database primary key as the resource identifier. that's the most common mistake where implementation details leak to the outside world. you should always have a natural business key (like username or company-number, rather than user-id or company-id). the uniqueness of such a key can be enforced by a unique constraint, if you wish, but the primary key of an entity should never ever everer leave the persistence-layer of your application, or at least it should never be an argument to any service method. If you go by this rule, you shouldn't have any trouble distinguishing between compositions (/companies/{company}/users/{user}) and associations (/users/{user}), because if your subresource doesn't have a natural business key, that identifies it in a global context, you can be very certain it really is a depending subresource (or you must first create a business key to make it globally identifiable).