work

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2019 License: Apache-2.0 Imports: 6 Imported by: 0

README

work

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

GoDoc Build Status Coverage Status Release License

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 Inserter, Updater, and Deleter interfaces, as well as an instance of *sql.DB (db).

Construction

Starting with a sample setup,

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

// parameters.
i, u, d :=
	make(map[work.TypeName]work.Inserter),
	make(map[work.TypeName]work.Updater),
	make(map[work.TypeName]work.Deleter)
i[fType], i[bType] = fdm, bdm
u[fType], u[bType] = fdm, bdm
d[fType], d[bType] = fdm, bdm

we can create SQL work units:

unit, err := work.NewSQLUnit(work.SQLUnitParameters{
	UnitParameters: UnitParameters{
		Inserters: i,
		Updaters:  u,
		Deleters:  d,
	},
	ConnectionPool: db,
})
if err != nil {
	panic(err)
}

or we can create "best effort" units:

// best effort unit construction.
unit, err := work.NewBestEffortUnit(work.UnitParameters{
	Inserters: i,
	Updaters:  u,
	Deleters:  d,
})
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(work.SQLUnitParameters{
	UnitParameters: UnitParameters{
		Logger:    l,
		Inserters: i,
		Updaters:  u,
		Deleters:  d,
	},
	ConnectionPool: db,
})
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(work.UnitParameters{
	Scope:     s,
	Inserters: i,
	Updaters:  u,
	Deleters:  d,
})
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(work.SQLUnitParameters{
	UnitParameters: UnitParameters{
		Inserters: i,
		Updaters:  u,
		Deleters:  d,
	},
	ConnectionPool: 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

This section is empty.

Functions

This section is empty.

Types

type Deleter

type Deleter interface {

	// Delete removes the provided entities from a persistent store.
	Delete(...interface{}) error
}

Deleter represents a remover of entities.

type Inserter

type Inserter interface {

	// Insert creates the provided entities in a persistent store.
	Insert(...interface{}) error
}

Inserter represents a creator of entities.

type SQLUnitParameters

type SQLUnitParameters struct {
	UnitParameters

	ConnectionPool *sql.DB
}

SQLUnitParameters represents the dependencies and configuration required for SQL work units.

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{})

	// 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(parameters UnitParameters) Unit

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

func NewSQLUnit

func NewSQLUnit(parameters SQLUnitParameters) (Unit, error)

NewSQLUnit constructs a work unit for SQL stores.

type UnitParameters

type UnitParameters struct {

	//Inserters indicates the mappings between inserters
	//and the entity types they insert.
	Inserters map[TypeName]Inserter

	//Updates indicates the mappings between updaters
	//and the entity types they update.
	Updaters map[TypeName]Updater

	//Deleters indicates the mappings between deleters
	//and the entity types they delete.
	Deleters map[TypeName]Deleter

	//Logger represents the logger that the work unit will utilize.
	Logger *zap.Logger

	//Scope represents the metric scope that the work unit will utilize.
	Scope tally.Scope
}

UnitParameters represents the collection of dependencies and configuration needed for a 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(parameters UnitParameters) Uniter

NewBestEffortUniter constructs a new best effort unit factory.

func NewSQLUniter

func NewSQLUniter(parameters SQLUnitParameters) Uniter

NewSQLUniter constructs a new SQL work unit factory.

type Updater

type Updater interface {

	// Update modifies the provided entities within a persistent store.
	Update(...interface{}) error
}

Updater represents an alterer of entities.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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