Reputation: 1198
RESTful API design seems somewhat subjective, but I'd like some feedback on the approaches below and any pros/cons they have. Is one right? are all wrong? can both be provided (even though it would provide partially-duplicate functionality)?
GET filtered by multiple IDs...
Here are some approaches that seem fairly normal (the ?f= is just illustrating optional use of filter/search params):
GET /supplier/{supplierId}/products?f=...
GET /oem/{oemId}/products?f=...
Should there be more than 1 URL to get products? Also, what if I wanted to filter on both supplier and oem? Should I just have a /products endpoint and everything is a filter, or have products only accessible from one of supplier/oem but not both, or have all of them together?:
GET /products?supplier={supplierId}&oem={oemId}&f=...
GET /supplier/{supplierId}/products?oem={oemId}&f=...
GET /oem/{oemId}/products?supplier={supplierId}&f=...
GET child-collection without filtering by parent ID...
Here is an approach that seems fairly normal:
GET /customers/{customerId}/addresses?f=...
But what if I need orders for ALL customers. Would either of these be reasonable?
GET /customers/addresses?f=...
GET /addresses?f=...
The /customers/addresses URL (without a {customerId}) seems to make conceptual sense but I don't see that scheme used in the RESTful design docs I've been finding (there is always an {id} present). The /addresses URL is simple enough but breaks the customer-address hierarchy.
I suppose that it doesn't really matter all that much as long as it makes sense to everyone using the API and is consistent, but I'd like to know how others have approached these kinds of issues in an 'industry standard' way. Thanks.
Upvotes: 1
Views: 425
Reputation: 31423
There are no strict rules for REST, so solutions are subjective in a sense. I would look on how is this resolved with the some of the big API REST providers. I am sure exploring Etsy's or Fitbit's REST API will lead you to some insights.
GET filtered by multiple IDs
For your first case I would look into creating an aggregation service, which does 2 parallel calls to /supplier/{supplierId}/products?f=
and /oem/{oemId}/products?f=
and merges the result sets. It's based on what Etsy are doing with their concept of "bundles".
You can make it accept a list of filters in the form of filters=[key=value,key=value]
but naturally this will only support conjunction of the filters.
GET child-collection without filtering by parent ID
Google and others are using the "-" as notion of all.
For example:
/customers/{customerId}/addresses
is all addresses for a single customer
/customers/-/addresses
is all addresses for all customers
That's useful if you want to keep some hierarchical structure (e.g. in your domain you have employees and customers and both groups have addresses). Then you might say:
/customers/-/addresses
/employees/-/addresses
If you don't need the hierarchical characteristic, then you might just expose a dedicated endpoint for ALL addresses (simply /addresses
).
Upvotes: 0
Reputation: 99505
I'm also going to give a slightly different perspective on this.
You're mainly talking about collections of resources, but not about individual resources that are in this collection.
Generally I like to make sure that every individual resource (a product for example) has its own unique url. For example /products/1234
However, a product might appear in multiple collection. The collection doesn't really 'own' or 'contain' the item in the collection, it just links to it. So one way to think about it is that there can be many collections that contain the same product.
How do you know if it's the same product that appears in multiple collections? You enclose the uri of the product you are embedding, so it's always super clear that these collections are really all linking to the same thing.
This is really similar to filesystems. Many directories can contain the same (hardlinked or softlinked) file.
Sources:
Upvotes: 0
Reputation: 58
I haven't got huge experience in this, as I've only ever written an handful of services. However when developing them I always find it's best to do so with the consumer in mind. What is the consumer interested in? In your first example, it looks to be products, and I would consider the supplier and the oem as specific filters. Personally I'd probably go with something along the lines of:
With regards to the second part, again I'd suggest working with the consumer to see what is important and what would work for them. Are they interested in orders specifically, or are they more interested in customers and need to obtain details of orders, and addresses for specific customers?
Assuming that latter, then I would probably go with something like:
You could always have multiple routes to the same resource though, depending on context:
At the end of the day, it all comes down to how the API's consumer wants to get the information, and what makes sense to them.
Upvotes: 1
Reputation: 727
As you rightly mentioned, there aren't necessarily right or wrong ways to achieve this.
Personally, I like to think of endpoints from a resource perspective, and apply the relevant filters on the GET
to filter results. Occasionally, when it makes sense, I'd include a GET /search/<sub-resource>
to facilitate searches for sub-resources across a wider context.
Regarding your first example, if your primary resource is product
, whilst oem
and supplier
are just a property of a product (solely exist in relation to a product), then using /product?supplier=..&oem=...
to filter on products based on oem
and/or supplier
would work. You can use GET /supplier
and GET /oem
to return details on supplier(s) and oem(s) respectively, not necessarily the products themselves though.
Alternatively, if you deem both as sub-resources only, you can use GET /search/oem?product_id=...&customer_id=..
to search for oem
based on root resources, such as product
or customer
.
With regards to retrieving orders
for a customer, GET /customers/{customerId}/orders?f=...
makes sense for an individual customer. To return orders for all customers, you can use /orders?customerId=...
.
Again, as an alternative, you can use GET /search/orders?customerId=..
, if order
is only deemed a sub-resources of a customer.
Again, the above are merely suggestions. What you proposed can work too.
Upvotes: 0