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?

rtainc
  • 105

3 Answers3

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.

Erik Eidt
  • 33,747
  • 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 '15 at 04: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 – K. Alan Bates Jun 30 '15 at 05: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.

  • 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 '15 at 00: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.

  • Retrieve the owner of pet named Zeus: /pets/Zeus#owner You meant "Retrieve the owners of pets named Zeus", didn't you? Because Zeus is a name (only unique for a particular owner), not a global identifier, so there may be several pets named Zeus, and therefore several owners. – Géry Ogam May 06 '19 at 08:35
  • "There is nothing inherently hierarchical in the relationship between users and pets by my understanding." By the way, what is exactly a "hierarchical" relation? I am realizing that it's not clear in my mind. – Géry Ogam May 06 '19 at 08:55
  • @Maggyero (first, sorry for not getting an answer posted to your question yet. It's still sitting in a browser on my laptop in my office at home. I will get something posted out to you tonight even if I have to strip everything down and just push out smaller guidance) – K. Alan Bates May 06 '19 at 18:52
  • @Maggyero re: "pets named Zeus." Yes. That is an astute observation. It actually touches on a point I have prepared in my pending answer to you: always work with sets when you work with URLs. The number of the resources returned should have no impact on the structure of the data being yielded. – K. Alan Bates May 06 '19 at 19:02
  • 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. – K. Alan Bates May 06 '19 at 19:12
  • @Maggyero re:hierarchical data(continued) The tricky part is that the spec defines what the path is supposed to be communicating. In my experience, developers use pathing as a coding convenience with total disregard for how their conveniences impact resource relationships to external users(i.e. they arent even thinking if resources are hierarchical.) In the end, you get to decide whether data shall or shall not be represented as hierarchical data; some decisions are more impactful than others, but the end goal is expressiveness through the interface and the URI is your resource interface. – K. Alan Bates May 06 '19 at 19:23
  • (No problem, take your time—but not too much =P—, in the long run a correct answer is ultimately more important than a quick answer.) "Where semantics are shared between resources, you have an opportunity to leverage a hierarchy." So inheritance ("is a" relation) is hierarchical while composition ("has a" relation) is not? So for instance, the relations animal/mammal/dog are hierarchical since a dog is a mammal and a mammal is an animal (inheritance), while the relations artist/album/song are not hierarchical since a song is not an album and an album is not an artist (composition)? – Géry Ogam May 06 '19 at 19:42
  • @Maggyero Your observation is exactly on point. – K. Alan Bates May 06 '19 at 19:45
  • Can you also go up the hierarchy with the URI path component (/dog/mammal/animal for instance) or RFC 3986 requires that you always go down (/animal/mammal/dog for instance)? – Géry Ogam May 07 '19 at 13:19
  • I am realizing that my inheritance/composition division does not always map to the hierarchical/non-hierarchical division. For instance let’s take this URI recommended by Tim Berners-Lee in his 1998 article Cool URIs Don’t Change: http://www.w3.org/1998/12/01/chairs. Chairs are not days, days are not months and months are not years, yet they are used as hierarchical data in the URI path component. – Géry Ogam May 07 '19 at 15:02
  • @Maggyero I don't think any pertinent spec actually ever states that the hierarchy MUST be "top->bottom":"left->right" but I think there is a very strong correlation between the concept and the approach. – K. Alan Bates May 07 '19 at 15:35
  • @Maggyero I greatly respect Tim Berners-Lee's opinion on the subject and I would be interested to know what his opinion would be 21 years later.The original "hierarchical" heritage was taking file system folders and subfolders as inspiration because they were trying to build the web.I want my resource interface to be as expressive as possible, and I agree with you that there is no obvious a priori link between 1999 and chairs but what if I were to tell you that url was a historical api against a public orchestra and chairs is the list of section chairs? This is all about domain context. – K. Alan Bates May 07 '19 at 15:42
  • "I think there is a very strong correlation between the concept and the approach" Okay. I am asking this because I saw that you went up the hierarchy in one of your answer: "/Persons/Joe/Parents/Mother/Parents would be a way to grab Joe's maternal grandparents." So if I understood well going up is perfectly valid, as well as going up and down ("/parents/{parentID}/children" like you wrote in this same answer)? – Géry Ogam May 07 '19 at 17:43
  • 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. – K. Alan Bates May 07 '19 at 17:49
  • "This is all about domain context." So now I am reconsidering my statement: "while the relations /artist/album/song are not hierarchical since a song is not an album and an album is not an artist (composition)" In this context, the relations /artist/album/song can be considered hierarchical (like the relations /year/month/day above), can't they? So I am still wondering what is the definitive criteria for choosing between hierarchical/non-hierarchical data. You said it is "shared semantics". Since I misinterpreted it a little bit with inheritance/composition, could you elaborate? – Géry Ogam May 07 '19 at 18:04
  • 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. – K. Alan Bates May 07 '19 at 18:19
  • @Maggyero (continued) Keep in mind that when I say "inherited" vs "composed" , I am not saying that from the perspective of the underlying software. The resource interface doesn't care if you use classes in a generalization hierarchy behind your interface.Your resource interface presents the facade that you want to expose to callers.Things get complicated when people think that treating a database as a bag for their application's crap, treating their application as a coded representation of db tables, and URIs are simply ways to run commands against databases that causes things to foul up. – K. Alan Bates May 07 '19 at 18:22
  • 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. – K. Alan Bates May 07 '19 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. – K. Alan Bates May 07 '19 at 19:24
  • So if I understood well, you say that hierarchical resource relations implies path usage but path usage does not imply hierarchical resource relations. In other words, while the relations a dog is a mammal and a mammal is an animal implies the path usage /animal/mammal/dog, the path usage /make/america/great/again does not imply any hierarchical relations between the individual resources. I like that distinction. – Géry Ogam May 07 '19 at 22:51
  • But now talking about resources only (leaving aside URI concerns), I am still wondering how do we define that a relation (an edge in the resource graph) between two resources (two vertices in the resource graph) is hierarchical vs non-hierarchical. – Géry Ogam May 07 '19 at 22:54
  • "So if I understood well, you say that hierarchical resource relations implies path usage but path usage does not imply hierarchical resource relations." After thinking about it this night, I am not sure it is entirely correct. We have 1. (hierarchical resources ⇒ path usage). But we also have 2. (non-hierarchical resources ⇒ query usage). – Géry Ogam May 08 '19 at 06:08
  • […] The contrapositive of statement 1 is (¬path usage ⇒ ¬hierarchical resources), that is to say (query or fragment usage ⇒ non-hierarchical resources), and in particular (query usage ⇒ non-hierarchical resources). Likewise, the contrapositive of statement 2 is (¬query usage ⇒ ¬non-hierarchical resources), that is to say (path or fragment usage ⇒ hierarchical resources), and in particular (path usage ⇒ hierarchical resources). Therefore, we can conclude that (hierarchical resources ⇔ path usage) and (non-hierarchical resources ⇔ query usage). – Géry Ogam May 08 '19 at 06:14
  • @Maggyero Careful. Modus tollens certainly does say if P → Q, ¬Q ∴ ¬P. But you have to go a touch further than just the rule: "If Socrates is a Man or Socrates is a Mouse" → The Moon is Made of Cheese. The Moon is not Made of Cheese. ∴ Socrates is neither a Man nor a Mouse. If the Resource Relationship is Hierarchical or the Resource Relationship is Non-hierarchical→ The Path is appropriate. The Path is not appropriate.∴ The resource relationship is neither hierarchical nor non-hierarchical. – K. Alan Bates May 08 '19 at 11:46
  • @Maggyero If you build up a sound argument for each segment of the URL pattern entailing its usage by the nature of the relationships valid for its usage, then run the statement "The data is hierarchical",the only argument which will generate a true conclusion is "Path usage is valid". Within the system, it is the only one sufficient for representing hierarchical data.The statement is strengthened to The data is hierarchical→Must use the Path by virtue of the fact that after predicating the system,"Path Usage is Valid" is the only candidate remaining when "P=Data is Hierarchical" is injected. – K. Alan Bates May 08 '19 at 13:29
  • @Maggyero Ok. I see what happened. What I just detailed above is the ACTUAL usage consideration for the Path. What you had evaluated with your "on further review" comments was my simplification. YES. If you assume the simplification, Hierarchy → Path,¬Path ∴ ¬Hierarchy. If it helps keep things clean and easy in your mind, just go with the simplification. It is more austere than the actual usage, which makes it safe. I tend towards guiding people that direction anyway because those 2 arguments produce 2 clean binary partitions, are easier to explain, and are easier to understand. – K. Alan Bates May 08 '19 at 15:34
  • @Maggyero we're pretty deep into this point of discussion and I'm afraid we might get upside down and backwards on which statement by the other we each are replying towards.I hope we've thoroughly explored it to your satisfaction,but we can keep going if you have any further questions or concerns.The basic takeaway is that what you said with your contraposition is valid but is not complete because in the real world, you have a 3rd argument: 1. (hierarchical resources → path usage). But we also have 2. (non-hierarchical resources → query usage). 3. (non-hierarchical resources → path usage) – K. Alan Bates May 08 '19 at 15:50
  • @Maggyero To tie all of these semiformal statements together: 1 & 3 form a disjunction (H=hierarchical ∨ R=¬hierarchical → path usage) Since R is represented in the firing rule for path usage as well as the firing rule for query usage I preemptively apply disjunction elimination of the disjunct R disregarding the truth value of H & apportion its treatment entirely to the firing rule that concludes with query usage As I intended to communicate,this heuristic isnt formally required.Its a simplification to create singleton candidate sets.I hope this now is clearer than mud – K. Alan Bates May 08 '19 at 16:10
  • It makes sense, thanks. Now the remaining question is: what is a hierarchical relation in a (resource) graph? (Disregarding any URI concerns.) More precisely, which properties (among reflexivity, symmetry and transitivity) the adjacency relation of a graph must satisfied to be classified as hierarchical? – Géry Ogam May 08 '19 at 16:17
  • 1
    @Maggyero ok. Referring to symmetry This is a "small truth" If you have a DAG, your data is fairly easily represented as a hierarchy starting at the beginning and pathing your way down the edge of your graph you wish to traverse. If you have a DAG, your adjacency matrix is also asymmetrical thus there exists at least one such case where an asymmetrical adjacency matrix is appropriately represented with a hierarchical relationship – K. Alan Bates May 08 '19 at 16:39
  • 1
    @Maggyero as for reflexivity, I think you would be hard pressed to find a resource server in the wild which could interpret a cyclical path (possibly of dynamic length) and respond correctly. I doubt there is any formal requirement that you cannot path into a hierarchically defined resource which has already been represented in a lefthand path segment, but you probably don't want to do that even if it would be permitted. – K. Alan Bates May 08 '19 at 16:40
  • @Maggyero as for how to apply graph transitivity to identify if a resource relationship is a good candidate for hierarchical representation: I have no idea. – K. Alan Bates May 08 '19 at 16:40
  • 1
    @Maggyero (I've been replying to your graph theory questions piecemeal and didn't see the forest for the trees rimshot) What I'm about to say is not meant to be a "universally applicable excluding all other considerations" statement:If you can visualize your resource relationship as a DAG and you want the resource interface to be expressed as a DAG, hierarchy;use the path. If you do not have a DAG or you do NOT want your resource interface to be expressed as such, go non-hierarchical;choose your segment (I choose matrix params and query string) – K. Alan Bates May 08 '19 at 16:59
  • I think you have almost find the perfect definition: a hierarchy is a connected DAG. Because a DAG has an irreflexive antisymmetric adjacency relation, and a reflexive antisymmetric transitive reachability relation (the transitive closure of the adjacency relation). Therefore its reachability relation is a partial order. And since only vertices in the same connected component are related by the reachability relation, the DAG should be a connected DAG. This partial order corresponds to the intuitive notion of a hierarchy. – Géry Ogam May 15 '19 at 14:23
  • Connected DAGs (connected directed acyclic graphs, where acyclic = without directed cycles) should not be confused with directed trees (connected directed graphs without cycles, where cycles = directed cycles or semicycles). In other words, contrary to directed trees, connected DAGs allow semicycles. In my opinion it generalizes well the concept of a tree hierarchy (which is useful for representing categories) since a connected DAG hierarchy allows a vertex to have multiple parents (which is useful for representing tags). – Géry Ogam May 15 '19 at 14:38
  • The article Data Warehousing and Online Analytical Processing on concept hierarchy is very interesting. – Géry Ogam May 15 '19 at 14:44