Introduce support for caching.#55
Conversation
Codecov ReportBase: 96.45% // Head: 96.57% // Increases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## master #55 +/- ##
==========================================
+ Coverage 96.45% 96.57% +0.12%
==========================================
Files 13 14 +1
Lines 1296 1345 +49
==========================================
+ Hits 1250 1299 +49
Misses 34 34
Partials 12 12
Flags with carried forward coverage won't be shown. Click here to find out more.
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
|
|
||
| // Cached provides the entities that have been previously registered | ||
| // and have not been acted on via Add, Alter, or Remove. | ||
| Cached() *UnitCache |
There was a problem hiding this comment.
Could an interface be used here?
That gives flexible replacing sync.Map cache on Redis or an another provider in a distributed application.
There was a problem hiding this comment.
this is a great question and point. it's something i pondered on for a bit when designing this.
after careful consideration, i actually felt it was possible to support accepting arbitrary cache providers even though we return a struct. additionally, i wanted to keep the door open to being able to expand the functionality of UnitCache without it having to be a breaking change.
as this PR was written, UnitCache is essentially a wrapper around sync.Map, but really what it should be is a wrapper around an interface that consumers adhere to when they pass in their arbitrary cache clients.
for now i just went with the sync.Map route, but your comment here has helped confirm that i should go ahead and bite off the work to allow consumers to pass in various cache clients as options, and simply use sync.Map as the default. i will go ahead and slot in that work.
as an example for where i'm heading:
// input interface to handle arbitrary clients...
type UnitCacheClient interface {
Put(key, value interface{}
Load(key interface{}) (interface{}, bool)
}
// options...
var (
...
// consumers would write adaptors that adhere to UnitCacheClient.
// we could even provide some out of the box to make things easier.
CacheClient = func(c UnitCacheClient) UnitOption {
return func(o *UnitOptions) {
o.CacheClient = c
}
}
...
)
// default cache client
var UnitCacheClientDefault = &mapCacheClient{}
type UnitCache struct {
client UnitCacheClient
}and then internally (within work.UnitCache) we would instead wrap work.UnitCacheClient instead of sync.Map.
Description
Takes on #24!
Rationale
This feature unlocks the ability to prevent loading the same object (entity) from the applications data store. At the same call sites as
#Register, consumers of this package can now check whether the entity / entities being retrieved have already been retrieved and not been acted upon (also referred to as "dirty").For smaller applications, this ability may seem unnecessary, but as a codebase becomes larger and more complex, there typically arises a need to ensure particular objects are not loaded multiple times unnecessarily.
Callouts
Careful consideration was made into the approach for accomplish this. When assessing the pros and cons of whether or not to enforce an interface in the method signatures, it made more sense to instead leverage type assertions on provided entities instead. The following are the tradeoffs worth noting:
work.UnitCacheAKAunit.CacheMy initial swing at this had the
Cachedmethod returningmap[work.TypeName][]interface{}. However, there were some challenges with this approach:O(1)instead ofO(N), whereNis the number of cached entities of a particular type.work.UnitCache) provides additional flexibility, as it enables us to expand operations / simplifications for interacting with the cache in a backwards compatible way.Suggested Version
v4.0.0-beta.4Example Usage
(psuedocode)