OpenFeature Go SDK

repository·main·Indexed 42 days ago

https://github.com/open-feature/go-sdk

A vendor-agnostic implementation of the OpenFeature specification for Go (version 1.26+). It provides a standardized API to integrate feature flagging into applications, featuring a Multi-Provider for migrating between vendors, an InMemoryProvider for testing, and support for evaluation contexts, hooks, and transaction context propagation.

Tokens
8.1K
Snippets
23
Records
28
Agent score
53%

What's inside OpenFeature Go SDK

  1. How targeting works with Evaluation Context

    main

    Targeting allows flag evaluation to consider dynamic criteria like user location, IP, or version. You can provide this data using an EvaluationContext at three different levels:

    1. Global Context: Set via openfeature.SetEvaluationContext. Applies to all evaluations in the application.
    2. Client Context: Set via client.SetEvaluationContext. Applies to all evaluations made by that specific client.
    3. Invocation Context: Passed directly into a flag evaluation method (e.g., client.Boolean). Applies only to that specific call.
    // set a value to the global context
    openfeature.SetEvaluationContext(openfeature.NewTargetlessEvaluationContext(
        map[string]any{
            "region":  "us-east-1-iah-1a",
        },
    ))
    
    // set a value to the client context
    client := openfeature.NewDefaultClient()
    client.SetEvaluationContext(openfeature.NewTargetlessEvaluationContext(
        map[string]any{
            "version":  "1.4.6",
        },
    ))
    
    // set a value to the invocation context
    evalCtx := openfeature.NewEvaluationContext(
        "user-123",
        map[string]any{
            "company": "Initech",
        },
    )
    boolValue := client.Boolean(context.TODO(), "boolFlag", false, evalCtx)
  2. Implement a custom Multi-Provider strategy

    main

    You can define a custom strategy using multi.WithCustomStrategy combined with the multi.StrategyCustom constant. Because a strategy needs access to the underlying providers, you should use a StrategyConstructor to create a factory that closes over the providers.

    When implementing, it is highly recommended to use the exposed Evaluate and BuildDefaultResult functions:

    • Evaluate: Use this to evaluate a single NamedProvider. It determines the evaluation type based on the defaultVal parameter.
    • BuildDefaultResult: Use this when the strategy fails or encounters an error to return the appropriate default result.

    Custom strategy signature:

    StrategyFn[T FlagTypes] func(ctx context.Context, flag string, defaultValue T, flatCtx openfeature.FlattenedContext) openfeature.GenericResolutionDetail[T]
    option := multi.WithCustomStrategy(func(providers []multi.NamedProvider) multi.StrategyFn[FlagTypes] {
    	return func[T FlagTypes](ctx context.Context, flag string, defaultValue T, flatCtx openfeature.FlattenedContext) openfeature.GenericResolutionDetail[T] {
    		// implementation
    		// ...
    	}
    })
  3. Use Domains to associate clients with specific providers

    main

    A domain is a logical identifier used to bind clients to specific providers. If a client is assigned to a domain that has no provider registered, it will fall back to the default provider. You can register a named provider using SetNamedProvider.

    import (
        "github.com/open-feature/go-sdk/openfeature"
        "github.com/open-feature/go-sdk/openfeature/memprovider"
    )
    
    // Registering the default provider
    openfeature.SetProviderAndWait(openfeature.NoopProvider{})
    // Registering a named provider
    openfeature.SetNamedProvider("clientForCache", memprovider.NewInMemoryProvider(map[string]memprovider.InMemoryFlag{}))
    
    // A Client backed by default provider
    clientWithDefault := openfeature.NewDefaultClient()
    // A Client backed by NewCachedProvider
    clientForCache := openfeature.NewClient("clientForCache")
  4. Propagate Transaction Context

    main

    Transaction context is a container for transaction-specific evaluation context (e.g., user ID, IP). You can set this context in a request handler and use a propagator to ensure it is automatically applied to all flag evaluations within that transaction (e.g., an HTTP request or a thread).

    import "github.com/open-feature/go-sdk/openfeature"
    
    // set the TransactionContext
    ctx := openfeature.WithTransactionContext(context.TODO(), openfeature.EvaluationContext{})
    
    // get the TransactionContext from a context
    ec := openfeature.TransactionContext(ctx)
    
    // merge an EvaluationContext with the existing TransactionContext, preferring
    // the context that is passed to MergeTransactionContext
    tCtx := openfeature.MergeTransactionContext(ctx, openfeature.EvaluationContext{})
    
    // use TransactionContext in a flag evaluation
    _ = client.Boolean(tCtx, ....)
  5. How Multi-Provider evaluation strategies work

    main

    The Multi-Provider uses an EvaluationStrategy to decide how to resolve flag values from multiple providers. There are three built-in strategies:

    First Match Strategy (multi.StrategyFirstMatch)

    Calls providers sequentially. It uses the first provider that returns a result. If a provider returns a FLAG_NOT_FOUND error, it moves to the next provider. If a provider returns any other error, the process stops and returns the default value.

    First Success Strategy (multi.StrategyFirstSuccess)

    Calls providers sequentially. It uses the first provider that returns a response with no errors. Unlike First Match, if a provider returns an error, the Multi-Provider continues to the next provider until a successful result is found or all providers are exhausted.

    Comparison Strategy (multi.StrategyComparison)

    Calls providers in parallel. It collects all results and compares them.

    • If all providers agree on a value, that value is returned.
    • If they disagree, it uses a fallback provider (if configured via WithFallbackProvider) or returns the default value.
    • FLAG_NOT_FOUND results are excluded from the comparison.
    • If any provider returns an error other than FLAG_NOT_FOUND, evaluation stops immediately and returns that error with the default value.
    • Note on non-comparable types: If evaluating objects that are not Go comparable types, you must provide a Comparator function via WithCustomComparator, otherwise it will always trigger the fallback/default behavior.
  6. Quick start with OpenFeature Go SDK

    main

    To use OpenFeature in your Go application, you need to register a provider, create a client, and then evaluate your flags. This allows you to decouple your code from specific feature flag vendors.

    Steps:

    1. Register a Provider: Use openfeature.SetProviderAndWait() to set the global provider (e.g., openfeature.NoopProvider{} for testing or a vendor-specific provider).
    2. Create a Client: Initialize a client using openfeature.NewDefaultClient().
    3. Evaluate Flags: Use methods like .Boolean(), .String(), or .BooleanValue() to retrieve flag values. You must provide a context.Context, the flag key, a default value, and an EvaluationContext.
    package main
    
    import (
        "fmt"
        "context"
        "github.com/open-feature/go-sdk/openfeature"
    )
    
    func main() {
        // Register your feature flag provider
        openfeature.SetProviderAndWait(openfeature.NoopProvider{})
        // Create a new client
        client := openfeature.NewDefaultClient()
        // Evaluate your feature flag
        v2Enabled := client.Boolean(
            context.TODO(), "v2_enabled", true, openfeature.EvaluationContext{},
        )
        // Use the returned flag value
        if v2Enabled {
            fmt.Println("v2 is enabled")
        }
    }
  7. Register a Provider with OpenFeature

    main

    Providers act as the abstraction between a flag management system and the OpenFeature SDK. To use a provider, you must register it with OpenFeature. You can register a single global provider using SetProviderAndWait.

    openfeature.SetProviderAndWait(MyProvider{})
  8. Develop a custom FeatureProvider

    main

    To extend the SDK with a new provider, create a project that includes the OpenFeature SDK as a dependency and implement the openfeature.FeatureProvider interface.

    Required Methods:

    • Metadata() openfeature.Metadata: Returns the provider's identity.
    • Hooks() []openfeature.Hook: Returns a collection of hooks provided by this provider.
    • Evaluation methods for all supported types: BooleanEvaluation, StringEvaluation, FloatEvaluation, IntEvaluation, and ObjectEvaluation.

    Optional Interfaces:

    • openfeature.StateHandler: Implement Init(evaluationContext openfeature.EvaluationContext) error and Shutdown() to manage the provider's lifecycle.
    • openfeature.EventHandler: Implement EventChannel() <-chan openfeature.Event to expose an event stream that the SDK can listen to.
    package myfeatureprovider
    
    import (
      "context"
      "github.com/open-feature/go-sdk/openfeature"
    )
    
    type MyFeatureProvider struct{}
    
    func (i MyFeatureProvider) Metadata() openfeature.Metadata {
      return openfeature.Metadata{
        Name: "MyFeatureProvider",
      }
    }
    
    func (i MyFeatureProvider) Hooks() []openfeature.Hook {
      return []openfeature.Hook{}
    }
    
    func (i MyFeatureProvider) BooleanEvaluation(ctx context.Context, flag string, defaultValue bool, flatCtx openfeature.FlattenedContext) openfeature.BoolResolutionDetail {
      // implementation
      return openfeature.BoolResolutionDetail{}
    }
    
    // ... implement StringEvaluation, FloatEvaluation, IntEvaluation, ObjectEvaluation
  9. Use the OpenFeature Multi-Provider

    main

    The multi package allows you to wrap multiple underlying FeatureProvider instances into a single unified interface. This is useful for migrating between providers (e.g., moving from one SaaS to another) or combining multiple data sources like environment variables, local files, and databases.

    When evaluating a flag, the Multi-Provider consults its managed providers based on a defined Evaluation Strategy to determine the final result.

    WARNING

    The multi package for the go-sdk is experimental.

    import (
    	"github.com/open-feature/go-sdk/openfeature"
    	"github.com/open-feature/go-sdk/openfeature/multi"
    	"github.com/open-feature/go-sdk/memprovider"
    )
    
    mprovider, err := multi.NewProvider(
    	multi.StrategyFirstMatch,
    	multi.WithProvider("providerA", memprovider.NewInMemoryProvider(/*...*/)),
    	multi.WithProvider("providerB", myCustomProvider),
    )
    if err != nil {
    	return err
    }
    
    openfeature.SetNamedProviderAndWait("multiprovider", mprovider)
  10. Debug flag evaluations with LoggingHook

    main

    The Go SDK uses slog for structured logging. To troubleshoot flag evaluations, use the LoggingHook. It logs detailed information at key lifecycle stages (e.g., before, error). To see these logs, ensure your slog level is set to DEBUG.

    package main
    
    import (
        "context"
        "log/slog"
        "os"
    
        "github.com/open-feature/go-sdk/openfeature"
        "github.com/open-feature/go-sdk/openfeature/hooks"
        "github.com/open-feature/go-sdk/openfeature/memprovider"
    )
    
    func main() {
        // Register an in-memory provider with no flags
        openfeature.SetNamedProviderAndWait("example", memprovider.NewInMemoryProvider(map[string]memprovider.InMemoryFlag{}))
    
        // Configure slog
        handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug})
        logger := slog.New(handler)
    
        // Register a logging hook globally to run on all evaluations
        loggingHook := hooks.NewLoggingHook(false, logger)
        openfeature.AddHooks(loggingHook)
    
        // Create a new client
        client := openfeature.NewClient("example")
    
        // Attempt to evaluate a flag that doesn't exist
        _ = client.Boolean(context.TODO(), "not-exist", true, openfeature.EvaluationContext{})
    }