Documentation
¶
Overview ¶
Package openfeature provides an OpenFeature-compatible feature flag provider that integrates with Datadog Remote Config for server-side feature flag evaluation.
Overview ¶
This package implements the OpenFeature Provider interface, allowing applications to evaluate feature flags using configurations delivered dynamically through Datadog's Remote Config system. The provider supports all standard OpenFeature flag types: boolean, string, integer, float, and JSON objects.
Key Features ¶
- Dynamic flag configuration via Datadog Remote Config
- Support for all OpenFeature flag types (boolean, string, integer, float, JSON)
- Advanced targeting with attribute-based conditions
- Traffic sharding for gradual rollouts and A/B testing
- Time-based flag scheduling (start/end times)
- Thread-safe concurrent flag evaluation
- Comprehensive error handling with proper OpenFeature error codes
Basic Usage ¶
To use the Datadog OpenFeature provider, create a new provider instance and register it with the OpenFeature SDK:
import (
"github.com/DataDog/dd-trace-go/v2/openfeature"
of "github.com/open-feature/go-sdk/openfeature"
)
// Create and register the provider
provider, err := openfeature.NewDatadogProvider()
if err != nil {
log.Fatal(err)
}
defer provider.Shutdown()
// This can take a few seconds to complete as it waits for Remote Config initialization
err = of.SetProviderAndWait(provider)
if err != nil {
log.Fatal(err)
}
// Create a client and evaluate flags
client := of.NewClient("my-app")
ctx := context.Background()
// Evaluate a boolean flag
enabled, err := client.BooleanValue(ctx, "new-feature", false, of.EvaluationContext{})
if err != nil {
log.Printf("Failed to evaluate flag: %v", err)
}
if enabled {
// Execute new feature code
}
Targeting Context ¶
The provider supports attribute-based targeting using the OpenFeature evaluation context. You can pass user attributes to determine which flag variant a user receives:
evalCtx := of.NewEvaluationContext("user-123", map[string]interface{}{
"country": "US",
"tier": "premium",
"age": 25,
"email": "user@example.com",
})
value, err := client.StringValue(ctx, "api-version", "v1", evalCtx)
The provider automatically looks for a targeting key "targetingKey" (OpenFeature standard)
Flag Configuration ¶
Feature flags are configured through Datadog Remote Config and include:
- Flag metadata (key, enabled status, variation type)
- Variants with their values
- Allocation rules with targeting conditions
- Traffic distribution (sharding) configuration
- Optional time windows for scheduled rollouts
The configuration format follows the Datadog Feature Flag Evaluation (FFE) schema.
Evaluation Logic ¶
Flag evaluation follows this order:
- Check if configuration is loaded (error if not)
- Check if flag exists (FLAG_NOT_FOUND error if not)
- Check if flag is enabled (return default with DISABLED reason if not)
- Evaluate allocations in order (first match wins): a. Check time window constraints (startAt/endAt) b. Evaluate targeting rules (OR logic between rules, AND within conditions) c. Evaluate traffic sharding (consistent hash-based distribution)
- Return matched variant or default value
Targeting Conditions ¶
The provider supports various condition operators:
Numeric comparisons:
- LT, LTE, GT, GTE: Compare numeric attributes
String matching:
- MATCHES, NOT_MATCHES: Regex pattern matching
Set membership:
- ONE_OF, NOT_ONE_OF: Check if attribute is in a list
Null checks:
- IS_NULL: Check if attribute is present or absent
Example configuration structure (conceptual):
{
"flags": {
"premium-feature": {
"key": "premium-feature",
"enabled": true,
"variationType": "BOOLEAN",
"variations": {
"on": {"key": "on", "value": true},
"off": {"key": "off", "value": false}
},
"allocations": [{
"key": "premium-users",
"rules": [{
"conditions": [{
"operator": "ONE_OF",
"attribute": "tier",
"value": ["premium", "enterprise"]
}]
}],
"splits": [{
"shards": [{
"salt": "feature-salt",
"ranges": [{"start": 0, "end": 8192}],
"totalShards": 8192
}],
"variationKey": "on"
}]
}]
}
}
}
Traffic Sharding ¶
The provider uses consistent hashing (MD5) for deterministic traffic distribution. This ensures users consistently receive the same variant across evaluations.
Sharding allows for:
- Gradual rollouts (e.g., 10% -> 50% -> 100%)
- A/B testing with precise traffic splits
- Canary deployments
The default total shards is 8192, providing fine-grained control over traffic distribution percentages.
Error Handling ¶
The provider properly maps errors to OpenFeature error codes:
- FLAG_NOT_FOUND: Requested flag doesn't exist in configuration
- TYPE_MISMATCH: Flag value type doesn't match requested type
- PARSE_ERROR: Error parsing or converting flag value
- GENERAL: Other errors (e.g., no configuration loaded)
Errors are returned through the OpenFeature ResolutionDetail, and the default value is returned when errors occur.
Thread Safety ¶
The provider is fully thread-safe and can handle concurrent flag evaluations while configuration updates are in progress. Configuration updates use a read-write mutex to ensure consistency.
Remote Config Integration ¶
The provider automatically subscribes to Datadog Remote Config updates using the FFE_FLAGS product (capability 46). When new configurations are received, they are validated and applied atomically.
Configuration updates are acknowledged back to Remote Config with appropriate status codes (acknowledged for success, error for validation failures).
Prerequisites ¶
Before creating the provider, ensure that:
- The Datadog tracer is started (tracer.Start()) OR
- Remote Config client is properly configured
If the default Remote Config setup fails, the provider creation will return an error asking you to call tracer.Start first.
Performance Considerations ¶
- Regex patterns are compiled once and cached for reuse
- Read locks are used for flag evaluation (multiple concurrent reads)
- Write locks only during configuration updates
- MD5 hashing is used for sharding (fast, non-cryptographic)
Example: Complete Integration ¶
package main
import (
"context"
"log"
"github.com/DataDog/dd-trace-go/v2/ddtrace/tracer"
"github.com/DataDog/dd-trace-go/v2/openfeature"
of "github.com/open-feature/go-sdk/openfeature"
)
func main() {
// Start Datadog tracer (required for Remote Config)
tracer.Start()
defer tracer.Stop()
// Create OpenFeature provider
provider, err := openfeature.NewDatadogProvider(openfeature.ProviderConfig{})
if err != nil {
log.Fatalf("Failed to create provider: %v", err)
}
defer provider.Shutdown()
// Register provider with OpenFeature
if err := of.SetProviderAndWait(provider); err != nil {
log.Fatalf("Failed to set provider: %v", err)
}
// Create client for your application
client := of.NewClient("my-service")
// Evaluate flags with user context
ctx := context.Background()
evalCtx := of.NewEvaluationContext("user-123", map[string]interface{}{
"country": "US",
"tier": "premium",
})
// Boolean flag
if enabled, _ := client.BooleanValue(ctx, "new-checkout", false, evalCtx); enabled {
log.Println("New checkout experience enabled")
}
// String flag
apiVersion, _ := client.StringValue(ctx, "api-version", "v1", evalCtx)
log.Printf("Using API version: %s", apiVersion)
// Integer flag
rateLimit, _ := client.IntValue(ctx, "rate-limit", 100, evalCtx)
log.Printf("Rate limit: %d requests/minute", rateLimit)
// Float flag
discountRate, _ := client.FloatValue(ctx, "discount-rate", 0.0, evalCtx)
log.Printf("Discount rate: %.2f%%", discountRate*100)
// JSON/Object flag
config, _ := client.ObjectValue(ctx, "feature-config", nil, evalCtx)
log.Printf("Feature config: %+v", config)
}
Testing ¶
For testing purposes, you can create a provider without Remote Config and manually update its configuration:
// Use the internal constructor for testing
provider := openfeature.newDatadogProvider()
// Manually set test configuration
config := &universalFlagConfiguration{
Format: "SERVER",
Flags: map[string]*flag{
"test-flag": {
Key: "test-flag",
Enabled: true,
VariationType: valueTypeBoolean,
Variations: map[string]*variant{
"on": {Key: "on", Value: true},
},
Allocations: []*allocation{},
},
},
}
provider.updateConfiguration(config)
Limitations ¶
- Configuration updates replace the entire flag set (no incremental updates)
- Provider shutdown doesn't fully unsubscribe from Remote Config yet
- Multi-config tracking (multiple Remote Config paths) not yet supported
Additional Resources ¶
- OpenFeature Specification: https://openfeature.dev/specification/
- Datadog Remote Config: https://docs.datadoghq.com/agent/remote_config/
- Datadog APM Go SDK: https://docs.datadoghq.com/tracing/setup_overview/setup/go/
Index ¶
- func NewDatadogProvider(ProviderConfig) (openfeature.FeatureProvider, error)
- type DatadogProvider
- func (p *DatadogProvider) BooleanEvaluation(_ context.Context, flagKey string, defaultValue bool, ...) openfeature.BoolResolutionDetail
- func (p *DatadogProvider) FloatEvaluation(_ context.Context, flagKey string, defaultValue float64, ...) openfeature.FloatResolutionDetail
- func (p *DatadogProvider) Hooks() []openfeature.Hook
- func (p *DatadogProvider) Init(openfeature.EvaluationContext) error
- func (p *DatadogProvider) IntEvaluation(_ context.Context, flagKey string, defaultValue int64, ...) openfeature.IntResolutionDetail
- func (p *DatadogProvider) Metadata() openfeature.Metadata
- func (p *DatadogProvider) ObjectEvaluation(_ context.Context, flagKey string, defaultValue any, ...) openfeature.InterfaceResolutionDetail
- func (p *DatadogProvider) Shutdown()
- func (p *DatadogProvider) StringEvaluation(_ context.Context, flagKey string, defaultValue string, ...) openfeature.StringResolutionDetail
- type ProviderConfig
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewDatadogProvider ¶
func NewDatadogProvider(ProviderConfig) (openfeature.FeatureProvider, error)
NewDatadogProvider creates a new Datadog OpenFeature provider. It subscribes to Remote Config updates and automatically updates the provider's configuration when new flag configurations are received.
The provider will be ready to use immediately, but flag evaluations will return errors until the first configuration is received from Remote Config.
Returns an error if the default configuration of the Remote Config client is NOT working In this case, please call tracer.Start before creating the provider.
Types ¶
type DatadogProvider ¶
type DatadogProvider struct {
// contains filtered or unexported fields
}
DatadogProvider is an OpenFeature provider that evaluates feature flags using configuration received from Datadog Remote Config.
func (*DatadogProvider) BooleanEvaluation ¶
func (p *DatadogProvider) BooleanEvaluation( _ context.Context, flagKey string, defaultValue bool, flatCtx openfeature.FlattenedContext, ) openfeature.BoolResolutionDetail
BooleanEvaluation evaluates a boolean feature flag.
func (*DatadogProvider) FloatEvaluation ¶
func (p *DatadogProvider) FloatEvaluation( _ context.Context, flagKey string, defaultValue float64, flatCtx openfeature.FlattenedContext, ) openfeature.FloatResolutionDetail
FloatEvaluation evaluates a numeric (float) feature flag.
func (*DatadogProvider) Hooks ¶
func (p *DatadogProvider) Hooks() []openfeature.Hook
Hooks returns the hooks for this provider. Currently returns an empty slice as we don't have provider-level hooks.
func (*DatadogProvider) Init ¶
func (p *DatadogProvider) Init(openfeature.EvaluationContext) error
Init initializes the provider. For the Datadog provider, this is waiting for the first configuration to be loaded.
func (*DatadogProvider) IntEvaluation ¶
func (p *DatadogProvider) IntEvaluation( _ context.Context, flagKey string, defaultValue int64, flatCtx openfeature.FlattenedContext, ) openfeature.IntResolutionDetail
IntEvaluation evaluates an integer feature flag.
func (*DatadogProvider) Metadata ¶
func (p *DatadogProvider) Metadata() openfeature.Metadata
Metadata returns provider metadata including the provider name.
func (*DatadogProvider) ObjectEvaluation ¶
func (p *DatadogProvider) ObjectEvaluation( _ context.Context, flagKey string, defaultValue any, flatCtx openfeature.FlattenedContext, ) openfeature.InterfaceResolutionDetail
ObjectEvaluation evaluates a structured (JSON) feature flag.
func (*DatadogProvider) Shutdown ¶
func (p *DatadogProvider) Shutdown()
Shutdown shuts down the provider and stops Remote Config updates.
func (*DatadogProvider) StringEvaluation ¶
func (p *DatadogProvider) StringEvaluation( _ context.Context, flagKey string, defaultValue string, flatCtx openfeature.FlattenedContext, ) openfeature.StringResolutionDetail
StringEvaluation evaluates a string feature flag.
type ProviderConfig ¶
type ProviderConfig struct {
}