Skip to content

Commit

Permalink
Replace parameters with option pattern. Introduce DataMapper and SQLD…
Browse files Browse the repository at this point in the history
…ataMapper.
  • Loading branch information
fr33r committed Jul 19, 2019
1 parent efea8e2 commit e68286a
Show file tree
Hide file tree
Showing 24 changed files with 538 additions and 600 deletions.
15 changes: 3 additions & 12 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@
[prune]
go-tests = true
unused-packages = true

[[constraint]]
name = "github.com/stretchr/testify"
branch = "master"
65 changes: 18 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,49 +25,40 @@ There are a bundle of benefits you get by using work units:
- shorter transactions for SQL datastores.

## How to use it?

The following assumes your application has types (`fdm`, `bdm`) that satisfy the [`Inserter`][inserter-doc], [`Updater`][updater-doc],
and [`Deleter`][deleter-doc] interfaces, as well as an instance of [`*sql.DB`][db-doc] (`db`).

### Construction
Starting with a sample setup,

Starting with entities `Foo` and `Bar`,
```go
// 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:
```go
unit, err := work.NewSQLUnit(work.SQLUnitParameters{
UnitParameters: UnitParameters{
Inserters: i,
Updaters: u,
Deleters: d,
},
ConnectionPool: db,
})
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:
```go
// best effort unit construction.
unit, err := work.NewBestEffortUnit(work.UnitParameters{
Inserters: i,
Updaters: u,
Deleters: d,
})
mappers := map[work.TypeName]work.DataMapper {
fType: fdm,
bType: bdm,
}

unit, err := work.NewBestEffortUnit(mappers)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -115,15 +106,7 @@ We use [`zap`][zap] as our logging library of choice. To leverage the logs emitt
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,
})
unit, err := work.NewSQLUnit(mappers, db, work.UnitLogger(l))
if err != nil {
panic(err)
}
Expand All @@ -132,12 +115,7 @@ if err != nil {
### Metrics
For emitting metrics, we use [`tally`][tally]. To utilize the metrics emitted from the work units, pass in a [`Scope`][scope-doc] upon creation. Assuming we have an a scope `s`, it would look like so:
```go
unit, err := work.NewBestEffortUnit(work.UnitParameters{
Scope: s,
Inserters: i,
Updaters: u,
Deleters: d,
})
unit, err := work.NewBestEffortUnit(mappers, work.UnitScope(s))
if err != nil {
panic(err)
}
Expand All @@ -156,14 +134,7 @@ if err != nil {
### 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 [`Uniter`][uniter-doc]s to create instances of [`Unit`][unit-doc], like so:
```go
uniter := work.NewSQLUniter(work.SQLUnitParameters{
UnitParameters: UnitParameters{
Inserters: i,
Updaters: u,
Deleters: d,
},
ConnectionPool: db,
})
uniter := work.NewSQLUniter(mappers, db)

// create the unit.
unit, err := uniter.Unit()
Expand Down
73 changes: 59 additions & 14 deletions best_effort_unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,46 @@
package work

import (
"errors"
"fmt"

"go.uber.org/multierr"
"go.uber.org/zap"
)

var (
bestEffortUnitTag map[string]string = map[string]string{
bestEffortUnitTag = map[string]string{
"unit_type": "best_effort",
}
)

type bestEffortUnit struct {
unit

successfulInserts map[TypeName][]interface{}
successfulUpdates map[TypeName][]interface{}
successfulDeletes map[TypeName][]interface{}

mappers map[TypeName]DataMapper
successfulInserts map[TypeName][]interface{}
successfulUpdates map[TypeName][]interface{}
successfulDeletes map[TypeName][]interface{}
successfulInsertCount int
successfulUpdateCount int
successfulDeleteCount int
}

// NewBestEffortUnit constructs a work unit that when faced
// with adversity, attempts rollback a single time.
func NewBestEffortUnit(parameters UnitParameters) Unit {
func NewBestEffortUnit(
mappers map[TypeName]DataMapper, options ...Option) (Unit, error) {
if len(mappers) < 1 {
return nil, errors.New("must have at least one data mapper")
}

var o UnitOptions
for _, applyOption := range options {
applyOption(&o)
}
u := bestEffortUnit{
unit: newUnit(parameters),
unit: newUnit(o),
mappers: mappers,
successfulInserts: make(map[TypeName][]interface{}),
successfulUpdates: make(map[TypeName][]interface{}),
successfulDeletes: make(map[TypeName][]interface{}),
Expand All @@ -53,7 +64,7 @@ func NewBestEffortUnit(parameters UnitParameters) Unit {
if u.hasScope() {
u.scope = u.scope.Tagged(bestEffortUnitTag)
}
return &u
return &u, nil
}

func (u *bestEffortUnit) rollbackInserts() (err error) {
Expand All @@ -62,7 +73,7 @@ func (u *bestEffortUnit) rollbackInserts() (err error) {
u.logDebug("attempting to rollback inserted entities",
zap.Int("count", u.successfulInsertCount))
for typeName, inserts := range u.successfulInserts {
if err = u.deleters[typeName].Delete(inserts...); err != nil {
if err = u.mappers[typeName].Delete(inserts...); err != nil {
u.logError(err.Error(), zap.String("typeName", typeName.String()))
return
}
Expand All @@ -76,7 +87,7 @@ func (u *bestEffortUnit) rollbackUpdates() (err error) {
u.logDebug("attempting to rollback updated entities",
zap.Int("count", u.successfulUpdateCount))
for typeName, r := range u.registered {
if err = u.updaters[typeName].Update(r...); err != nil {
if err = u.mappers[typeName].Update(r...); err != nil {
u.logError(err.Error(), zap.String("typeName", typeName.String()))
return
}
Expand All @@ -90,7 +101,7 @@ func (u *bestEffortUnit) rollbackDeletes() (err error) {
u.logDebug("attempting to rollback deleted entities",
zap.Int("count", u.successfulDeleteCount))
for typeName, deletes := range u.successfulDeletes {
if err = u.inserters[typeName].Insert(deletes...); err != nil {
if err = u.mappers[typeName].Insert(deletes...); err != nil {
u.logError(err.Error(), zap.String("typeName", typeName.String()))
return
}
Expand Down Expand Up @@ -138,7 +149,7 @@ func (u *bestEffortUnit) applyInserts() (err error) {

u.logDebug("attempting to insert entities", zap.Int("count", len(u.additions)))
for typeName, additions := range u.additions {
if err = u.inserters[typeName].Insert(additions...); err != nil {
if err = u.mappers[typeName].Insert(additions...); err != nil {
err = multierr.Combine(err, u.rollback())
u.logError(err.Error(), zap.String("typeName", typeName.String()))
return
Expand All @@ -157,7 +168,7 @@ func (u *bestEffortUnit) applyUpdates() (err error) {

u.logDebug("attempting to update entities", zap.Int("count", len(u.alterations)))
for typeName, alterations := range u.alterations {
if err = u.updaters[typeName].Update(alterations...); err != nil {
if err = u.mappers[typeName].Update(alterations...); err != nil {
err = multierr.Combine(err, u.rollback())
u.logError(err.Error(), zap.String("typeName", typeName.String()))
return
Expand All @@ -176,7 +187,7 @@ func (u *bestEffortUnit) applyDeletes() (err error) {

u.logDebug("attempting to remove entities", zap.Int("count", len(u.removals)))
for typeName, removals := range u.removals {
if err = u.deleters[typeName].Delete(removals...); err != nil {
if err = u.mappers[typeName].Delete(removals...); err != nil {
err = multierr.Combine(err, u.rollback())
u.logError(err.Error(), zap.String("typeName", typeName.String()))
return
Expand All @@ -191,6 +202,40 @@ func (u *bestEffortUnit) applyDeletes() (err error) {
return
}

// Register tracks the provided entities as clean.
func (u *bestEffortUnit) Register(entities ...interface{}) {
u.register(entities...)
}

// Add marks the provided entities as new additions.
func (u *bestEffortUnit) Add(entities ...interface{}) error {
c := func(t TypeName) bool {
_, ok := u.mappers[t]
return ok
}
return u.add(c, entities...)
}

// Alter marks the provided entities as modifications.
func (u *bestEffortUnit) Alter(entities ...interface{}) error {
c := func(t TypeName) bool {
_, ok := u.mappers[t]
return ok
}
return u.alter(c, entities...)
}

// Remove marks the provided entities as removals.
func (u *bestEffortUnit) Remove(entities ...interface{}) error {
c := func(t TypeName) bool {
_, ok := u.mappers[t]
return ok
}
return u.remove(c, entities...)
}

// Save commits the new additions, modifications, and removals
// within the work unit to a persistent store.
func (u *bestEffortUnit) Save() (err error) {

//setup timer.
Expand Down

0 comments on commit e68286a

Please sign in to comment.