txkit

package
v0.231.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 9, 2024 License: Apache-2.0 Imports: 5 Imported by: 0

README

txkit Package

The txkit package is designed to help you manage resources that lack native commit protocols by mimicking transaction-like behaviour. It simplifies transaction management at the use-case code level, proving especially valuable in stateful systems, such as APIs without built-in transactional capabilities.

When an error occurs during an operation, the txkit package ensures all stateful changes are undone in a Last-In-First-Out (LIFO) manner, similar to how defer works at the function scope, but txkit operates at the use-case scope. While it doesn't replace proper transaction support, it can handle many scenarios where extensive rollback logic implementations or manual intervention were necessary, making human interaction almost negligible.

package mypkg

import (
	"context"
	"go.llib.dev/frameless/pkg/txkit"
)

func MyUseCase(ctx context.Context) (returnErr error) {

	// Begins a new transaction, returning it as a context.
	//
	// If there was a transaction present in the received context, 
	// it automatically nests the transaction level.
	//
	// This means that this function can operate on its own,
	// or can dynamically join into a bigger use-case 
	// where multiple components are touched to achieve a high-level goal.
	ctx, err := txs.Begin(ctx)
	if err != nil {
		return err
	}

	// the deferred Finish call will finish the current transaction.
	// If the function returns without an error, txs.Commit is executed on the current transaction. 
	// In case of an error, Rollback is triggered.
	defer txs.Finish(&returnErr, ctx)

	// The happy path where we make changes to the system state.
	// This could be anything from an API call to a file system change.
	if err := MyActionWhichMutateTheSystemState(ctx); err != nil {
		return err
	}

	// Describes what to do in case of an error, ensuring a rollback for the stateful changes above.
	txs.OnRollback(ctx, func(ctx context.Context) error {
		return UndoActionForMyPreviousActionWhichMutatedTheSystemState(ctx)
	})

	return nil
}

Challenges without txkit

In systems without transactional support, managing rollback logic becomes complex as the number of actions involving external resources increases. For instance, if you create entities A, B, C, and D, and encounter an error during the creation of C, you must undo A and B. An error during the creation of D means rolling back A, B, and C. As actions become more intricate, maintaining an effective rollback mechanism becomes challenging.

How txkit Simplifies

The txkit package simplifies this by allowing you to register an OnRollback callback after each successful action that changes the system state. When an error occurs, rolling back the current txkit transaction executes all prepared rollback steps in a Last-In-First-Out (LIFO) order, similar to defer functionality. This ensures the system state is restored to stability.

Reduced Mental Load

With txkit, your code focuses on individual entities and their actions, reducing the mental load needed for rollbacks. For example, the code for creating entity C involves starting a transaction, performing the action (creating C), defining rollback steps (deleting C), and finishing the transaction with either Commit or Rollback in case of an error.

Nested Transactions

The package supports nested transactions seamlessly, automatically joining an existing transaction if present in the context. This allows smaller transactions to combine into larger units without code changes.

Dynamic Rollback Mechanism

txkit dynamically adapts to refactored business logic, tying rollback steps to small atomic changes rather than high-level scenarios. This flexibility simplifies maintaining the rollback mechanism as your code evolves.

Documentation

Overview

Example (PkgLevelTxFunctions)
package main

import (
	"context"

	"go.llib.dev/frameless/pkg/logger"
	"go.llib.dev/frameless/pkg/logging"
	"go.llib.dev/frameless/pkg/txkit"
)

func MyActionWhichMutateTheSystemState(ctx context.Context) error {
	return nil
}

func RollbackForMyActionWhichMutatedTheSystemState(ctx context.Context) error {
	return nil
}

func MyUseCase(ctx context.Context) (returnErr error) {
	ctx, err := txkit.Begin(ctx)
	if err != nil {
		return err
	}
	defer txkit.Finish(&returnErr, ctx)

	if err := MyActionWhichMutateTheSystemState(ctx); err != nil {
		return err
	}

	txkit.OnRollback(ctx, func(ctx context.Context) error {
		return RollbackForMyActionWhichMutatedTheSystemState(ctx)
	})

	return nil
}

func main() {
	ctx := context.Background()
	ctx, err := txkit.Begin(ctx)
	if err != nil {
		logger.Error(ctx, "error with my tx", logging.ErrField(err))
	}

	if err := MyUseCase(ctx); err != nil {
		txkit.Rollback(ctx)
		return
	}
	txkit.Commit(ctx)
}

Index

Examples

Constants

View Source
const (
	ErrTxDone errorkit.Error = "transaction is already finished"
	ErrNoCtx  errorkit.Error = "context.Context not given"
	ErrNoTx   errorkit.Error = "no transaction present in the current context"
)

Variables

This section is empty.

Functions

func Begin

func Begin(ctx context.Context) (context.Context, error)

func Commit

func Commit(ctx context.Context) error

func Finish

func Finish(returnError *error, tx context.Context)

func OnRollback

func OnRollback[StepFn onRollbackStepFn](ctx context.Context, step StepFn) error

func Rollback

func Rollback(ctx context.Context) error

Types

type TxRollbackError

type TxRollbackError struct {
	Err   error
	Cause error
}

func (*TxRollbackError) Error

func (err *TxRollbackError) Error() string

func (*TxRollbackError) Unwrap

func (err *TxRollbackError) Unwrap() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL