devlop
devlop

Reputation: 1198

RESTful API different ways of doing same thing

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

Answers (4)

hovanessyan
hovanessyan

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

Evert
Evert

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

Ben
Ben

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:

  • GET: /products?filters(to include supplier and or oem)

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:

  • GET: /customers - Gets basic info for all customers (id, name etc)
  • GET: /customers/{customerId} - Gets basic info for specific customer (id, name etc)
  • GET: /customers/{customerId}/orders - Gets basic order info (order number, date created maybe?)
  • GET: /customers/{customerId}/orders/{orderId} - Gets detailed information about a specific order.
  • GET: /customers/{customerId}/addresses - Gets all addresses associated with the customer.
  • GET: /customers/{customerId}/addresses/{addressId} - Gets a specific address associated with the customer.

You could always have multiple routes to the same resource though, depending on context:

  • GET: /customers/{customerId}/addresses/123 - Returns the address with the id of 123
  • GET: /addresses/123 - Returns the address with the id 123

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

Mo A
Mo A

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

Related Questions