How to model a CANCEL action in a RESTful way?

2020-07-06 03:22发布

We are currently in the process of wrangling smaller services from our monoliths. Our domain is very similar to a ticketing system. We have decided to start with the cancellation process of the domain.

Our cancel service has as simple endpoint "Cancel" which takes in the id of the ticket. Internally, we retrieve the id, perform some operations related to cancel on it and update the state of the entity in the store. From the store's perspective the only difference between a cancelled ticket and a live ticket are a few properties.

From what I have read, PATCH seems to be the correct verb to be used in this case, as am updating only a simple property in the resource.

PATCH /api/tickets/{id}
Payload {isCancelled: true}

But isCancelled is not an actual property in the entity. Is it fair to send properties in the payload that are not part of the entity or should I think of some other form of modeling this request? I would not want to send the entire entity as part of the payload, since it is large.

I have considered creating a new resource CancelledTickets, but in our domain we would never have the need to a GET on cancelled tickets. Hence stayed away from having to create a new resource.

4条回答
虎瘦雄心在
2楼-- · 2020-07-06 03:58

REST is basically a generalization of the browser based Web. Any concepts you apply for the Web can also be applied to REST.

So, how would you design a cancel activity in a Web page? You'd probably have a table row with certain activities like edit and delete outlined with icons and mouse-over text that on clicking invoke a URI on the server and lead to a followup state. You are not that much interested how the URI of that button might look like or if a PATCH or DELETE command is invoked in the back. You are just interested that the request is processed.

The same holds true if you want to perform the same via REST. Instead of images that hint the user that an edit or cancel activity is performed on an entry, a meaningful link-relation name should be used to hint the client about the possiblities. In your case this might be something like reserve new tickets, edit reservation or cancel reservation. Each link relation name is accompanied by a URL the client can invoke if he wants to perform one of the activities. The exact characters of the URI is not of importance here to the client also. Which method to invoke might either be provided already in the response (as further accompanying field) or via the media type the response was processed for. If neither the media type nor an accompanying field gives a hint on which HTTP operation to use an OPTIONS request may be issued on the URI beforehand. The rule of thumb here is, the server should teach a client on how to achieve something in the end.

By following such a concept you decouple a client from the API and make it robust to changes. Instead of a client generating a URI to invoke the client is fed by the server with possible URIs to invoke. If the server ever changes its iternal URI structure a client using one of the provided URIs will still be able to invoke the service as it simply used one of the URIs provided by the server. Which one to use is determined by analyzing the link relation name that hints the client when to invoke such URI. As mentioned above, such link relation names need to be defined somewhere. But this is exactly what Fielding claimed back in 2008 by:

A REST API should spend almost all of its descriptive effort in defining the media type(s) used for representing resources and driving application state, or in defining extended relation names and/or hypertext-enabled mark-up for existing standard media types. (Source)

Which HTTP operation to choose for canceling a ticket/reservation may depend on your desing. While some of the answers recommended DELETE RFC 7231 states that only the association between the URI and the resource is removed and no gurantee is given that the actual resource is also being removed here as well. If you design a system where a cancelation might be undone, then DELETE is not the right choice for you as the mapping of the URI to the resource should not exist further after handling a DELETE request. If you, however, consider a cancelation to also lead to a removal of the reservation then you MAY use DELETE.

If you model your resource in a way that maintains the state as property within the resource, PATCHing the resource might be a valid option. However, simply sending something like state=canceled is probably not enough here as PATCH is a calculation of steps done by the client in order to transform a certain resource (or multipe resources) into a desired target state. JSON Patch might give a clue on how this might be done. A further note needs to be done on the atomicy requirement PATCH has. Either all of the instructions succeed or none at all.

As also PUT was mentioned in one of the other answers. PUT has the semantics of replacing the current representation available at the given URI with the one given in the request' payload. The server is further allowed to either reject the content or transform it to a more suitable representation and also affect other resources as well, i.e. if they mimic a version history of the resource.

If neither of the above mentioned operations really satisfies your needs you should use POST as this is the all-purpose, swiss-army-knife toolkit of HTTP. While this operation is usually used to create new resources, it isn't limited to it. It should be used in any situation where the semantics of the other operations aren't applicable. According to the HTTP specification

The POST method requests that the target resource process the representation enclosed in the request according to the resource's own specific semantics.

This is basically the get-free-out-of-jail card. Here you can literally process anything at the server according to your own rules. If you want to cancel or pause something, just do it.

I highly discourage to use GET for creating, altering or canceling/removing something. GET is a safe operation and gurantees any invoking client that it wont alter any state for the invoked resource on the server. Note that it might have minor side-effects, i.e. logging, though the actual state should be unaffected by an invocation. This is a property Web crawler rely on. They will simply invoke any URI via GET and learn the content of the received response. And I assume you don't want Google (or any other crawler) to cancel all of your reservations, or do you?

As mentioned above, which HTTP operation you should use depends on your design. DELETE should only be used if you are also going to remove the representation, eventhough the spec does not necessarily require this, but once the URI mapping to the resource is gone, you basically have no way to invoke this resource further (unless you have created a further URI mapping first, of course). If you designed your resource to keep the state within a property I'd probably go for PATCH but in general I'd basically opt for POST here as here you have all the choices at your hands.

查看更多
兄弟一词,经得起流年.
3楼-- · 2020-07-06 04:05

Take a look what exactly is RESTful way. No matter if you send PATCH request with isCancelled as payload or even DELETE if you want tickets to disappear. It's still RESTful.

Your move depends on your needs. As you said

I have considered creating a new resource CancelledTickets, but in our domain we would never have the need to a GET on cancelled tickets.

I would just send DELETE. You don't have to remove it physically. If it's possible to un-cancel, then implement isCancelled mechanism. It's just question of taste.

查看更多
女痞
4楼-- · 2020-07-06 04:14

Exposing the GET interface of a resource is not compulsory.

For example, use

PUT /api/tickets/{id}/actions/cancel

to submit the cancellation request. I choose PUT since there would be no more than one cancellation request in effect.

Hope it be helpful.

查看更多
Ridiculous、
5楼-- · 2020-07-06 04:18

For modelling CANCEL Action in Restful way : Suppose we have to delete a note in DB by providing noteId(Note's ID) and Note is a pojo

1] At controller :

 @DeleteMapping(value="/delete/{noteId}")
     public ResponseEntity<Note> deleteNote( @PathVariable Long noteId)
     {
        noteServiceImpl.deleteNote(noteId);
        return ResponseEntity.ok().build();
     }

2]Service Layer :

@Service
public class NoteServiceImpl {

@Autowired
private NotesRepository notesDao;

public void deleteNote(Long id) {
    notesDao.delete(id);
}

}

3] Repository layer :

 @Repository
public interface NotesRepository extends CrudRepository<Note, Long> {
 }

and in 4] postman : http://localhost:8080/delete/1

enter image description here

So we have deleted note Id 1 from DB by CANCEL Action

查看更多
登录 后发表回答