Skip to content

Commit

Permalink
adding Transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
samber committed Dec 12, 2022
1 parent a6aabb4 commit 6438444
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

Adding:
- lo.PartialX
- lo.Transaction

Improvement:
- lo.Associate / lo.SliceToMap: faster memory allocation
Expand Down
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ Concurrency helpers:
- [Debounce](#debounce)
- [Synchronize](#synchronize)
- [Async](#async)
- [Transaction](#transaction)

Error handling:

Expand Down Expand Up @@ -2482,6 +2483,58 @@ ch := lo.Async2(func() (int, string) {
// chan lo.Tuple2[int, string] ({42, "Hello"})
```

### Transaction

Implements a Saga pattern.

```go
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
fmt.Println("step 1")
return state + 10, nil
},
func(state int) int {
fmt.Println("rollback 1")
return state - 10
},
).
Then(
func(state int) (int, error) {
fmt.Println("step 2")
return state + 15, nil
},
func(state int) int {
fmt.Println("rollback 2")
return state - 15
},
).
Then(
func(state int) (int, error) {
fmt.Println("step 3")

if true {
return state, fmt.Errorf("error")
}

return state + 42, nil
},
func(state int) int {
fmt.Println("rollback 3")
return state - 42
},
)

_, _ = transaction.Process(-5)

// Output:
// step 1
// step 2
// step 3
// rollback 2
// rollback 1
```

### Validate

Helper function that creates an error when a condition is not met.
Expand Down
53 changes: 53 additions & 0 deletions retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,57 @@ func AttemptWhileWithDelay(maxIteration int, delay time.Duration, f func(int, ti
return maxIteration, time.Since(start), err
}

type transactionStep[T any] struct {
exec func(T) (T, error)
onRollback func(T) T
}

// NewTransaction instanciate a new transaction.
func NewTransaction[T any]() *Transaction[T] {
return &Transaction[T]{
steps: []transactionStep[T]{},
}
}

// Transaction implements a Saga pattern
type Transaction[T any] struct {
steps []transactionStep[T]
}

// Then adds a step to the chain of callbacks. It returns the same Transaction.
func (t *Transaction[T]) Then(exec func(T) (T, error), onRollback func(T) T) *Transaction[T] {
t.steps = append(t.steps, transactionStep[T]{
exec: exec,
onRollback: onRollback,
})

return t
}

// Process runs the Transaction steps and rollbacks in case of errors.
func (t *Transaction[T]) Process(state T) (T, error) {
var i int
var err error

for i < len(t.steps) {
state, err = t.steps[i].exec(state)
if err != nil {
break
}

i++
}

if err == nil {
return state, nil
}

for i > 0 {
i--
state = t.steps[i].onRollback(state)
}

return state, err
}

// throttle ?
120 changes: 120 additions & 0 deletions retry_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,123 @@ func ExampleAttemptWithDelay() {
// 2 1ms <nil>
// 2 1ms error
}

func ExampleTransaction() {
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
fmt.Println("step 1")
return state + 10, nil
},
func(state int) int {
fmt.Println("rollback 1")
return state - 10
},
).
Then(
func(state int) (int, error) {
fmt.Println("step 2")
return state + 15, nil
},
func(state int) int {
fmt.Println("rollback 2")
return state - 15
},
).
Then(
func(state int) (int, error) {
fmt.Println("step 3")

if true {
return state, fmt.Errorf("error")
}

return state + 42, nil
},
func(state int) int {
fmt.Println("rollback 3")
return state - 42
},
)

_, _ = transaction.Process(-5)

// Output:
// step 1
// step 2
// step 3
// rollback 2
// rollback 1
}

func ExampleTransaction_ok() {
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
return state + 10, nil
},
func(state int) int {
return state - 10
},
).
Then(
func(state int) (int, error) {
return state + 15, nil
},
func(state int) int {
return state - 15
},
).
Then(
func(state int) (int, error) {
return state + 42, nil
},
func(state int) int {
return state - 42
},
)

state, err := transaction.Process(-5)

fmt.Println(state)
fmt.Println(err)
// Output:
// 62
// <nil>
}

func ExampleTransaction_error() {
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
return state + 10, nil
},
func(state int) int {
return state - 10
},
).
Then(
func(state int) (int, error) {
return state, fmt.Errorf("error")
},
func(state int) int {
return state - 15
},
).
Then(
func(state int) (int, error) {
return state + 42, nil
},
func(state int) int {
return state - 42
},
)

state, err := transaction.Process(-5)

fmt.Println(state)
fmt.Println(err)
// Output:
// -5
// error
}
95 changes: 95 additions & 0 deletions retry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,98 @@ func TestDebounce(t *testing.T) {
}
}
}

func TestTransation(t *testing.T) {
is := assert.New(t)

// no error
{
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
return state + 100, nil
},
func(state int) int {
return state - 100
},
).
Then(
func(state int) (int, error) {
return state + 21, nil
},
func(state int) int {
return state - 21
},
)

state, err := transaction.Process(21)
is.Equal(142, state)
is.Equal(nil, err)
}

// with error
{
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
return state + 100, nil
},
func(state int) int {
return state - 100
},
).
Then(
func(state int) (int, error) {
return state, assert.AnError
},
func(state int) int {
return state - 21
},
).
Then(
func(state int) (int, error) {
return state + 42, nil
},
func(state int) int {
return state - 42
},
)

state, err := transaction.Process(21)
is.Equal(21, state)
is.Equal(assert.AnError, err)
}

// with error + update value
{
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
return state + 100, nil
},
func(state int) int {
return state - 100
},
).
Then(
func(state int) (int, error) {
return state + 21, assert.AnError
},
func(state int) int {
return state - 21
},
).
Then(
func(state int) (int, error) {
return state + 42, nil
},
func(state int) int {
return state - 42
},
)

state, err := transaction.Process(21)
is.Equal(42, state)
is.Equal(assert.AnError, err)
}
}

0 comments on commit 6438444

Please sign in to comment.