DEV Community

Cover image for Rollback Functions in Golang
Omnia Wahid
Omnia Wahid

Posted on

Rollback Functions in Golang

Errors in Go

Go methods can return multiple values. One of these values can be an error. The zero value of an error is nil. After the call of each method, you check the error value with nil. If it is not equal to nil, then an error has occurred.

How to Implement a Rollback Function?

A function can call multiple methods in it. When an error occurs in one of the methods, you will need to reverse the methods called before the error. Reverting the output of the methods is done by implementing a rollback function and calling it every time an error occurs.

The concept of the rollback function is reverse-engineering. First, extract information from the called methods. This information is the success status of each method. The state may be a boolean variable with a value equal to true when the method succeeds and false when it fails; it also may be a string with the result of the method.

Then, collect the information and abstract it into a model. Finally, pass this model to the rollback function and review each status; if it is a success status, reverse the method and if it is a failure status, ignore it.

In this way, there are no side effects to the function when an error occurs. Moreover, the program will be maintainable.

Example

//Modeling
type CreateHotelStatusModel struct {
    HotelId             string `json:"hotelId"`
    FloorId             string `json:"floorId"`
    RoomId              string `json:"roomId"`
    IsFirstFloorCreated bool   `json:"isFirstFloorCreated"`
    IsFirstRoomCreated  bool   `json:"isFirstRoomCreated"`
}

func CreateHotel(hotelName string) error {
    var hotelStatusModel CreateHotelStatusModel
    //create hotel DB record
    hotelId, createHotelDBErr := CreateHotelDBRecord(hotelName)
    //Information extraction
    hotelStatusModel.HotelId = hotelId
    if createHotelDBErr != nil {
        createHotelRollback(&hotelStatusModel)
        return createHotelDBErr
    }

    //create first floor
    floorId, isFloorCreated, createFloorErr := CreateFirstFloor(hotelId)
    hotelStatusModel.FloorId = floorId
    hotelStatusModel.IsFirstFloorCreated = isFloorCreated
    if createFloorErr != nil {
        createHotelRollback(&hotelStatusModel)
        return createFloorErr
    }

    //create first room
    roomId, isRoomCreated, createRoomErr := CreateFirstRoom(floorId)
    hotelStatusModel.RoomId = roomId
    hotelStatusModel.IsFirstRoomCreated = isRoomCreated
    if createRoomErr != nil {
        createHotelRollback(&hotelStatusModel)
        return createRoomErr
    }
    DispatchEvent("success","topic")
    return nil
}

//Review
func createHotelRollback(statusModel *CreateHotelStatusModel) {
    if statusModel.HotelId != "" {
        DeleteHotelDBRecord(statusModel.HotelId)
    }
    if statusModel.IsFirstFloorCreated {
        DeleteFloor(statusModel.FloorId)
    }
    if statusModel.IsFirstRoomCreated {
        DeleteRoom(statusModel.RoomId)
    }
    DispatchEvent("failure","topic")
}
Enter fullscreen mode Exit fullscreen mode

Discussion (2)

Collapse
3ddpaz profile image
Ed

Why not simply add everything on a database transaction?? and dispatch the event only if it fails that transaction.

Collapse
omniawahid profile image
Omnia Wahid Author

That can be a solution if you are only integrating with database, however, if you are integrating with third parties services, you will need to reverse each.