Processing update of a collection of ManyToOne objects via the REST API

I'm afraid to think of a better way to handle updating the collection on another resource through the REST API, and I'm looking for guidance on how others see this process.

Suppose you have a many-to-one relationship with the entities Parent (One) and Child (many). My thought process is that you can process the parent collection of children through one PUT endpoint. Thus, the endpoint for updating the parent child and adding new children to the parent collection occurs through one endpoint. The request body will contain an array of child objects, and the endpoint itself will contain enough information to know which parent is being updated:

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

The endpoint will tell us that the parent with uid {uid} is the requested one and updates the child objects.

This mechanism just feels a little weird. Namely, I have to persist in new entities in one way and update them in another. My update / save operations are preferably performed in batch mode, but it is strange that they perform both batch saving and updating. I have to do this because you cannot update a new object

Is there a better way to do this? The IS relationship is hierarchical, which means that without a parent, child resources do not exist. I still want to be able to POST / PUT in batch mode.

I can expose the difference between POST and PUT (using the same endpoint as above). I have a limitation, so that the child has a unique name, so POST will fail for POSTs of new children with the existing name, and PUT should fail if the request body contains a child whose name does not exist. That is why I chose a joint operation with one endpoint.

+6
source share
2 answers

Work with ManyToOne

Regarding the correct attitude towards ManyToOne relationships, let me explain how I would do it if I tried to adhere to the principles of ReST.

Nested Resources

There are several ways to express relationships, one of which you suggested using a path hierarchy. You will need the following endpoints:

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

This choice better expresses compositional relationships in which children cannot exist independently.

First level resources

Another option, if you apply the Hypermedia restriction (which you should call your ReSTful API), is as follows:

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

When creating a child resource, you must include in the request body a link to the parent resource with the corresponding rel type. For example, if you use 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 each other.

Security context

There is a third option, which we should consider as a special case. If the parent resource is an authenticated director, you can implicitly relate to each other. For example, if the parent of the User domain is the owner of the Photo s collection, you might be tempted to expose the following endpoints:

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

Given that only a User can access only his own Photo s, this would be enough:

  • /photos
  • /photos/:id

Because the authenticated User will be accessible to the endpoint through the security context, and relationships can be implicitly made without explicit expression through a hierarchical path or other means.

Additional considerations

Based on your question, I find some signals about what might lead to bad practices. So, here are some principles regarding your post that you should try to adhere to whenever possible (be pragmatic).

  • Each resource needs a unique identifier, a URI in ReST. As you already found out, failure to do this will have strange consequences when you try to update child objects, as was developed in your question.
  • Your API must be reSTful to implement the hypermedia restriction. If your resource identifiers are URIs, you can create full and rich relationships between your resources regardless of their location on the server.
  • Identifiers must be opaque. Therefore, never use correlation numbers or enumerations in the :id URI part. Don't even let your consumers try to guess the URIs that might reveal what you don't want them to see. Security is key, but an opaque identifier is optional.
  • If, for good reason, identifiers should be generated by the server, not the client. Otherwise, your identifier will not be opaque.
  • Usually:

    • Create using POST and return 201 Created . I like to respond with the body of the resource. And do not forget to include the URI in the created resource.
    • Read with GET and return 200 OK .
    • Change the entire resource using PUT . I like to match the message and return the updated resource with 200 OK .
    • Delete with DELETE and reply 204 No Content .
    • I rarely use PATCH for partial updates.

This will help you in most cases.

+4
source

Is there a better way to do this?

I think it should be. One idea that should be kept in mind is this: the point of a single interface is that clients and intermediaries do not need to know anything about the server implementation.

 GET /people/bob/favoriteColors 200 OK [] 

If this is the 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" your favorite color using 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. Please note that we are intentionally isolated 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: {}) 

This is not to say that your api should not allow publishing directly on a new resource; it is also good.

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

What you should keep in mind is that, from the perspective of your standard swamp, of the intermediate components, there is no implied relationship between /people/bob/favoriteColors and /people/bob/favoriteColors/OCTARINE . Changing one does not invalidate the cache entries for the other - the same interface that protects us from the details of the implementation of entries also protects us from side effects on other resources. When developing your API, you need to think about the implications of having multiple resources that can change the β€œsame” state.

To some extent, you probably have this problem. Intermediaries will not know that

 DELETE /people/bob 

should also carve /people/bob/favoriteColors

In all the examples, at this point I used the full representations of the addressed resource. Allow all PUT - send a replacement view for the target resource. If you want to submit an idea of ​​the change, you need to think about PATCH or POST.

The combination of POST to create is false. I assume the link was accepted in response to the POST description in RFC 2616 . The language in [RFC 7231] views it as something more of all. POST is the only writing method supported in HTML, and the Internet is booming, so we have to deal with it somehow.

An additional way out is to send messages where all the interesting work is a side effect. This is similar to sending a message to the message queue. The target resource is the turn itself, so your requests and answers correspond to the conceptual model of adding a document to the collection; since the documents themselves are notions of work, not representations of results, they are separate from the domain model itself. This means that you can send a full view of the BeigifyColors , which can be arbitrarily complex, for handlers tuned to this particular use case and watch for side effects in your views.

Command processing resources here, readable representations, are another expression of the CQRS pattern. As with PUT OCTARINE, intermediaries are not going to know which views to evict, but otherwise they handle the protocol perfectly.

+1
source

Source: https://habr.com/ru/post/1011826/


All Articles