Created
June 22, 2017 16:10
-
-
Save jiahaog/eb7dc4bf111e6f31fcc709b8a4199cc4 to your computer and use it in GitHub Desktop.
Simulating transactions in Go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
// This file attempts to simulate trasactions, that can be used to run tasks in a chain and | |
// rollback everything if the chain fails. For example, this can be used to simulate a database | |
// transaction to clean up dirty data. | |
// While I am aware that this abstraction is not truely a atomic transaction as it does not | |
// obtain locks on the affected objects, it is sufficient for our use cases to cleanup dirty data | |
const txLogTag = "transaction" | |
// Transform specifies a series of functions that have a "Up" step and a "Down" step in case of | |
// rollback. | |
// For Up methods, it will be executed with the previous result of the transform in the chain | |
// (if any), and will return a result which is stored and will be used as a parameter to the | |
// Down(arg) method of the same transform if rolling back. | |
// The returned result of Down() txMethods is ignored | |
type Transform struct { | |
Up txMethod | |
Down txMethod | |
// Name is used for logging in case it fails | |
Name string | |
// result is used to keep track of results from the transform, and will | |
// be used the argument to Down() if a rollback is needed | |
result interface{} | |
} | |
// A txMethod is a up or down step in a transform. | |
type txMethod func(interface{}) (interface{}, error) | |
type transaction struct { | |
transforms []*Transform | |
name string | |
} | |
// RunTX takes all transforms in the transaction and runs them sequentially. The result of the last | |
// transform is returned, and the error (if any) of the failing transform (when doing the happy | |
// path). In the case of a failure to rollback the transforms, and an inconsistent state is | |
// reached, the rollback chain will be stopped immediately (subsequent rollbacks will not be | |
// executed), and the error will be logged | |
func RunTX(name string, transforms ...*Transform) (interface{}, error) { | |
tx := createTX(name, transforms...) | |
return tx.Run() | |
} | |
func createTX(name string, transforms ...*Transform) *transaction { | |
var transformSlice []*Transform | |
for _, transform := range transforms { | |
transformSlice = append(transformSlice, transform) | |
} | |
return &transaction{transforms, name} | |
} | |
func (t *transaction) Run() (interface{}, error) { | |
// prevArg tracks the output from the previous transform that will be passed as a | |
// parameter to the next transform (when doing the happy path) | |
var prevArg interface{} | |
// errorWhenFailed keeps track of the main error that causes the happy path to fail | |
var errorWhenFailed error | |
var completed []*Transform | |
// Run happy path | |
for _, transform := range t.transforms { | |
result, err := transform.Up(prevArg) | |
// Stop chain if it fails | |
if err != nil { | |
errorWhenFailed = err | |
break | |
} | |
// save output of transform (in case we have to rollback) | |
transform.result = result | |
prevArg = result | |
completed = append(completed, transform) | |
} | |
// If allizwell, return result of last transform | |
if errorWhenFailed == nil { | |
return prevArg, nil | |
} | |
// Rollback everything that has been completed | |
for i := len(completed) - 1; i >= 0; i-- { | |
toRollback := completed[i] | |
_, err := toRollback.Down(toRollback.result) | |
logging.Debug(txLogTag, "ROLLBACK: [Transaction: %v][Transform: %v]", t.name, toRollback.Name) | |
if err != nil { | |
logging.Error(txLogTag, "Error rolling back, inconsistent state reached in transaction %v. transform %v failed.", t.name, toRollback.Name) | |
} | |
} | |
return nil, errorWhenFailed | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment