1

I have following model:

class Bikes:
   id: int                 # primary key
   group_id: int           # for split/merge (see below)
   quantity: int           # number of items 
   is_broken: bool         # true if all are broken
   repair_request_id: int  # reference to some RepairReqest (if broken).

repair_request_id is set to some RepairRequest object that describes a request for repair quantity of Bikes. is_broken must be true if repair_request_id is set.

My RESTful API for it:

GET /bikes        # returns list
GET /bikes/:id    # returns single instance
PUT /bikes/:id    # updates existing or adds new instance on list, returns instance data

With PUT I can mark Bikes as broken by setting is_broken=true and repair_request_id=R.

Now I need to add an operation that allow to add RepairRequest for part of bikes with group_id=X by splitting a single instance in two.

Assuming that I want to add a repair request for quantity=Q bikes from group_id=X I need to do following steps, in single transaction:

  1. Find A, where A is: Bikes with group_id==X and is_broken==false
  2. Create B, set quantity=Q, is_broken=true
  3. in A, Set A.quantity = A.quantity - Q
  4. Save all

This creates new instance and modifies original one. There is a requirement that I can have only one instance with group_id=X and is_broken=false, so when Bikes B are repaired they are merged back with following steps, in single transaction:

  1. Find A, where A is: Bikes with group_id==X and is_broken==false
  2. If not found, update B (is_broken=false, repair_request_id=null)
  3. If found:
    1. Set A.quantity = A.quantity+B.quantity
    2. Delete B

Merge can be done with PUT /bikes/:id. This could return a different instance that was sent, but this is fine for me.

Problem is with split operation. My requirements are:

  • Backend should decide if it needs to do split or just call the same handler that was created for HTTP PUT.
  • I do not want to expose transactions in REST API - one HTTP request is a single transaction.

My current idea:

  POST /bikes/1/operations/set_broken

  payload:
  {
     "quantity": Q
  }

  response (after split):
  [
    {
      "id": 1,
      [...]
      "is_broken": false
    },
    {
      "id": 2,
      [...]
      "is_broken": true
    }
  ]  

Do you think it is clear? Or do you know a better design pattern for this scenario?

Lukasz
  • 185
  • 1
  • 10
  • You're not doing REST but RPC. In REST you work on top of given states while in RPC you operate on functions or operations that take some input and return some output, if any. There is a fundamental difference. On top of that, except for PATCH none of the standard HTTP methods really specifies a transactional behavior of the form of either all or none of the changes need to be applied. The best recommendation to give here is to design the interaction as if you'd have a typical Web page and translate this approach to the more general REST architecture. – Roman Vottner Sep 09 '21 at 14:06
  • Yes, you are right. This is a question how to add RPC-style operation to REST API. I mentioned transactions because doing research I saw solutions where one could expose transaction object as REST resource, like [in this question.](https://stackoverflow.com/questions/147207/transactions-in-rest) – Lukasz Sep 10 '21 at 07:20

0 Answers0