I’ve always felt there is a tension when creating RESTful APIs. REST focuses on managing the present contents of a resource, supporting the basic CRUD (create/read/update/delete) operations via HTTP verbs. However when a user is modifying something they are not simply updating state, they are executing an action. This is reflected in some architectures where we issue a command that expresses the action we want to take. Often that action is simply to update a entity, which is nicely modeled in REST via a PUT to a resource, but in other cases it is not.
One example where an action doesn’t correspond to a CRUD operation is in implementing workflow. Take a simple bug reporting system with the following state model:
In this workflow it is not possible to change between some pairs of states, for instance I can’t go from Closed to Testing, or Open to/from Testing.
In a purely RESTful implementation, I would PUT my bug to the API and it would check if the current state has changed and then check the legality of that workflow. I would also PUT my bug to the API if a few fields needed changing. This means we have two different actions being represented by the same endpoint, and the intent of the user has been lost. Having different intentions normally tells us that each intent should be considered separately: they are likely to be triggered by different elements in the UI; their validation will be different; and from a code-design perspective we usually seek cohesive designs where each code-element has a single responsibility. Furthermore, this allows us to perform both actions in a single call, making the handling logic considerably more complex. So handling multiple actions behind a single call runs in the face of normal design principles and creates complexity (which is software-developer speak for ‘creates bugs’).
I believe the normal solution to this problem is to introduce sub-paths on the resource. e.g.
PUT resource/:id/statechange*. At this point the purity of our RESTful implementation has been violated: specifically we cannot update all the fields in our resource with the underlying PUT. Thus we have introduced a method that’s not a verb-resource combination, and we need to know which fields can be updated via the main end-point and which ones can only be updated via sub-paths.
This resolves the tension I’ve found with RESTful APIs and is a solution I’m happy with. My experience has been that being ‘purist’ about any technology usually results in more effort than it’s worth, and this mix of resource-based verbs for the standard CRUD operations and sub-paths for other actions on the resource is simple and pragmatic. Documentation will tell us what those actions are and which fields on the resource can’t be updated via the PUT.
* Some might argue POST is more correct here. I was always told PUT is idempotent while POST is not i.e. doing PUT multiple times will have the same outcome. As we’re always targeting the same state, I’d argue this is idempotent, although if the action has side-effects like sending notification emails then maybe POST is more accurate.