10

Let's say there are Users and Pets. Each Pet has a single User as its owner, and a globally unique id. Endpoints could be

/users/1/pets/2

/pets/2

I feel like the first option could be unintuitive since IDs are inconsistent within one User. Which one should I use?

3 Answers 3

5

You have stated your choice that pets have a globally unique id. This means that a pet can be transferred to another owner without changing the pet's id (generally a good thing for stability under db refactoring). Further, it means that should you want to, you can have two owners (e.g. a couple or roommates, etc..) for the pet. (Not to mention directly referencing a pet as you note.)

Therefore, I'd say use the /pets/<id>.

However, you can still list pets for one user with /users/1/pets, and user for pet with /pets/<id>/user (if limited to one or /pets/<id>/users for more), for one, because we're talking about the API here not your normalized database hidden behind it. The use of simple integers for ID's may be confusing (so you could use something else), but when people see that /pets is an endpoint, I think they'll get it.

Have a look a OData, if you haven't seen it already. A good way to layer on top of REST, and provide for filters, nested data, even doing joins, from an API perspective. For example, https://stackoverflow.com/q/3920758/471129 gives a good discussion about using navigational properties for specific joins without having to introduce foreign keys.

Also, I think you will eventually want to remove the limitation of one owner per pet, as this is not going to stand the test of time. Cars, houses, bank accounts, all have notions of multiple owners. Children can have multiple guardians, why not pets having multiple owners.

2
  • I should have added that User and Pets are just examples as I just wanted to portray ownership. Perhaps a better example would be User and its Limbs for lack of a better one. Thank you for that link as well; I will definitely try that for the rest of my questions.
    – rtainc
    Jun 30, 2015 at 4:45
  • 1
    @rtainc well...loosely speaking, a user could be defined as an aggregation of their limbs. If a limb is a subcomponent of user, then (depending on your semantics) /users?firstName=Alan&lastName=Bates#limbs,arm=left&leg=right could return my left arm and right leg Jun 30, 2015 at 5:21
3

If a pet only exists in the context of a user, then it makes sense to make pet a subresource of user. But if you can envision the pet as a standalone resource as well, then it would be intuitive to put it on its own path. Ideally, you would structure your resources the way you think about them.

1
  • I was caught on this because I was thinking that while pets are in the context of a user, some people may want to look up a pet by its id and not have to know the owner. So I assume the latter option would be more fitting.
    – rtainc
    Jun 30, 2015 at 0:50
1

Stick to the fundies; they are fundamental for a reason.

<scheme>://<user>:<password>@<server.tld>:<port>/path;parameter?query#fragment

