Page:
OpenFeature Provider
No results
3
OpenFeature Provider
Insidious Fiddler edited this page 2026-02-06 21:04:12 +00:00
Table of Contents
OpenFeature Provider
Use Hoist with the OpenFeature SDK for vendor-neutral feature flag evaluation.
Overview
The Hoist OpenFeature provider wraps the native Hoist SDK and implements the OpenFeature FeatureProvider interface. This allows you to:
- Use the standard OpenFeature API
- Switch providers without code changes
- Leverage OpenFeature hooks and middleware
Installation
go get git.macco.dev/macco/hoist/sdks/go/openfeature
go get github.com/open-feature/go-sdk/openfeature
Quick Start
package main
import (
"context"
"log"
"git.macco.dev/macco/hoist/sdks/go/hoist"
hoistof "git.macco.dev/macco/hoist/sdks/go/openfeature"
of "github.com/open-feature/go-sdk/openfeature"
)
func main() {
// Create the Hoist provider
provider, err := hoistof.NewProvider(hoist.Config{
APIKey: "hoist_live_xxx",
BaseURL: "https://hoist.example.com",
})
if err != nil {
log.Fatal(err)
}
defer provider.Close()
// Register with OpenFeature
of.SetProvider(provider)
// Create a client
client := of.NewClient("my-app")
// Evaluate flags using standard OpenFeature API
ctx := context.Background()
enabled, err := client.BooleanValue(ctx, "dark-mode", false, of.EvaluationContext{
TargetingKey: "user-123",
Attributes: map[string]any{
"plan": "pro",
},
})
if err != nil {
log.Printf("evaluation error: %v", err)
}
if enabled {
// Dark mode enabled
}
}
Provider Configuration
The provider accepts the same configuration as the native Hoist client:
provider, err := hoistof.NewProvider(hoist.Config{
// Required
APIKey: "hoist_live_xxx",
BaseURL: "https://hoist.example.com",
// Optional
PollInterval: 30 * time.Second,
HTTPClient: customHTTPClient,
Logger: customLogger,
// Callbacks
OnReady: func() {
log.Println("Provider ready")
},
OnError: func(err error) {
log.Printf("Provider error: %v", err)
},
})
Evaluation Context Mapping
OpenFeature context maps to Hoist context:
| OpenFeature | Hoist |
|---|---|
TargetingKey |
TargetingKey |
Attributes["*"] |
Attributes["*"] |
// OpenFeature context
ofCtx := of.EvaluationContext{
TargetingKey: "user-123",
Attributes: map[string]any{
"email": "user@example.com",
"plan": "enterprise",
},
}
// Automatically converted to Hoist context:
// hoist.Context{
// TargetingKey: "user-123",
// Attributes: map[string]any{
// "email": "user@example.com",
// "plan": "enterprise",
// },
// }
Evaluation Methods
Boolean
value, err := client.BooleanValue(ctx, "feature-flag", false, evalCtx)
// With details
details, err := client.BooleanValueDetails(ctx, "feature-flag", false, evalCtx)
fmt.Printf("Value: %v, Reason: %s, Variant: %s\n",
details.Value, details.Reason, details.Variant)
String
value, err := client.StringValue(ctx, "string-flag", "default", evalCtx)
// With details
details, err := client.StringValueDetails(ctx, "string-flag", "default", evalCtx)
Integer
value, err := client.IntValue(ctx, "int-flag", 100, evalCtx)
// With details
details, err := client.IntValueDetails(ctx, "int-flag", 100, evalCtx)
Float
value, err := client.FloatValue(ctx, "float-flag", 0.5, evalCtx)
// With details
details, err := client.FloatValueDetails(ctx, "float-flag", 0.5, evalCtx)
Object (JSON)
value, err := client.ObjectValue(ctx, "json-flag", map[string]any{"default": true}, evalCtx)
// With details
details, err := client.ObjectValueDetails(ctx, "json-flag", map[string]any{"default": true}, evalCtx)
Reason Mapping
Hoist reasons are mapped to OpenFeature reasons:
| Hoist Reason | OpenFeature Reason |
|---|---|
ReasonDefault |
DefaultReason |
ReasonTargetingMatch |
TargetingMatchReason |
ReasonSegmentMatch |
TargetingMatchReason |
ReasonRollout |
TargetingMatchReason |
ReasonIdentityOverride |
TargetingMatchReason |
ReasonDisabled |
DisabledReason |
ReasonNotFound |
ErrorReason |
ReasonError |
ErrorReason |
Flag Metadata
Evaluation details include Hoist-specific metadata:
details, _ := client.BooleanValueDetails(ctx, "feature", false, evalCtx)
// Access metadata
ruleID := details.FlagMetadata["ruleId"]
segmentKey := details.FlagMetadata["segmentKey"]
version := details.FlagMetadata["version"]
OpenFeature Hooks
Use OpenFeature hooks for cross-cutting concerns:
// Logging hook
type LoggingHook struct{}
func (h LoggingHook) Before(ctx context.Context, hookCtx of.HookContext, hints of.HookHints) (*of.EvaluationContext, error) {
log.Printf("Evaluating: %s", hookCtx.FlagKey())
return nil, nil
}
func (h LoggingHook) After(ctx context.Context, hookCtx of.HookContext, details of.InterfaceEvaluationDetails, hints of.HookHints) error {
log.Printf("Result: %s = %v (reason: %s)", hookCtx.FlagKey(), details.Value, details.Reason)
return nil
}
func (h LoggingHook) Error(ctx context.Context, hookCtx of.HookContext, err error, hints of.HookHints) {
log.Printf("Error: %s: %v", hookCtx.FlagKey(), err)
}
func (h LoggingHook) Finally(ctx context.Context, hookCtx of.HookContext, hints of.HookHints) {
// Cleanup
}
// Register the hook
of.AddHooks(LoggingHook{})
Multiple Providers
Use named providers for different services:
// Production flags
prodProvider, _ := hoistof.NewProvider(hoist.Config{
APIKey: "hoist_live_prod",
BaseURL: "https://hoist.example.com",
})
of.SetNamedProvider("production", prodProvider)
// Experiment flags
expProvider, _ := hoistof.NewProvider(hoist.Config{
APIKey: "hoist_live_exp",
BaseURL: "https://hoist.example.com",
})
of.SetNamedProvider("experiments", expProvider)
// Use specific provider
prodClient := of.NewClient("my-app", of.WithProvider("production"))
expClient := of.NewClient("my-app", of.WithProvider("experiments"))
Provider Lifecycle
// Create provider
provider, err := hoistof.NewProvider(config)
// Register with OpenFeature
of.SetProvider(provider)
// ... use flags ...
// Cleanup on shutdown
provider.Close()
Error Handling
value, err := client.BooleanValue(ctx, "feature", false, evalCtx)
if err != nil {
// Handle specific error types
var resErr of.ResolutionError
if errors.As(err, &resErr) {
log.Printf("Resolution error: %s", resErr.Error())
}
}
Provider Metadata
provider, _ := hoistof.NewProvider(config)
metadata := provider.Metadata()
fmt.Println(metadata.Name) // "Hoist"
Best Practices
1. Single Provider Instance
// Good: Create once, reuse
var provider *hoistof.HoistProvider
func init() {
provider, _ = hoistof.NewProvider(config)
of.SetProvider(provider)
}
2. Use Evaluation Context
// Good: Always provide context
client.BooleanValue(ctx, "feature", false, of.EvaluationContext{
TargetingKey: userID,
})
// Avoid: Empty context
client.BooleanValue(ctx, "feature", false, of.EvaluationContext{})
3. Handle Shutdown
func main() {
provider, _ := hoistof.NewProvider(config)
of.SetProvider(provider)
defer provider.Close()
// ... application code ...
}
Migration from Native SDK
Replace native Hoist calls with OpenFeature:
// Before (native SDK)
import "git.macco.dev/macco/hoist/sdks/go/hoist"
client, _ := hoist.NewClient(config)
enabled, _ := client.Bool(ctx, "feature", false, hoist.Context{
TargetingKey: "user-123",
})
// After (OpenFeature)
import (
hoistof "git.macco.dev/macco/hoist/sdks/go/openfeature"
of "github.com/open-feature/go-sdk/openfeature"
)
provider, _ := hoistof.NewProvider(config)
of.SetProvider(provider)
client := of.NewClient("my-app")
enabled, _ := client.BooleanValue(ctx, "feature", false, of.EvaluationContext{
TargetingKey: "user-123",
})
Next Steps
- Go SDK - Native SDK documentation
- TypeScript SDK - Browser and Node.js
- Core Concepts - Understanding targeting