Search code examples
djangodatabasedjango-rest-frameworktransactionsatomic

How to implement such a custom rollback in Django or DRF?


Out system accesses to a middle platform(I do not know how to call it in English, we call it 中台 in Chinese) which is for authentication like logging in, JWT verifying, etc. Then, We have encoutered questions when it is neccessary to rollback actions because of unexpected program errors. Like code below, the program will crash when running at 1 / 0, then AdminPermission.objects.create can be rolled back, but do_user_actions can not, cause it is a RPC function. So, we need to override transaction.atomic or something like this to implement our requirements. But, I do not know how to implement it. Pls give me some suggestions or sample code. Thx a lot.

    @transaction.atomic  # can not rollback remote func call `do_user_actions`
    def create(self, request, *args, **kwargs):
        # call a remote func here to create a admin account.
        user_dict = do_user_actions(query_kwargs=query_kwargs, action='create-admin')  

        user_id = user_dict.get('id')
        permission_code = 'base_permission'
        AdminPermission.objects.create(user_id=user_id, permission_code=permission_code)

        # some unexpected errors, like:
        1 / 0
        
        return Response('success')


Solution

  • Instead of using atomic with the decorator you could use it as a context manager, something like this,

    with transaction.atomic():
      try:
        # do your stuff
        user_dict = do_user_actions(query_kwargs=query_kwargs, action='create-admin')
        user_id = user_dict.get('id')
        permission_code = 'base_permission'
        AdminPermission.objects.create(user_id=user_id, permission_code=permission_code)
        1/0 # error
      except SomeError: # capture error
        revert_user_actions() # revert do_user_actions
        transaction.set_rollback(True) # rollback
        return Response(status=status.HTTP_424_FAILED_DEPENDENCY) # failure
    return Response(serializer.data, status=status.HTTP_201_CREATED) # success
    

    Django Docs - Transactions

    UPDATE

    As @Gorgine mention in the comment, the documentation don't recommend to handle errors inside atomic. Because is always a good idea to follow recomendations, you can put atomic inside try block. In this case atomic block will handle the rollback if an error occur, so you will need to handle the actions that are not rollback by atomic in the except block. Something like this:

    try:
      with transaction.atomic():
        # do your stuff
        user_dict = do_user_actions(query_kwargs=query_kwargs, action='create-admin')
        user_id = user_dict.get('id')
        permission_code = 'base_permission'
        AdminPermission.objects.create(user_id=user_id, permission_code=permission_code)
        1/0 # error
    except SomeError: # capture error
      revert_user_actions() # revert do_user_actions
      return Response(status=status.HTTP_424_FAILED_DEPENDENCY) # failure
    return Response(serializer.data, status=status.HTTP_201_CREATED) # success