(note that the usage of user:password to identify userinfo is deprecated and is a generally bad practice. They are included for completeness' sake as they are still technically valid segments of a URI)

Those are your fundamental blocks for building URIs. You may be opposed to the aesthetics of your URI because you aren't adequately using the tools at your disposal. There is nothing particularly wrong with your URI snippets. But if your data is not hierarchical -I can think of no particular reason why a person and a pet should naturally be hierarchical data in a generic domain- your objection likely stems from the fact that that is the exact relationship you have modeled; a hierarchical one.

Keep in mind here that with the proper understanding, you can craft your URIs to read as prosaically as you wish. So -first- let me import some details from the URI spec.

3.3. Path

The path component contains data, usually organized in hierarchical form

...

URI producing applications often use the reserved characters allowed in a segment to delimit scheme-specific or dereference-handler-specific subcomponents. For example, the semicolon (";") and equals ("=") reserved characters are often used to delimit parameters and parameter values applicable to that segment. The comma (",") reserved character is often used for similar purposes.

That part about path parameters is usually glossed over and underutilized

3.4. Query

The query component contains non-hierarchical data

The query is very well utilized (over-utilized in my opinion and usually while ignoring that little detail about "non-hierarchical" data)

3.5. Fragment

The fragment identifier component of a URI allows indirect
identification of a secondary resource by reference to a primary
resource and additional identifying information. The identified
secondary resource may be some portion or subset of the primary
resource, some view on representations of the primary resource, or
some other resource defined or described by those representations. A fragment identifier component is indicated by the presence of a
number sign ("#") character and terminated by the end of the URI.

The fragment is in widespread use, but usually within a narrow scope of identifying specific representations or switching out view fragments.

With that information brought into discussion, what kind of relationship between people and pets are you trying to model with your API? I personally feel it is very clunky and unnatural to use the "traditional" approach of drilling down the hierarchy as you have with /users/1/pets/2 even without considering the identifiers. There is nothing inherently hierarchical in the relationship between users and pets by my understanding. If a hierarchical relationship is what you are going for, then that is exactly what you have modeled and my opinion does not add value; in that case, you already have what you want.

But, lets assume you want to model a relationship between users and pets that is non-hierarchical. I can think of three basic usages here that would result in two different looking types of URIs.

Let's say that you don't want to consider "pets" and "people" to be distinct entities. Let's say you are using the name of a pet to find a person. You could model that like so

/customers?pet=Zeus

Perhaps say you are modeling a pet shop and you consider customers to be a core domain object. By using this structure, you will unsurprisingly retrieve customers having a pet named Zeus.

Let's say that you want to use the name of a person to find pets

/pets?owner=Alan

Perhaps you are modeling a veterinary clinic and pets are the core domain object of your medication tracking system. Using this structure will unsurprisingly retrieve pets owned by Alan.

For the final set of examples, let's assume that you want to model a close relationship between "owners" and "pets" without modeling the relationship hierarchically. You want to use a context defined in the path to retrieve some piece of data that is related to but not semantically coupled to the structure of the path. You can use the fragment for that (before using this, seek a definition of predefined semantics for your representation's media type to see if this usage is valid. In absence of predefined semantics, the fragment's usage is unconstrained and left to the design of the server. In that event, this would be valid.)

Retrieve the pet named Zeus for user Alan
/users;name=Alan#pet=Zeus

Retrieve the owner of pet named Zeus
/pets/Zeus#owner


/veterinary/patients;owner=Alan#pet=Zeus

For the final one, you are searching veterinary records by using the entity with name=Alan to retrieve a pet named Zeus, or more succinctly you are taking Alan's dog.

Ooooohhhhh noooooooeeeee.

36
  • 1
    @Maggyero re:hierarchical data Ultimately, hierarchical data would be data which is hierarchical in nature from the perspective of the consumer. Where the traditional approach fails in my mind is that hierarchies are typically defined from the perspective of the producer, and very important abstraction layers get leaked as a matter of convenience to the developer rather than correctness to the domain. Define a domain, identify your core resource relationships. Where semantics are shared between resources, you have an opportunity to leverage a hierarchy. May 6, 2019 at 19:12
  • 1
    @Maggyero Ah! I was thinking of a "default pathing flow" of just throwing a slash into the path when I answered your question a bit ago. If you want to "go up" the hierarchy and declare that is what you are doing, there would be no inherent problem with doing so. Note that I was trying to guide him in my direction with that answer, not proposing it as pristine. I don't like putting ids into URLs for lots of reasons. He seemed to be far off of that approach so I didn't even try to push it. May 7, 2019 at 17:49
  • 1
    @Maggyero The inheritance/composition distinction is a very good distinction to draw inspiration from, though. In the main, "inherited" relationships would likely be most clearly communicated through pathing. "Composed" relationships would more likely be most clearly communicated through an individual context within the path or with a fragment or with client choreography. It is entirely possible that you cannot represent all of your relationships within a single resource location structure and you would need to "break out" and coordinate multiple resource relationship groupings. May 7, 2019 at 18:19
  • 1
    @Maggyero one thing that is vitally important, subtle distinction to add to this. While hierarchical data "must" be communicated via the PATH, the PATH itself does not necessarily declare that the pathing relationship is hierarchical. You do not have to define a hierarchy in order to use the PATH;however, if you have hierarchical resource relationships, the PATH is the mechanism for communicating that hierarchy. It's a "necessary" vs "sufficient" kind of thing. May 7, 2019 at 18:31
  • 1
    @Maggyero I tend towards a hard distinction between the purposes of the path vs the query because the query very much is for non-hierarchical relationships and it is much easier to say "path=hierarchy" and "query=!hierarchy" than to bring in the subtlety that the path usually contains hierarchical data. If you keep the areas distinct for your modeling tasks, treat them for those purposes, then hold the "subtlety of what the path allows behind glass to be broken in case of modeling emergencies", you'll be in a good spot. May 7, 2019 at 19:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.