work

package module
v2.0.1+incompatible Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2019 License: Apache-2.0 Imports: 6 Imported by: 1

README

work

A compact library for tracking and committing atomic changes to your entities.

GoDoc Build Status Coverage Status Release License Blog

What is it?

work does the heavy lifting of tracking changes that your application makes to entities within a particular operation. This is accomplished using what we refer to as a "work unit", which is essentially an implementation of the Unit Of Work pattern popularized by Martin Fowler. With work units, you no longer need to write any code to track, apply, or rollback changes atomically in your application. This lets you focus on just writing the code that handles changes when they happen.

Why use it?

There are a bundle of benefits you get by using work units:

  • easier management of changes to your entities.
  • rollback of changes when chaos ensues.
  • centralization of save and rollback functionality.
  • reduced overhead when applying changes.
  • decoupling of code triggering changes from code that persists the changes.
  • shorter transactions for SQL datastores.

How to use it?

The following assumes your application has types (fdm, bdm) that satisfy the SQLDataMapper and DataMapper interfaces, as well as *sql.DB (db).

Construction

Starting with entities Foo and Bar,

// type names.
fType, bType :=
	work.TypeNameOf(Foo{}), work.TypeNameOf(Bar{})

we can create SQL work units:

mappers := map[work.TypeName]work.SQLDataMapper {
	fType: fdm,
	bType: bdm,
}

unit, err := work.NewSQLUnit(mappers, db)
if err != nil {
	panic(err)
}

or we can create "best effort" units:

mappers := map[work.TypeName]work.DataMapper {
	fType: fdm,
	bType: bdm,
}

unit, err := work.NewBestEffortUnit(mappers)
if err != nil {
	panic(err)
}
Adding

When creating new entities, use Add:

additions := interface{}{Foo{}, Bar{}}
unit.Add(additions...)
Updating

When modifying existing entities, use Alter:

updates := interface{}{Foo{}, Bar{}}
unit.Alter(updates...)
Removing

When removing existing entities, use Remove:

removals := interface{}{Foo{}, Bar{}}
unit.Remove(removals...)
Registering

When retrieving existing entities, track their intial state using Register:

fetched := interface{}{Foo{}, Bar{}}
unit.Register(fetched...)
Saving

When you are ready to commit your work unit, use Save:

if err := unit.Save(); err != nil {
	panic(err)
}
Logging

We use zap as our logging library of choice. To leverage the logs emitted from the work units, simply pass in an instance of *zap.Logger upon creation:

l, _ := zap.NewDevelopment()

// create an SQL unit with logging.
unit, err := work.NewSQLUnit(mappers, db, work.UnitLogger(l))
if err != nil {
	panic(err)
}
Metrics

For emitting metrics, we use tally. To utilize the metrics emitted from the work units, pass in a Scope upon creation. Assuming we have an a scope s, it would look like so:

unit, err := work.NewBestEffortUnit(mappers, work.UnitScope(s))
if err != nil {
	panic(err)
}
Emitted Metrics
Name Type Description
[PREFIX.]unit.save.success counter The number of successful work unit saves.
[PREFIX.]unit.save timer The time duration when saving a work unit.
[PREFIX.]unit.rollback.success counter The number of successful work unit rollbacks.
[PREFIX.]unit.rollback.failure counter The number of unsuccessful work unit rollbacks.
[PREFIX.]unit.rollback timer The time duration when rolling back a work unit.
Uniters

In most circumstances, an application has many aspects that result in the creation of a work unit. To tackle that challenge, we recommend using Uniters to create instances of Unit, like so:

uniter := work.NewSQLUniter(mappers, db)

// create the unit.
unit, err := uniter.Unit()
if err != nil {
	panic(err)
}

Contribute

Want to lend us a hand? Check out our guidelines for contributing.

License

We are rocking an Apache 2.0 license for this project.

Code of Conduct

Please check out our code of conduct to get up to speed how we do things.

Artwork

Discovered via the interwebs, the artwork was created by Marcus Olsson and Jon Calhoun for Gophercises.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// UnitLogger specifies the option to provide a logger for the work unit.
	UnitLogger = func(l *zap.Logger) Option {
		return func(o *UnitOptions) {
			o.Logger = l
		}
	}

	// UnitScope specifies the option to provide a metric scope for the work unit.
	UnitScope = func(s tally.Scope) Option {
		return func(o *UnitOptions) {
			o.Scope = s
		}
	}
)
View Source
var (

	// ErrMissingDataMapper represents the error that is returned
	// when attempting to add, alter, remove, or register an entity
	// that doesn't have a corresponding data mapper.
	ErrMissingDataMapper = errors.New("missing data mapper for entity")
)

Functions

This section is empty.

Types

type DataMapper

type DataMapper interface {
	Insert(...interface{}) error
	Update(...interface{}) error
	Delete(...interface{}) error
}

DataMapper represents a creator, modifier, and deleter of entities.

type Option

type Option func(*UnitOptions)

Option applies an option to the provided configuration.

type SQLDataMapper

type SQLDataMapper interface {
	Insert(*sql.Tx, ...interface{}) error
	Update(*sql.Tx, ...interface{}) error
	Delete(*sql.Tx, ...interface{}) error
}

SQLDataMapper represents a creator, modifier, and deleter of entities persisted in SQL data stores.

type TypeName

type TypeName string

TypeName represents an entity's type.

func TypeNameOf

func TypeNameOf(entity interface{}) TypeName

TypeNameOf provides the type name for the provided entity.

func (TypeName) String

func (t TypeName) String() string

String provides the string representation of the type name.

type Unit

type Unit interface {

	// Register tracks the provided entities as clean.
	Register(...interface{}) error

	// Add marks the provided entities as new additions.
	Add(...interface{}) error

	// Alter marks the provided entities as modifications.
	Alter(...interface{}) error

	// Remove marks the provided entities as removals.
	Remove(...interface{}) error

	// Save commits the new additions, modifications, and removals
	// within the work unit to a persistent store.
	Save() error
}

Unit represents an atomic set of entity changes.

func NewBestEffortUnit

func NewBestEffortUnit(
	mappers map[TypeName]DataMapper, options ...Option) (Unit, error)

NewBestEffortUnit constructs a work unit that when faced with adversity, attempts rollback a single time.

func NewSQLUnit

func NewSQLUnit(
	mappers map[TypeName]SQLDataMapper,
	db *sql.DB,
	options ...Option,
) (Unit, error)

NewSQLUnit constructs a work unit for SQL data stores.

type UnitOptions

type UnitOptions struct {
	Logger *zap.Logger
	Scope  tally.Scope
}

UnitOptions represents the configuration options for the work unit.

type Uniter

type Uniter interface {

	//Unit constructs a new work unit.
	Unit() (Unit, error)
}

Uniter represents a factory for work units.

func NewBestEffortUniter

func NewBestEffortUniter(
	mappers map[TypeName]DataMapper, options ...Option) Uniter

NewBestEffortUniter constructs a new best effort unit factory.

func NewSQLUniter

func NewSQLUniter(
	mappers map[TypeName]SQLDataMapper,
	db *sql.DB,
	options ...Option,
) Uniter

NewSQLUniter constructs a new SQL work unit factory.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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