In this piece of code I'm trying to:
-
Check the local cache to see if the value exists
-
If not, use singleflight to ensure one request is made to fetch the value
-
Fetch the value from a third party API
-
Store that value in the cache.
func (vc *client) getAndCacheMessage(ctx context.Context, start, end time.Time, data url.Values) (*CacheResult, error) {
page, err := vc.client.Messages.GetMessagesInRange(start, end, data).Next(ctx)
if err != nil {
return nil, err
}
key := hash("messages", data.Encode(), start, end)
vc.cache.Set(key, page, frontPageTimeout)
return &CacheResult{Value: page}, nil
}
func (vc *client) cacheToMsg(user *config.User, val interface{}) (*MessagePage, uint64, error) {
result, ok := val.(*CacheResult)
if !ok {
return nil, 0, errors.New("Could not cast fetch result to a CacheResult")
}
page, ok := result.Value.(*twilio.MessagePage)
if !ok {
return nil, 0, errors.New("Could not cast fetch result to a MessagePage")
}
mp, err := NewMessagePage(page, vc.permission, user)
return mp, result.Time, err
}
func (vc *client) GetMessagePageInRange(ctx context.Context, user *config.User, start time.Time, end time.Time, data url.Values) (*MessagePage, uint64, error) {
key := hash("messages", data.Encode(), start, end)
val, err := vc.group.Do(key, func() (interface{}, error) {
page := new(twilio.MessagePage)
t, err := vc.cache.Get(key, page)
if err == nil {
return &CacheResult{t, page}, nil
}
return vc.getAndCacheMessage(ctx, start, end, data)
})
if err != nil {
return nil, 0, err
}
return vc.cacheToMsg(user, val)
}
The code is here: https://github.com/kevinburke/logrole/blob/master/views/client.go#L272-L299
Of the function calls to accomplish this, the following require interfaces:
-
Checking the local cache. The API is:
// Get gets the value at the key and decodes it into val. Returns the time // the value was stored in the cache, or an error, if the value was not // found, expired, or could not be decoded into val. func (c *Cache) Get(key string, val interface{}) (uint64, error)
-
Using singleflight. The API is:
// Do executes and returns the results of the given function, making sure // that only one execution is in-flight for a given key at a time. If a // duplicate comes in, the duplicate caller waits for the original to // complete and receives the same results. func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error)
-
Fetching the value from the third party API. This does not require interfaces, though there is this deeper in the API client.
// GetResource retrieves an instance resource with the given path part (e.g. // "/Messages") and sid (e.g. "MM123"). func (c *Client) GetResource(ctx context.Context, pathPart string, sid string, v interface{}) error // CreateResource makes a POST request to the given resource. func (c *Client) CreateResource(ctx context.Context, pathPart string, data url.Values, v interface{}) error { func (c *Client) UpdateResource(ctx context.Context, pathPart string, sid string, data url.Values, v interface{}) error func (c *Client) ListResource(ctx context.Context, pathPart string, data url.Values, v interface{}) error {
-
Store the value in the cache. The API is:
func (c *Cache) Set(key string, val interface{}, timeout time.Duration)
All of these required interface{}
in some places; it would be nice if that
wasn't as necessary.