No results
3
Go SDK
Insidious Fiddler edited this page 2026-02-06 21:04:12 +00:00
Table of Contents
- Go SDK
- Installation
- Quick Start
- Configuration
- API Key Types
- Evaluation Methods
- Evaluation Context
- Evaluation Results
- Hooks
- Client Methods
- Thread Safety
- Error Handling
- Custom Logger
- Best Practices
- 1. Initialize Once
- 2. Always Provide Targeting Key
- 3. Handle Errors Gracefully
- 4. Use Meaningful Defaults
- Next Steps
Go SDK
The native Go SDK for Hoist with real-time streaming support.
Installation
go get git.macco.dev/macco/hoist/sdks/go/hoist
Quick Start
package main
import (
"context"
"log"
"git.macco.dev/macco/hoist/sdks/go/hoist"
)
func main() {
client, err := hoist.NewClient(hoist.Config{
APIKey: "hoist_live_xxx",
BaseURL: "https://hoist.example.com",
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
enabled, err := client.Bool(ctx, "new-feature", false, hoist.Context{
TargetingKey: "user-123",
})
if err != nil {
log.Printf("evaluation error: %v", err)
}
if enabled {
// New feature code path
}
}
Configuration
client, err := hoist.NewClient(hoist.Config{
// Required
APIKey: "hoist_live_xxx",
BaseURL: "https://hoist.example.com",
// Optional
PollInterval: 30 * time.Second, // Polling interval (if streaming disabled)
HTTPClient: &http.Client{}, // Custom HTTP client
Logger: myLogger, // Custom logger
// Callbacks
OnReady: func() {
log.Println("Hoist client ready")
},
OnError: func(err error) {
log.Printf("Hoist error: %v", err)
},
OnFlagsUpdated: func(keys []string) {
log.Printf("Flags updated: %v", keys)
},
})
API Key Types
Server Keys (hoist_sk_*)
Server keys provide full functionality:
- Fetches complete flag configuration on startup
- Evaluates all flags locally (fast, no network latency)
- Receives real-time updates via WebSocket streaming
- Thread-safe for concurrent use
client, _ := hoist.NewClient(hoist.Config{
APIKey: "hoist_sk_live_xxx", // Server key
})
Client Keys (hoist_pk_*)
Client keys are for untrusted environments:
- Evaluates flags via API calls (protects targeting rules)
- No local configuration storage
- Suitable for mobile apps or browser-exposed code
client, _ := hoist.NewClient(hoist.Config{
APIKey: "hoist_pk_xxx", // Client key
})
Evaluation Methods
Boolean Flags
// Simple evaluation
enabled, err := client.Bool(ctx, "dark-mode", false, hoist.Context{
TargetingKey: "user-123",
})
// With detailed result
result := client.BoolDetail(ctx, "dark-mode", false, hoist.Context{
TargetingKey: "user-123",
})
fmt.Printf("Value: %v, Reason: %s, Variant: %s\n",
result.Value, result.Reason, result.VariantKey)
String Flags
version, err := client.String(ctx, "api-version", "v1", hoist.Context{
TargetingKey: "user-123",
})
// With details
result := client.StringDetail(ctx, "api-version", "v1", hoist.Context{
TargetingKey: "user-123",
})
Integer Flags
limit, err := client.Int(ctx, "rate-limit", 100, hoist.Context{
TargetingKey: "user-123",
})
// With details
result := client.IntDetail(ctx, "rate-limit", 100, hoist.Context{
TargetingKey: "user-123",
})
Float Flags
threshold, err := client.Float(ctx, "score-threshold", 0.5, hoist.Context{
TargetingKey: "user-123",
})
// With details
result := client.FloatDetail(ctx, "score-threshold", 0.5, hoist.Context{
TargetingKey: "user-123",
})
JSON Flags
type FeatureConfig struct {
MaxItems int `json:"maxItems"`
Enabled bool `json:"enabled"`
Allowlist []string `json:"allowlist"`
}
var config FeatureConfig
err := client.JSON(ctx, "feature-config", FeatureConfig{MaxItems: 10}, &config, hoist.Context{
TargetingKey: "user-123",
})
// With details
result := client.JSONDetail(ctx, "feature-config", FeatureConfig{MaxItems: 10}, hoist.Context{
TargetingKey: "user-123",
})
Evaluation Context
Provide user context for targeting:
evalCtx := hoist.Context{
// Required for consistent targeting and percentage rollouts
TargetingKey: "user-123",
// Optional attributes for targeting rules
Attributes: map[string]any{
"email": "user@example.com",
"plan": "enterprise",
"country": "US",
"beta_tester": true,
"account_age": 365,
"teams": []string{"engineering", "platform"},
},
}
enabled, err := client.Bool(ctx, "feature", false, evalCtx)
Evaluation Results
Detailed results include:
result := client.BoolDetail(ctx, "feature", false, evalCtx)
result.Value // The evaluated value
result.VariantKey // The variant key (if multivariate)
result.Reason // Why this value was returned
result.RuleID // Which rule matched
result.SegmentKey // Which segment matched (if any)
result.Version // Flag configuration version
result.Error // Evaluation error (if any)
Evaluation Reasons
| Reason | Description |
|---|---|
ReasonDefault |
No rules matched, using default |
ReasonTargetingMatch |
Matched a targeting rule |
ReasonSegmentMatch |
Matched via segment |
ReasonRollout |
Percentage rollout |
ReasonIdentityOverride |
User-specific override |
ReasonDisabled |
Flag is disabled |
ReasonNotFound |
Flag doesn't exist |
ReasonError |
Evaluation error |
Hooks
Add hooks for observability:
client.AddHook(hoist.Hook{
Before: func(ctx context.Context, key string, evalCtx hoist.Context) {
log.Printf("Evaluating flag: %s for user: %s", key, evalCtx.TargetingKey)
},
After: func(ctx context.Context, key string, result hoist.EvaluationResult) {
// Log to your metrics system
metrics.RecordFlagEvaluation(key, result.Reason, result.Value)
},
Error: func(ctx context.Context, key string, err error) {
log.Printf("Flag evaluation error: %s: %v", key, err)
},
})
Client Methods
Ready Check
if client.Ready() {
// Client has loaded configuration
}
Force Refresh
err := client.Refresh(ctx)
List All Flags
keys := client.AllFlags()
for _, key := range keys {
fmt.Println(key)
}
Close Client
client.Close() // Stops streaming/polling, releases resources
Thread Safety
The Hoist client is fully thread-safe:
var client *hoist.Client
func init() {
var err error
client, err = hoist.NewClient(hoist.Config{
APIKey: "hoist_live_xxx",
BaseURL: "https://hoist.example.com",
})
if err != nil {
log.Fatal(err)
}
}
func handler(w http.ResponseWriter, r *http.Request) {
// Safe to call from multiple goroutines
enabled, _ := client.Bool(r.Context(), "feature", false, hoist.Context{
TargetingKey: getUserID(r),
})
// ...
}
Error Handling
enabled, err := client.Bool(ctx, "feature", false, evalCtx)
if err != nil {
// Log the error but use the default value
log.Printf("flag evaluation error: %v", err)
// enabled is already set to the default (false)
}
// Use the value (default if error occurred)
if enabled {
// ...
}
Custom Logger
Implement the Logger interface:
type Logger interface {
Debug(msg string, args ...any)
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, args ...any)
}
// Example with zerolog
type ZerologAdapter struct {
log zerolog.Logger
}
func (l ZerologAdapter) Debug(msg string, args ...any) {
l.log.Debug().Fields(toMap(args)).Msg(msg)
}
// ... implement other methods
client, _ := hoist.NewClient(hoist.Config{
Logger: ZerologAdapter{log: zerolog.New(os.Stdout)},
})
Best Practices
1. Initialize Once
Create one client and reuse it:
// Good: Single client instance
var flagClient *hoist.Client
func main() {
flagClient, _ = hoist.NewClient(config)
defer flagClient.Close()
// ... use flagClient throughout your app
}
// Bad: Creating clients per request
func handler(w http.ResponseWriter, r *http.Request) {
client, _ := hoist.NewClient(config) // Don't do this!
defer client.Close()
}
2. Always Provide Targeting Key
// Good: Consistent targeting
client.Bool(ctx, "feature", false, hoist.Context{
TargetingKey: userID,
})
// Bad: No targeting key (percentage rollouts won't be sticky)
client.Bool(ctx, "feature", false, hoist.Context{})
3. Handle Errors Gracefully
enabled, err := client.Bool(ctx, "feature", false, evalCtx)
if err != nil {
// Log but continue with default
log.Printf("flag error: %v", err)
}
// Always use the returned value (it's the default on error)
4. Use Meaningful Defaults
// Good: Safe default that doesn't break existing behavior
enabled, _ := client.Bool(ctx, "new-experimental-feature", false, evalCtx)
// Be careful: Default true means all users get the feature on error
enabled, _ := client.Bool(ctx, "critical-feature", true, evalCtx)
Next Steps
- OpenFeature Provider - Use with OpenFeature SDK
- TypeScript SDK - Browser and Node.js
- Core Concepts - Understanding targeting and segments