Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(go-feature-flag)!: GO Feature Flag provider rebuild #547

Merged
merged 17 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 4 additions & 28 deletions providers/go-feature-flag/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,17 @@ This is a complete feature flagging solution with the possibility to target only

## Install dependencies

The first things we will do is install the **Open Feature SDK** and the **GO Feature Flag provider**.
The first things we will do are to install the **Open Feature SDK** and the **GO Feature Flag provider**.

```shell
go get github.com/open-feature/go-sdk-contrib/providers/go-feature-flag
```

## Initialize your Open Feature provider

Despite other providers, this GO provider can be used with the **relay proxy** or used standalone
using the **GO Feature Flag module**.
### Connecting to the relay proxy

### Using the relay proxy

If you want to use the provider with the **relay proxy** you should set the field `Endpoint` in the options.
This provider has to connect with the **relay proxy**, to do that you should set the field `Endpoint` in the options.
By default it will use a default `HTTPClient` with a **timeout** configured at **10000** milliseconds. You can change
this configuration by providing your own configuration of the `HTTPClient`.

Expand All @@ -40,30 +37,9 @@ options := gofeatureflag.ProviderOptions{
provider, _ := gofeatureflag.NewProviderWithContext(ctx, options)
```

### Using the GO module _(standalone version)_
If you want to use the provider in standalone mode using the GO module, you should set the field `GOFeatureFlagConfig`
in the options.

You can check the [GO Feature Flag documentation website](https://docs.gofeatureflag.org) to look how to configure the
GO module.

#### Example
```go
options := gofeatureflag.ProviderOptions{
GOFeatureFlagConfig: &ffclient.Config{
PollingInterval: 10 * time.Second,
Context: context.Background(),
Retriever: &fileretriever.Retriever{
Path: "../testutils/module/flags.yaml",
},
},
}
provider, _ := gofeatureflag.NewProviderWithContext(ctx, options)
```

## Initialize your Open Feature client

To evaluate the flags you need to have an Open Feature configured in you app.
To evaluate the flag you need to have an Open Feature configured in you app.
This code block shows you how you can create a client that you can use in your application.

```go
Expand Down
16 changes: 5 additions & 11 deletions providers/go-feature-flag/go.mod
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
module github.com/open-feature/go-sdk-contrib/providers/go-feature-flag

go 1.21
go 1.21.0

toolchain go1.22.3
toolchain go1.22.5

require (
github.com/bluele/gcache v0.0.2
github.com/open-feature/go-sdk v1.11.0
github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.4
github.com/stretchr/testify v1.9.0
github.com/thomaspoignant/go-feature-flag v1.25.0
)

require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/apache/thrift v0.16.0 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/nikunjy/rules v1.5.0 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect
golang.org/x/text v0.16.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
150 changes: 6 additions & 144 deletions providers/go-feature-flag/go.sum

Large diffs are not rendered by default.

139 changes: 139 additions & 0 deletions providers/go-feature-flag/pkg/controller/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package controller

import (
"fmt"
"github.com/bluele/gcache"
of "github.com/open-feature/go-sdk/openfeature"
"hash/fnv"
"time"
)

const defaultCacheSize = 10000
const defaultCacheTTL = 1 * time.Minute

type Cache struct {
internalCache gcache.Cache
maxEventInMemory int64

Check failure on line 16 in providers/go-feature-flag/pkg/controller/cache.go

View workflow job for this annotation

GitHub Actions / lint

field `maxEventInMemory` is unused (unused)
ttl time.Duration
disabled bool
}

// NewCache creates a new cache with the given options.
func NewCache(cacheSize int, ttl time.Duration, disabled bool) *Cache {
if cacheSize == 0 {
cacheSize = defaultCacheSize
}
if ttl == 0 {
ttl = defaultCacheTTL
}
c := &Cache{
ttl: ttl,
disabled: disabled,
}
if cacheSize > 0 && !disabled {
c.internalCache = gcache.New(cacheSize).
LRU().
Build()
}
return c
}

// GetBool returns the boolean value of the flag from the cache.
func (c *Cache) GetBool(flag string, evalCtx of.FlattenedContext) (*of.BoolResolutionDetail, error) {
if c.disabled || c.internalCache == nil {
return nil, nil
}
cacheValue, err := c.internalCache.Get(c.buildCacheKey(flag, evalCtx))
if err != nil {
return nil, err
}
if value, ok := cacheValue.(of.BoolResolutionDetail); ok {
return &value, nil
}
return nil, fmt.Errorf("unexpected type in cache (expecting bool)")
}

// GetString returns the string value of the flag from the cache.
func (c *Cache) GetString(flag string, evalCtx of.FlattenedContext) (*of.StringResolutionDetail, error) {
if c.disabled || c.internalCache == nil {
return nil, nil
}
cacheValue, err := c.internalCache.Get(c.buildCacheKey(flag, evalCtx))
if err != nil {
return nil, err
}
if value, ok := cacheValue.(of.StringResolutionDetail); ok {
return &value, nil
}
return nil, fmt.Errorf("unexpected type in cache (expecting string)")
}

// GetFloat returns the float value of the flag from the cache.
func (c *Cache) GetFloat(flag string, evalCtx of.FlattenedContext) (*of.FloatResolutionDetail, error) {
if c.disabled || c.internalCache == nil {
return nil, nil
}
cacheValue, err := c.internalCache.Get(c.buildCacheKey(flag, evalCtx))
if err != nil {
return nil, err
}
if value, ok := cacheValue.(of.FloatResolutionDetail); ok {
return &value, nil
}
return nil, fmt.Errorf("unexpected type in cache (expecting float)")
}

// GetInt returns the int value of the flag from the cache.
func (c *Cache) GetInt(flag string, evalCtx of.FlattenedContext) (*of.IntResolutionDetail, error) {
if c.disabled || c.internalCache == nil {
return nil, nil
}
cacheValue, err := c.internalCache.Get(c.buildCacheKey(flag, evalCtx))
if err != nil {
return nil, err
}
if value, ok := cacheValue.(of.IntResolutionDetail); ok {
return &value, nil
}
return nil, fmt.Errorf("unexpected type in cache (expecting int)")
}

// GetInterface returns the interface value of the flag from the cache.
func (c *Cache) GetInterface(flag string, evalCtx of.FlattenedContext) (*of.InterfaceResolutionDetail, error) {
if c.disabled || c.internalCache == nil {
return nil, nil
}
cacheValue, err := c.internalCache.Get(c.buildCacheKey(flag, evalCtx))
if err != nil {
return nil, err
}
if value, ok := cacheValue.(of.InterfaceResolutionDetail); ok {
return &value, nil
}
return nil, fmt.Errorf("unexpected type in cache (expecting interface)")
}

// Set sets the value of the flag in the cache.
func (c *Cache) Set(flag string, evalCtx of.FlattenedContext, value interface{}) error {
if c.disabled || c.internalCache == nil {
return nil
}
if c.ttl >= 0 {
return c.internalCache.SetWithExpire(c.buildCacheKey(flag, evalCtx), value, c.ttl)
}
return c.internalCache.Set(c.buildCacheKey(flag, evalCtx), value)
}

func (c *Cache) Purge() {
if c.internalCache != nil {
c.internalCache.Purge()
}
}

// buildCacheKey builds a cache key from the flag and evaluation context.
func (c *Cache) buildCacheKey(flag string, evalCtx of.FlattenedContext) uint32 {
key := fmt.Sprintf("%s-%+v", flag, evalCtx)
h := fnv.New32a()
thomaspoignant marked this conversation as resolved.
Show resolved Hide resolved
_, _ = h.Write([]byte(key))
return h.Sum32()
}
Loading
Loading