Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling an update a ManyToOne entity collection via REST API

I'm struggling with thinking about the best way to deal with updating a collection to another resource through REST APIs, and am looking for guidance on how others see this process.

Suppose you have a Many-To-One relationship with entities "Parent" (One) and "Child" (Many). My thought process is that you can handle updating parent's collection of children via a single PUT endpoint. This way, the endpoint for updating a parent's child entity, and adding new child entities to the parent's collection happens through a single endpoint. The request body would contain an array of the child entities, and the endpoint itself would contain enough information to know which parent is being updated:

i.e. PUT .../parent/{uid}/child

The endpoint would tell us that the parent entity with uid of {uid} is the one being requested and to update it's child entities.

This mechanism just feels a bit -- strange. Namely, I must persist the new entities one way, and updating them in another. My update/save operations are preferably performed in batch, but it feels strange to do both a batch save and update. I have to do both because you cannot update a new entity

Is there a better way to accomplish this? The relationship IS hierarchical, meaning that without the parent, the child resources don't exist. I still also want to be able to POST/PUT in batch.

I can expose a POST vs. PUT distinction (using same endpoint as above). I have a constraint such that a child entity has a unique name, so the POST would have to fail for POST'ing new child entities with an existing name, and the PUT would have to fail when a request body contains a child entity whose name doesn't exist. This is why I opted for the shared operation with a single endpoint.

like image 420
rosenthal Avatar asked Oct 28 '16 19:10

rosenthal


2 Answers

Dealing with ManyToOne

Regarding to properly dealing with ManyToOne relationships, let me explain how I would do it, if I was to stick as much as possible to ReST principles.

Nested resources

There are several ways to express the relationship, one being the one you suggested using a path hierarchy. You would need the following endpoints:

  • /parents/
  • /parents/:id
  • /parents/:id/children
  • /parents/:id/children/:id

This choice better expresses composition relationships where the children cannot exists on their own.

First level resources

Another option, if you are applying the Hypermedia constraint (which you should to call your API ReSTful) is the following:

  • /parents
  • /parents/:id
  • /childrens
  • /childrens/id

When creating a children resource, you would include in the body of the request a link to the parent resource with the appropriate rel type. For example, if using HAL:

{
  ...
  ...,
  "_links": {
    "parent": { "href": "https://api.domain.com/parents/9283jdp92cn"}
  }
}

This choice better expresses weak relationships or aggregations where both ends of the relationship can exist without one another.

Security context

There is a third option, that we should treat as a special case. If the parent resource is the authenticated principal you can implicitly relate one to another. For example, if the parent is a User domain entity is the owner of a collection of Photos, you could be tempted to expose the following endpoints:

  • /users
  • /users/:id
  • /users/:id/photos
  • /users/:id/photos/:id

Given that only a User can only access his own Photos, this would suffice:

  • /photos
  • /photos/:id

Because the authenticated User would be available to the endpoint through the security context and the relationships could be implicitly made without explicitly expressing it through a hierarchical path or other means.

Additional considerations

Stemming from your question, I find some signals of what could lead to implementation of bad practices. So here come some principles relating to your post that you should try to stick to when possible (be pragmatic).

  1. Every resource needs a unique identifier, an URI in ReST. As you have already discovered, failing to do so will have weird implications when trying to update child entities as designed in your question.
  2. Your API to be ReSTful should implement the hypermedia constraint. If your resource Ids are URIs you can create fully qualified and rich relationsionships between your resources no matter their server location.
  3. Identifiers should be opaque. So never use correlative numbers or enumerations in the :id part of an URI. Don't even let your consumers to try guessing URIs that could expose what you don't want them to see. Security is key but opaque id's are an extra.
  4. Unless for good reason, identifiers should be generated by the server, not by the client. Otherwise your id's wouldn't be opaque.
  5. As a rule of thumb:

    1. Create with POST and return 201 Created. I like to respond with the body of the resource. And do not forget to include the URI to the created resource.
    2. Read with GETand return 200 OK.
    3. Modify a whole resource with PUT. I like to be consistent with post and return the updated resource with a 200 OK.
    4. Delete with DELETE and respond with 204 No Content.
    5. I rarely use PATCH for partial updates.

This gets you covered for most cases.

like image 127
Daniel Cerecedo Avatar answered Nov 02 '22 22:11

Daniel Cerecedo


Is there a better way to accomplish this?

I think there must be. One idea to keep in mind is this: the point of the uniform interface is that clients, and intermediaries, don't need to know anything about the implementation on the server.

GET /people/bob/favoriteColors

200 OK
[]

If that's are starting point, and we want to add a new color to the list

PUT /people/bob/favoriteColors
[ "RED" : { "redChannel":255, "greenChannel":0, "blueChannel":0} ]

200 OK

No problem, we "created" a favorite color with a PUT.

PUT /people/bob/favoriteColors
[ "RED" : { "redChannel":239, "greenChannel":0, "blueChannel":0} ,
[ "BLUE" : { "redChannel":16, "greenChannel":16, "blueChannel":239} ]

200 OK

Again, no problem: we created BLUE and updated RED. Note that we are deliberately insulated from the gymnastics that the server performed while accepting this update.

KeyValueStore.put(/people/bob/favoriteColors, [...])

KeyValueStore.put(/people/bob/favoriteColors/RED, {...})
KeyValueStore.put(/people/bob/favoriteColors/BLUE, {...})

KeyValueStore.put(/people/bob, {...,favoriteColors:{...}})

RDBMS.commit( [ favoriteColors.insert(BLUE : {}), favoriteColors.update(RED: {})

Which isn't to say that your api shouldn't allow posting directly to a new resource; that's fine too

PUT /people/bob/favoriteColors/OCTARINE
{ "redChannel":-inf, "greenChannel":Nan, "blueChannel":i}

201 CREATED

What you do need to keep in mind is that, from the point of view of your bog-standard, out of the box, intermediary components, there is no implied relationship between /people/bob/favoriteColors and /people/bob/favoriteColors/OCTARINE. Modifying one doesn't invalidate the cache entries for the other - the same interface that protects us from the implementation details of the writes also protects us from the side effects on other resources. In designing your API, you do need to think through the implications of having multiple resources that can change the "same" state.

To some extent, you probably have that problem anyway. Intermediaries are not going to know that

DELETE /people/bob

should also evict /people/bob/favoriteColors

In all of the examples to this point, I've been using full representations of the addressed resource. That's all PUT is allowed to do - send a representation of a replacement for the target resource. If you want to send a representation of a change, then you need to be thinking about PATCH or POST.

The coupling of POST to create is a false one. My guess is that coupling was assumed in response to the description of POST in RFC 2616. The language in [RFC 7231] treats it as more of a catch-all. POST is the only write method supported in HTML, and the web thrived, so we must be able to manage somehow.

An additional out is to send messages where all of the interesting work is side effect. This is analogous to sending a message to a message queue. The target resource is the queue itself, so your requests and responses all correspond to the conceptual model of adding a document to a collection; since the documents themselves are representations of work to do, rather than representations of results, they stand apart from the domain model itself. This means that you can send a complete representation of a BeigifyColors command, which can be arbitrarily complicated, to handlers tuned to that specific use case, and observe the side effects in your representations.

Command handling resources here, readable representations there is another expression of the CQRS pattern. As with the PUT OCTARINE case, the intermediaries are not going to know which representations to evict, but otherwise they handle the protocol just fine.

like image 20
VoiceOfUnreason Avatar answered Nov 02 '22 23:11

VoiceOfUnreason