Skip to content

Instantly share code, notes, and snippets.

@kevinburke
Created July 14, 2017 23:00
Show Gist options
  • Save kevinburke/a10aed6d8d07ecd5efe658b21cd168c1 to your computer and use it in GitHub Desktop.
Save kevinburke/a10aed6d8d07ecd5efe658b21cd168c1 to your computer and use it in GitHub Desktop.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment