7

I would like to know which is the best practice when you are having a resource which contains a list of subresources. For example, you have the resource Author which has info like name, id, birthday and a List books. This list of books exists only in relation with the Author. So, you have the following scenario:

  1. You want to add a new book to the book list
  2. You want to update the name of a book from the list
  3. You want to delete a book from the list

SOLUTION 1

I searched which is the correct design and I found multiple approaches. I want to know if there is a standard way of designing this. I think the design by the book says to have the following methods:

  1. To add: POST /authors/{authorId}/book/
  2. To update: PUT /authors/{authorId}/book/{bookId}
  3. To delete: DELETE /authors/{authorId}/book/{bookId}

SOLUTION 2

My solution is to have only one PUT method which does all these 3 things because the list of books exists only inside object author and you are actually updating the author. Something like:

PUT /authors/{authorId}/updateBookList (and send the whole updated book list inside the author object)

I find multiple errors in my scenario. For example, sending more data from the client, having some logic on the client, more validation on the API and also relying that the client has the latest version of Book List.

My question is: is it anti-pattern to do this?

SITUATION 1. In my situation, my API is using another API, not a database. The used API has just one method of "updateBookList", so I am guessing it is easier to duplicate this behavior inside my API too. Is it also correct?

SITUATION 2. But, supposing my API would use a database would it be more suitable to use SOLUTION 1?

Also, if you could provide some articles, books where you can find similar information. I know this kind of design is not written in stone but some guidelines would help. (Example: from Book REST API Design Rulebook - Masse - O'Reilly)

Kashyap
  • 15,354
  • 13
  • 64
  • 103
green
  • 153
  • 3
  • 13
  • If this is a real problem and not homework, bear in mind that books can have multiple authors. A book should really be a top-level resource. – Eric Stein Jul 19 '17 at 13:38
  • It is a real problem, but in my situation the list of books belongs to only one author. For example, if you delete an author those books won't exist anymore. – green Jul 21 '17 at 09:53
  • For now. You're risking a breaking API change if the business requirements change. Only you can know how likely that is, of course. – Eric Stein Jul 21 '17 at 13:19

3 Answers3

7

Solution 2 sounds very much like old-style RPC where a method is invoked that performs some processing. This is like a REST antipattern as REST's focus is on resources and not on methods. The operations you can perform on a resource are given by the underlying protocol (HTTP in your case) and thus REST should adhere to the semantics of the underlying protocol (one of its few constraints).

In addition, REST doesn't care how you set up your URIs, hence there are no RESTful URLs actually. For an automated system a URI following a certain structure has just the same semantics as a randomly generated string acting as a URI. It's us humans who put sense into the string though an application should use the rel attribute which gives the URI some kind of logical name the application can use. An application who expects a certain logical composition of an URL is already tightly coupled to the API and hence violates the principles REST tries to solve, namely the decoupling of clients from server APIs.

If you want to update (sub)resources via PUT in a RESTful way, you have to follow the semantics of put which basically state that the received payload replaces the payload accessible at the given URI before the update.

The PUT method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message payload.

...

The target resource in a POST request is intended to handle the enclosed representation according to the resource's own semantics, whereas the enclosed representation in a PUT request is defined as replacing the state of the target resource. Hence, the intent of PUT is idempotent and visible to intermediaries, even though the exact effect is only known by the origin server.

In regards to partial updates RFC 7231 states that partial updates are possible by either using PATCH as suggested by @Alexandru or by issuing a PUT request directly at a sub-resource where the payload replaces the content of the sub-resource with the one in the payload. For the resource containing the sub-resouce this has an affect of a partial update.

Partial content updates are possible by targeting a separately identified resource with state that overlaps a portion of the larger resource, or by using a different method that has been specifically defined for partial updates (for example, the PATCH method defined in [RFC5789]).

In your case you could therefore send the updated book collection directly via a PUT operation to something like an .../author/{authorId}/books resource which replaces the old collection. As this might not scale well for authors that have written many publications PATCH is probably preferable. Note, however, that PATCH requires an atomic and transactional behavior. Either all actions succeed or none. If an error occurs in the middle of the actions you have to role back all already executed steps.

In regards to your request for further literature, SO isn't the right place to ask this as there is an own off-topic close/flag reason exactly for this.

Community
  • 1
  • 1
Roman Vottner
  • 12,213
  • 5
  • 46
  • 63
2

I'd go with the first option and have separate methods instead of cramming all logic inside a generic PUT. Even if you're relying on an API instead of a database, that's just a 3rd party dependency that you should be able to switch at any point, without having to refactor too much of your code.

That being said, if you're going to allow the update of a large number of books at once, then PATCH might be your friend:

Looking at the RFC 6902 (which defines the Patch standard), from the client's perspective the API could be called like

PATCH /authors/{authorId}/book
[
    { "op": "add", "path": "/ids", "value": [ "24", "27", "35" ]},
    { "op": "remove", "path": "/ids", "value": [ "20", "30" ]}
]
Community
  • 1
  • 1
Alexandru Marculescu
  • 5,569
  • 6
  • 34
  • 50
  • 1
    You should be looking at HTTP PATCH (https://tools.ietf.org/html/rfc5789) semantics not JSON PATCH. – Kashyap Mar 28 '19 at 19:10
0

Technically, solution 1 hands down.

REST API URLs consist of resources (and identifiers and filter attribute name/values). It should not contain actions (verbs). Using verbs encourages creation of stupid APIs.

E.g. I know a real-life-in-production API that wants you to

  • do POST on /getrecords to get all records
  • do POST on /putrecords to add a new record

Reasons to choose solution 2 would not be technical.

For requirement #2 (You want to update the name of a book from the list), it is possible to use JSON PATCH semantics, but use HTTP PATCH (https://tools.ietf.org/html/rfc5789) semantics to design the URL (not JSON PATCH semantics as suggested by Alexandru Marculescu).

I.e.

  • Do PATCH on /authors/{authorId}/book/{bookId}, where body contains only PK and changed attributes. Instead of:
  • To update: PUT on /authors/{authorId}/book/{bookId}

JSON PATCH semantics may of course be used to design the body of a PATCH request, but it just complicates things IMO.

Kashyap
  • 15,354
  • 13
  • 64
  • 103