Skip to content

Commit f12e483

Browse files
toddbaerterkagemini-code-assist[bot]sahidvelji
authored
feat(multi): add tracking, functional options (#446)
* refactor(multi): simplify API with functional options pattern Changes: - Add WithProvider option for registering named providers with optional hooks - Remove ProviderMap type in favor of variadic functional options - Change NamedProvider from struct to interface - Update NewProvider signature to take strategy first, then options - Add Track method support to MultiProvider for tracking events - Update tests, documentation and examples to reflect new API Signed-off-by: Roman Dmytrenko <[email protected]> * address PR feedback Signed-off-by: Roman Dmytrenko <[email protected]> * fix the data race Signed-off-by: Roman Dmytrenko <[email protected]> * simplify providers' hooks Signed-off-by: Roman Dmytrenko <[email protected]> * improve event handling in multiprovider - remove inboundEvents channel in favor of direct pipe channel - fix race condition in updateProviderStateFromEvent with lock - optimize Shutdown to only spawn goroutines for StateHandler providers - ensure outboundEvents channel is closed after all workers complete Signed-off-by: Roman Dmytrenko <[email protected]> * support ContextAwareStateHandler interface Signed-off-by: Roman Dmytrenko <[email protected]> * address PR feedback about readme Signed-off-by: Roman Dmytrenko <[email protected]> * Update openfeature/multi/multiprovider.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Roman Dmytrenko <[email protected]> * address PR feedback from gemini Signed-off-by: Roman Dmytrenko <[email protected]> * improve test coverage Signed-off-by: Roman Dmytrenko <[email protected]> * Update openfeature/multi/multiprovider.go Co-authored-by: Sahid Velji <[email protected]> Signed-off-by: Roman Dmytrenko <[email protected]> * Update openfeature/multi/multiprovider.go Co-authored-by: Sahid Velji <[email protected]> Signed-off-by: Roman Dmytrenko <[email protected]> * Update openfeature/multi/errors.go Co-authored-by: Sahid Velji <[email protected]> Signed-off-by: Roman Dmytrenko <[email protected]> * address PR review Signed-off-by: Roman Dmytrenko <[email protected]> * address PR feedback Signed-off-by: Roman Dmytrenko <[email protected]> --------- Signed-off-by: Roman Dmytrenko <[email protected]> Co-authored-by: Roman Dmytrenko <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Sahid Velji <[email protected]>
1 parent 1a0d39e commit f12e483

14 files changed

+815
-429
lines changed

openfeature/multi/README.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@ import (
3232
"github.com/open-feature/go-sdk/openfeature/memprovider"
3333
)
3434

35-
providers := make(multi.ProviderMap)
36-
providers["providerA"] = memprovider.NewInMemoryProvider(map[string]memprovider.InMemoryFlag{})
37-
providers["providerB"] = myCustomProvider
38-
mprovider, err := multi.NewProvider(providers, multi.StrategyFirstMatch)
35+
mprovider, err := multi.NewProvider(
36+
multi.StrategyFirstMatch,
37+
multi.WithProvider("providerA", memprovider.NewInMemoryProvider(/*...*/)),
38+
multi.WithProvider("providerB", myCustomProvider),
39+
)
3940
if err != nil {
40-
return err
41+
return err
4142
}
4243

4344
openfeature.SetNamedProviderAndWait("multiprovider", mprovider)
@@ -101,7 +102,7 @@ type StrategyConstructor func(providers []*NamedProvider) StrategyFn[FlagTypes]
101102

102103
Build your strategy to wrap around the slice of providers
103104
```go
104-
option := multi.WithCustomStrategy(func(providers []*NamedProvider) StrategyFn[FlagTypes] {
105+
option := multi.WithCustomStrategy(func(providers []NamedProvider) StrategyFn[FlagTypes] {
105106
return func[T FlagTypes](ctx context.Context, flag string, defaultValue T, flatCtx openfeature.FlattenedContext) openfeature.GenericResolutionDetail[T] {
106107
// implementation
107108
// ...
@@ -140,12 +141,12 @@ essentially a factory that allows the `StrategyFn` to wrap around a slice of `Na
140141
Allows for setting global hooks for the multi-provider. These are `openfeature.Hook` implementations that affect
141142
**all** internal `FeatureProvider` instances.
142143

143-
### `WithProviderHooks`
144+
### `WithProvider`
144145

145-
Allows for setting `openfeature.Hook` implementations on a specific named `FeatureProvider` within the multi-provider.
146-
This should only be used when hooks need to be attached to a `FeatureProvider` instance that does not implement that functionality.
147-
Using a provider name that is not known will cause an error to be returned during the creation time. This option can be
148-
used multiple times using unique provider names.
146+
Allows for registering a specific `FeatureProvider` instance under a unique provider name. Optional `openfeature.Hook`
147+
implementations may also be provided, which will execute only for this specific provider. This option can be used multiple
148+
times with unique provider names to register multiple providers.
149+
The order in which `WithProvider` options are provided determines the order in which the providers are registered and evaluated.
149150

150151
## `StrategyComparision` specific options
151152

openfeature/multi/comparison_strategy.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type Comparator func(values []any) bool
2424
// can be passed as long as ObjectEvaluation is never called with objects that are not comparable. The custom [Comparator]
2525
// will only be used for [of.FeatureProvider.ObjectEvaluation] if set. If [of.FeatureProvider.ObjectEvaluation] is
2626
// called without setting a [Comparator], and the returned object(s) are not comparable, then an error will occur.
27-
func newComparisonStrategy(providers []*NamedProvider, fallbackProvider of.FeatureProvider, comparator Comparator) StrategyFn[FlagTypes] {
27+
func newComparisonStrategy(providers []NamedProvider, fallbackProvider of.FeatureProvider, comparator Comparator) StrategyFn[FlagTypes] {
2828
return evaluateComparison[FlagTypes](providers, fallbackProvider, comparator)
2929
}
3030

@@ -81,7 +81,7 @@ func comparisonResolutionError(metadata of.FlagMetadata) of.ResolutionError {
8181
return of.NewGeneralResolutionError("comparison failure")
8282
}
8383

84-
func evaluateComparison[T FlagTypes](providers []*NamedProvider, fallbackProvider of.FeatureProvider, comparator Comparator) StrategyFn[T] {
84+
func evaluateComparison[T FlagTypes](providers []NamedProvider, fallbackProvider of.FeatureProvider, comparator Comparator) StrategyFn[T] {
8585
return func(ctx context.Context, flag string, defaultValue T, evalCtx of.FlattenedContext) of.GenericResolutionDetail[T] {
8686
if comparator == nil {
8787
comparator = defaultComparator
@@ -103,7 +103,7 @@ func evaluateComparison[T FlagTypes](providers []*NamedProvider, fallbackProvide
103103
// Short circuit if there's only one provider as no comparison nor workers are needed
104104
if len(providers) == 1 {
105105
result := Evaluate(ctx, providers[0], flag, defaultValue, evalCtx)
106-
metadata := setFlagMetadata(StrategyComparison, providers[0].Name, make(of.FlagMetadata))
106+
metadata := setFlagMetadata(StrategyComparison, providers[0].Name(), make(of.FlagMetadata))
107107
metadata[MetadataFallbackUsed] = false
108108
result.FlagMetadata = mergeFlagMeta(result.FlagMetadata, metadata)
109109
return result
@@ -124,13 +124,13 @@ func evaluateComparison[T FlagTypes](providers []*NamedProvider, fallbackProvide
124124
notFound := result.ResolutionDetail().ErrorCode == of.FlagNotFoundCode
125125
if !notFound && result.Error() != nil {
126126
return &ProviderError{
127-
ProviderName: closedProvider.Name,
128-
Err: result.Error(),
127+
ProviderName: closedProvider.Name(),
128+
err: result.Error(),
129129
}
130130
}
131131
if !notFound {
132132
resultChan <- &namedResult{
133-
name: closedProvider.Name,
133+
name: closedProvider.Name(),
134134
res: &result,
135135
}
136136
} else {
@@ -225,7 +225,7 @@ func evaluateComparison[T FlagTypes](providers []*NamedProvider, fallbackProvide
225225
if fallbackProvider != nil {
226226
fallbackResult := Evaluate(
227227
ctx,
228-
&NamedProvider{Name: "fallback", FeatureProvider: fallbackProvider},
228+
&namedProvider{name: "fallback", FeatureProvider: fallbackProvider},
229229
flag,
230230
defaultValue,
231231
evalCtx,

0 commit comments

Comments
 (0)