Skip to content

Commit

Permalink
Improve the documentation for the public API (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
creativecreature authored Jul 8, 2024
1 parent ce5fb56 commit 6828005
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 91 deletions.
54 changes: 10 additions & 44 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,16 @@ linters:
- asciicheck # checks that your code does not contain non-ASCII identifiers
- bidichk # checks for dangerous unicode character sequences
- bodyclose # checks whether HTTP response body is closed successfully
# - cyclop # checks function and package cyclomatic complexity
- cyclop # checks function and package cyclomatic complexity
- dupl # tool for code clone detection
- durationcheck # checks for two durations multiplied together
- errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error
- errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13
- execinquery # checks query string in Query function which reads your Go src files and warning it finds
- exhaustive # checks exhaustiveness of enum switch statements
- exportloopref # checks for pointers to enclosing loop variables
- forbidigo # forbids identifiers
- funlen # tool for detection of long functions
- gocheckcompilerdirectives # validates go compiler directive comments (//go:)
# - gochecknoglobals # checks that no global variables exist
- gochecknoinits # checks that no init functions are present in Go code
- gochecksumtype # checks exhaustiveness on Go "sum types"
- gocognit # computes and checks the cognitive complexity of functions
Expand Down Expand Up @@ -98,36 +96,28 @@ linters:
- decorder # checks declaration order and count of types, constants, variables and functions
- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
- paralleltest # detects missing usage of t.Parallel() method in your Go test
- tagalign # checks that struct tags are well aligned

## you may want to enable
## may want to enable
# - wrapcheck # checks that errors returned from external packages are wrapped
#- gci # controls golang package import order and makes it always deterministic
#- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega
#- godox # detects FIXME, TODO and other comment keywords
#- goheader # checks is file header matches to pattern
#- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters
#- interfacebloat # checks the number of methods inside an interface
#- ireturn # accept interfaces, return concrete types
#- tagalign # checks that struct tags are well aligned
#- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope
#- wrapcheck # checks that errors returned from external packages are wrapped
#- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event
# - ireturn # accept interfaces, return concrete types
# - varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope
# - nlreturn # checks for a new line before return and branch statements to increase code clarity

## disabled
# - lll # reports long lines
# - exhaustruct # [highly recommend to enable] checks if all structure fields are initialized
#- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages
#- dupword # [useless without config] checks for duplicate words in the source code
#- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted
#- forcetypeassert # [replaced by errcheck] finds forced type assertions
# - exhaustruct # checks if all structure fields are initialized
#- goerr113 # [too strict] checks the errors handling expressions
#- gofmt # [replaced by goimports] checks whether code was gofmt-ed
#- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed
#- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase
#- grouper # analyzes expression groups
#- importas # enforces consistent import aliases
#- maintidx # measures the maintainability index of each function
#- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines
#- tagliatelle # checks the struct tags
#- contextcheck # Check whether the function uses a non-inherited context.

Expand Down Expand Up @@ -163,33 +153,9 @@ linters-settings:
nlreturn:
block-size: 8

exhaustruct:
# Default: []
exclude:
- ".*_test.go$"
# std libs
- "^net/http.Client$"
- "^net/http.Cookie$"
- "^net/http.Request$"
- "^net/http.Response$"
- "^net/http.Server$"
- "^net/http.Transport$"
- "^net/url.URL$"
- "^os/exec.Cmd$"
- "^reflect.StructField$"
# public libs
- "^github.com/Shopify/sarama.Config$"
- "^github.com/Shopify/sarama.ProducerMessage$"
- "^github.com/mitchellh/mapstructure.DecoderConfig$"
- "^github.com/prometheus/client_golang/.+Opts$"
- "^github.com/spf13/cobra.Command$"
- "^github.com/spf13/cobra.CompletionOptions$"
- "^github.com/stretchr/testify/mock.Mock$"
- "^github.com/testcontainers/testcontainers-go.+Request$"
- "^github.com/testcontainers/testcontainers-go.FromDockerfile$"
- "^golang.org/x/tools/go/analysis.Analyzer$"
- "^google.golang.org/protobuf/.+Options$"
- "^gopkg.in/yaml.v3.Node$"
gosec:
excludes:
- G404 # Ignore insecure random number source.

funlen:
# Checks the number of lines in a function.
Expand Down
81 changes: 74 additions & 7 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ func (c *Client[T]) getWithState(key string) (value T, exists, markedAsMissing,
}

// Get retrieves a single value from the cache.
//
// Parameters:
//
// key - The key to be retrieved.
//
// Returns:
//
// The value corresponding to the key and a boolean indicating if the value was found.
func (c *Client[T]) Get(key string) (T, bool) {
shard := c.getShard(key)
val, ok, markedAsMissing, refresh := shard.get(key)
Expand All @@ -143,6 +151,14 @@ func (c *Client[T]) Get(key string) (T, bool) {
}

// GetMany retrieves multiple values from the cache.
//
// Parameters:
//
// keys - The list of keys to be retrieved.
//
// Returns:
//
// A map of keys to their corresponding values.
func (c *Client[T]) GetMany(keys []string) map[string]T {
records := make(map[string]T, len(keys))
for _, key := range keys {
Expand All @@ -155,9 +171,18 @@ func (c *Client[T]) GetMany(keys []string) map[string]T {

// GetManyKeyFn follows the same API as GetOrFetchBatch and PassthroughBatch.
// You provide it with a slice of IDs and a keyFn, which is applied to create
// the cache key. The returned map uses the IDs as keys instead of the cache key.
// If you've used ScanKeys to retrieve the actual keys, you can retrieve the records
// using GetMany instead.
// the cache key. The returned map uses the IDs as keys instead of the cache
// key. If you've used ScanKeys to retrieve the actual keys, you can retrieve
// the records using GetMany instead.
//
// Parameters:
//
// ids - The list of IDs to be retrieved.
// keyFn - A function that generates the cache key for each ID.
//
// Returns:
//
// A map of IDs to their corresponding values.
func (c *Client[T]) GetManyKeyFn(ids []string, keyFn KeyFn) map[string]T {
records := make(map[string]T, len(ids))
for _, id := range ids {
Expand All @@ -168,7 +193,16 @@ func (c *Client[T]) GetManyKeyFn(ids []string, keyFn KeyFn) map[string]T {
return records
}

// Set writes a single value to the cache. Returns true if it triggered an eviction.
// Set writes a single value to the cache.
//
// Parameters:
//
// key - The key to be set.
// value - The value to be associated with the key.
//
// Returns:
//
// A boolean indicating if the set operation triggered an eviction.
func (c *Client[T]) Set(key string, value T) bool {
shard := c.getShard(key)
return shard.set(key, value, false)
Expand All @@ -181,7 +215,15 @@ func (c *Client[T]) StoreMissingRecord(key string) bool {
return shard.set(key, zero, true)
}

// SetMany writes a map of key value pairs to the cache.
// SetMany writes a map of key-value pairs to the cache.
//
// Parameters:
//
// records - A map of keys to values to be set in the cache.
//
// Returns:
//
// A boolean indicating if any of the set operations triggered an eviction.
func (c *Client[T]) SetMany(records map[string]T) bool {
var triggeredEviction bool
for key, value := range records {
Expand All @@ -193,9 +235,18 @@ func (c *Client[T]) SetMany(records map[string]T) bool {
return triggeredEviction
}

// SetManyKeyFn follows the same API as GetOrFetchBatch and PassThroughBatch. It
// takes a map of records where the keyFn is applied to each key in the map
// SetManyKeyFn follows the same API as GetOrFetchBatch and PassthroughBatch.
// It takes a map of records where the keyFn is applied to each key in the map
// before it's stored in the cache.
//
// Parameters:
//
// records - A map of IDs to values to be set in the cache.
// cacheKeyFn - A function that generates the cache key for each ID.
//
// Returns:
//
// A boolean indicating if any of the set operations triggered an eviction.
func (c *Client[T]) SetManyKeyFn(records map[string]T, cacheKeyFn KeyFn) bool {
var triggeredEviction bool
for id, value := range records {
Expand All @@ -208,6 +259,10 @@ func (c *Client[T]) SetManyKeyFn(records map[string]T, cacheKeyFn KeyFn) bool {
}

// ScanKeys returns a list of all keys in the cache.
//
// Returns:
//
// A slice of strings representing all the keys in the cache.
func (c *Client[T]) ScanKeys() []string {
keys := make([]string, 0, c.Size())
for _, shard := range c.shards {
Expand All @@ -217,6 +272,10 @@ func (c *Client[T]) ScanKeys() []string {
}

// Size returns the number of entries in the cache.
//
// Returns:
//
// An integer representing the total number of entries in the cache.
func (c *Client[T]) Size() int {
var sum int
for _, shard := range c.shards {
Expand All @@ -226,12 +285,20 @@ func (c *Client[T]) Size() int {
}

// Delete removes a single entry from the cache.
//
// Parameters:
//
// key: The key of the entry to be removed.
func (c *Client[T]) Delete(key string) {
shard := c.getShard(key)
shard.delete(key)
}

// NumKeysInflight returns the number of keys that are currently being fetched.
//
// Returns:
//
// An integer representing the total number of keys that are currently being fetched.
func (c *Client[T]) NumKeysInflight() int {
c.inFlightMutex.Lock()
defer c.inFlightMutex.Unlock()
Expand Down
8 changes: 4 additions & 4 deletions distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet
}

return func(ctx context.Context, ids []string) (map[string]V, error) {
// We need to be able to lookup the ID of the record based on the key.
// We need to be able to look up the ID of the record based on the key.
keyIDMap := make(map[string]string, len(ids))
keys := make([]string, 0, len(ids))
for _, id := range ids {
Expand Down Expand Up @@ -193,7 +193,7 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet

// If distributedStaleStorage isn't enabled it means all records are fresh, otherwise checked the CreatedAt time.
if !c.distributedEarlyRefreshes || c.clock.Since(record.CreatedAt) < c.distributedRefreshAfterDuration {
// We never wan't to return missing records.
// We never want to return missing records.
if !record.IsMissingRecord {
fresh[id] = record.Value
} else {
Expand All @@ -205,7 +205,7 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet
idsToRefresh = append(idsToRefresh, id)
c.reportDistributedRefresh()

// We never wan't to return missing records.
// We never want to return missing records.
if !record.IsMissingRecord {
stale[id] = record.Value
} else {
Expand All @@ -218,7 +218,7 @@ func distributedBatchFetch[V, T any](c *Client[T], keyFn KeyFn, fetchFn BatchFet
}

dataSourceResponses, err := fetchFn(ctx, idsToRefresh)
// Incase of an error, we'll proceed with the ones we got from the distributed storage.
// In case of an error, we'll proceed with the ones we got from the distributed storage.
if err != nil {
for i := 0; i < len(stale); i++ {
c.reportDistributedStaleFallback()
Expand Down
73 changes: 64 additions & 9 deletions fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,40 @@ func getFetch[V, T any](ctx context.Context, c *Client[T], key string, fetchFn F
}

// GetOrFetch attempts to retrieve the specified key from the cache. If the value
// is absent, it invokes the "fetchFn" function to obtain it and then stores
// the result. Additionally, when background refreshes is enabled, GetOrFetch
// determines if the record needs refreshing and, if necessary, schedules this
// task for background execution.
// is absent, it invokes the fetchFn function to obtain it and then stores the result.
// Additionally, when background refreshes are enabled, GetOrFetch determines if the record
// needs refreshing and, if necessary, schedules this task for background execution.
//
// Parameters:
//
// ctx - The context to be used for the request.
// key - The key to be fetched.
// fetchFn - Used to retrieve the data from the underlying data source if the key is not found in the cache.
//
// Returns:
//
// The value corresponding to the key and an error if one occurred.
func (c *Client[T]) GetOrFetch(ctx context.Context, key string, fetchFn FetchFn[T]) (T, error) {
return getFetch[T, T](ctx, c, key, fetchFn)
}

// GetOrFetch is a convenience function that performs type assertion on the result of client.GetOrFetch.
//
// Parameters:
//
// ctx - The context to be used for the request.
// c - The cache client.
// key - The key to be fetched.
// fetchFn - Used to retrieve the data from the underlying data source if the key is not found in the cache.
//
// Returns:
//
// The value corresponding to the key and an error if one occurred.
//
// Type Parameters:
//
// V - The type returned by the fetchFn. Must be assignable to T.
// T - The type stored in the cache.
func GetOrFetch[V, T any](ctx context.Context, c *Client[T], key string, fetchFn FetchFn[V]) (V, error) {
res, err := getFetch[V, T](ctx, c, key, fetchFn)
return unwrap[V](res, err)
Expand Down Expand Up @@ -104,16 +129,46 @@ func getFetchBatch[V, T any](ctx context.Context, c *Client[T], ids []string, ke
return cachedRecords, nil
}

// GetOrFetchBatch attempts to retrieve the specified ids from the cache. If any
// of the values are absent, it invokes the fetchFn function to obtain them and
// then stores the result. Additionally, when background refreshes is enabled,
// GetOrFetch determines if any of the records needs refreshing and, if
// GetOrFetchBatch attempts to retrieve the specified ids from the cache. If
// any of the values are absent, it invokes the fetchFn function to obtain them
// and then stores the result. Additionally, when background refreshes are
// enabled, GetOrFetch determines if any of the records need refreshing and, if
// necessary, schedules this to be performed in the background.
//
// Parameters:
//
// ctx - The context to be used for the request.
// ids - The list of IDs to be fetched.
// keyFn - Used to generate the cache key for each ID.
// fetchFn - Used to retrieve the data from the underlying data source if any IDs are not found in the cache.
//
// Returns:
//
// A map of IDs to their corresponding values and an error if one occurred.
func (c *Client[T]) GetOrFetchBatch(ctx context.Context, ids []string, keyFn KeyFn, fetchFn BatchFetchFn[T]) (map[string]T, error) {
return getFetchBatch[T, T](ctx, c, ids, keyFn, fetchFn)
}

// GetOrFetchBatch is a convenience function that performs type assertion on the result of client.GetOrFetchBatch.
// GetOrFetchBatch is a convenience function that performs type assertion on the
// result of client.GetOrFetchBatch.
//
// Parameters:
//
// ctx - The context to be used for the request.
// c - The cache client.
// ids - The list of IDs to be fetched.
// keyFn - Used to prefix each ID in order to create a unique cache key.
// fetchFn - Used to retrieve the data from the underlying data source.
//
// Returns:
//
// A map of ids to their corresponding values and an error if one occurred.
//
// Type Parameters:
//
// V - The type returned by the fetchFn. Must be assignable to T.
// T - The type stored in the cache.

func GetOrFetchBatch[V, T any](ctx context.Context, c *Client[T], ids []string, keyFn KeyFn, fetchFn BatchFetchFn[V]) (map[string]V, error) {
res, err := getFetchBatch[V, T](ctx, c, ids, keyFn, fetchFn)
return unwrapBatch[V](res, err)
Expand Down
Loading

0 comments on commit 6828005

Please sign in to comment.