work icon indicating copy to clipboard operation
work copied to clipboard

Injectable Identity Map

Open fr33r opened this issue 5 years ago • 0 comments

Description

Another well known design pattern presented by Martin Fowler is known as the Identity Map. Although very straightforward, this pattern can be incredibly powerful, as it reduce unnecessary round trips between you application and it's data store.

I propose being able to inject an Identity Map into the implementers of work.Unit. Doing so would allow the work units to manage the state of the identity map.

Example Usage

The Identity Map can be injected by creating a new option:

var (

    ...

    // UnitIdentityMap specifies the option to provide an identity map for the work unit.
    UnitIdentityMap = func(identityMap map[interface{}]Entity) Option {
        return func(o *UnitOptions) {
            o.IdentityMap = identityMap
        }
    }

    ...
)

where Entity is the following interface:

// Entity represents an entity managed by the work unit.
type Entity interface {
    ID() interface{}
}

The work.Unit interface would need to be adapted slightly:

type Unit interface {

    // Register tracks the provided entities as clean.
    Register(...Entity) error

    // Add marks the provided entities as new additions.
    Add(...Entity) error

    // Alter marks the provided entities as modifications.
    Alter(...Entity) error

    // Remove marks the provided entities as removals.
    Remove(...Entity) error

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

Lastly, each implementer of work.Unit will then appropriately store the provided entities to Register(...) into the Identity Map. ~~If an entity is passed into Alter(...) or Remove(...) and it also exists in the Identity Map, than it's entry will be removed.~~ Calling Save() on the work unit will clear the Identity Map if successful.

Since the consumer of the work provides the Identity Map, they have free will to use it as see fit. in particular, the consumer should check the Identity Map before performing retrieval operations against their data store. some like the following:

...

// Get retrieves the account with the UUID provided from the repository.
func (a AccountRepository) Get(uuid u.UUID) (Account, error) {
  query := NewFindByUUIDQuery(uuid, a.identityMap) // <-- pass in the identity map to query object.
  account, err := query.Execute() // <--- checks the identity map before issuing query.
  if err != nil {
    return Account{}, err
  }
  if err = a.unit.Register(account); err != nil { // <-- Register(...) will update the identity map.
    return Account{}, err
  }
  return account, nil
}

...

fr33r avatar Feb 15 '20 22:02 fr33r