سؤال

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?

هل كانت مفيدة؟

المحلول

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.

نصائح أخرى

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.

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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى softwareengineering.stackexchange
scroll top