txkit

package
v0.244.1 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2024 License: Apache-2.0 Imports: 7 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 Manager added in v0.244.0

type Manager[DB, TX, Queryable any] struct {
	// DB  is the underlying Database type to access.
	// It is ideal if ConnectionAdapter used as the Connection type implementation, but you need access to not exposed functionalities.
	//
	// 		type Connection txkit.Manager[*sql.DB, *sql.Tx]
	//
	DB *DB
	// TxAdapter provides the mapping for a native driver specific TX type to be usable as a Queryable.
	TxAdapter func(tx *TX) Queryable
	// DBAdapter provides the mapping for a native driver specific DB type to be usable as a Queryable.
	DBAdapter func(db *DB) Queryable
	// Begin is a function that must create a new transaction that is also a connection.
	Begin func(ctx context.Context, db *DB) (*TX, error)
	// Commit is a function that must commit a given transaction.
	Commit func(ctx context.Context, tx *TX) error
	// Rollback is a function that must rollback a given transaction.
	Rollback func(ctx context.Context, tx *TX) error
	// ErrTxDone is the error returned when the transaction is already finished.
	// ErrTxDone is an optional field.
	//
	// default: txkit.ErrTxDone
	ErrTxDone error
	// OnClose [optional] is used to implement the io.Closer.
	// If The ConnectionAdapter needs to close something,
	// then this function can be used for that.
	//
	// default: DB.Close()
	OnClose func() error
}

Manager is generic implementation to handle query interactions which are aware of trasnactions within the context.

Example:

type Connection = txkit.Manager[*sql.DB, *sql.Tx]

Type Arguments: - DB: the main connection that

func (Manager[DB, TX, Queryable]) BeginTx added in v0.244.0

func (m Manager[DB, TX, Queryable]) BeginTx(ctx context.Context) (context.Context, error)

func (Manager[DB, TX, Queryable]) Close added in v0.244.0

func (m Manager[DB, TX, Queryable]) Close() error

func (Manager[DB, TX, Queryable]) CommitTx added in v0.244.0

func (m Manager[DB, TX, Queryable]) CommitTx(ctx context.Context) error

func (Manager[DB, TX, Queryable]) LookupTx added in v0.244.0

func (m Manager[DB, TX, Queryable]) LookupTx(ctx context.Context) (*TX, bool)

func (Manager[DB, TX, Queryable]) Q added in v0.244.0

func (m Manager[DB, TX, Queryable]) Q(ctx context.Context) Queryable

Connection returns the current context's sql Q. This can be a *sql.DB or if we within a transaction, then a *sql.Tx.

func (Manager[DB, TX, Queryable]) RollbackTx added in v0.244.0

func (m Manager[DB, TX, Queryable]) RollbackTx(ctx context.Context) error

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