Go SDK
Backend feature flags in Go with built-in circuit breaker, caching, SSE streaming, and zero external dependencies.
Circuit Breaker
Auto-recovery
Zero Dependencies
Stdlib only
Caching
Persistent cache
SSE Support
Real-time updates
Installation
go get github.com/rollgate/[email protected]Quick Start
package main
import (
"context"
"log"
"os"
"time"
rollgate "github.com/rollgate/sdk-go"
)
func main() {
client, err := rollgate.NewClient(rollgate.Config{
APIKey: os.Getenv("ROLLGATE_API_KEY"),
BaseURL: "https://api.rollgate.io",
RefreshInterval: 30 * time.Second,
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
if err := client.Init(ctx); err != nil {
log.Printf("Rollgate init failed: %v", err)
}
// Check flags
enabled := client.IsEnabled("my-feature", false)
if enabled {
// New feature code
}
// Identify user
client.Identify(ctx, rollgate.UserContext{
ID: "user-123",
Email: "[email protected]",
Attributes: map[string]interface{}{
"plan": "pro",
},
})
}Configuration
| Option | Type | Default | Description |
|---|---|---|---|
| APIKey | string | required | Server API key (rg_server_...) |
| BaseURL | string | "https://api.rollgate.io" | API base URL |
| RefreshInterval | time.Duration | 30s | Polling interval, 0 to disable |
| EnableStreaming | bool | false | Use SSE for real-time updates (opt-in) |
| Timeout | time.Duration | 5s | HTTP request timeout |
Methods
Init(ctx)
Initialize the client by fetching flags from the server. Should be called once at startup. Starts background polling or SSE streaming if configured.
ctx := context.Background()
if err := client.Init(ctx); err != nil {
log.Printf("Rollgate init failed: %v", err)
// SDK will use default values until flags are fetched
}Close()
Gracefully shut down the client, stopping background polling and SSE connections. Use defer client.Close() for cleanup.
defer client.Close()IsEnabled(key, defaultValue)
Check if a boolean flag is enabled. Returns the flag value or the default.
enabled := client.IsEnabled("new-feature", false)
if enabled {
// New feature code
}GetValue(key, defaultValue)
Get a flag value as interface{}. Use for flags of any type.
value := client.GetValue("feature-config", nil)GetString(key, defaultValue)
Type-safe helper for string flags.
buttonColor := client.GetString("cta-color", "blue")
variant := client.GetString("checkout-variant", "control")GetNumber(key, defaultValue)
Type-safe helper for numeric flags. Returns a float64.
pageSize := client.GetNumber("results-per-page", 20)
timeout := client.GetNumber("api-timeout-ms", 5000)GetJSON(key, defaultValue)
Get a flag value as a map[string]interface{}. Use for structured configuration flags.
config := client.GetJSON("rate-limit-config", map[string]interface{}{
"enabled": false,
"limit": 100,
})
if enabled, ok := config["enabled"].(bool); ok && enabled {
limit := config["limit"].(float64)
// Apply rate limiting
}GetAllFlags()
Get all flags as a map[string]interface{}.
flags := client.GetAllFlags()
// map["feature-a":true "feature-b":false]Identify(ctx, user)
Set user context for targeting rules. Re-fetches flags with the new context.
client.Identify(ctx, rollgate.UserContext{
ID: "user-123",
Email: "[email protected]",
Attributes: map[string]interface{}{
"plan": "enterprise", // For "plan in pro,enterprise" rules
"country": "US", // For "country equals US" rules
"company": "Acme Inc", // For "company contains Acme" rules
"app_version": "2.1.0", // For "app_version semver_gt 2.0.0" rules
},
})Refresh(ctx)
Force refresh flags from the server.
if err := client.Refresh(ctx); err != nil {
log.Printf("Failed to refresh flags: %v", err)
}Circuit Breaker
The SDK includes a circuit breaker that protects your application when Rollgate is unavailable. When open, it uses cached flag values to ensure your application continues to function.
// Get current state
state := client.GetCircuitState()
// "closed" | "open" | "half-open"
// Listen to state changes
client.OnCircuitOpen(func() {
log.Println("Circuit breaker opened - using cached flags")
})
client.OnCircuitClosed(func() {
log.Println("Circuit breaker closed - normal operation")
})
// Force reset (use with caution)
client.ResetCircuit()Error Handling & Graceful Degradation
The SDK is designed to never panic during flag evaluation. If Rollgate is unavailable, flags return their default values. Use these patterns for production-grade reliability:
// Default values ensure your app works even without Rollgate
showNewFeature := client.IsEnabled("new-feature", false)
// Returns false if: flag doesn't exist, API down, circuit open
// Health check endpoint
func healthHandler(w http.ResponseWriter, r *http.Request) {
circuit := client.GetCircuitState()
isHealthy := circuit == "closed"
status := http.StatusOK
if !isHealthy {
status = http.StatusServiceUnavailable
}
w.WriteHeader(status)
json.NewEncoder(w).Encode(map[string]interface{}{
"status": map[bool]string{true: "healthy", false: "degraded"}[isHealthy],
"circuit": circuit,
})
}Initialization Error Handling
func startServer() {
ctx := context.Background()
if err := client.Init(ctx); err != nil {
// Log but don't crash - SDK will use defaults
log.Printf("Rollgate init failed: %v", err)
log.Println("Continuing with default flag values")
}
flags := client.GetAllFlags()
log.Printf("Rollgate initialized with %d flags", len(flags))
// Server starts regardless of Rollgate status
log.Fatal(http.ListenAndServe(":3000", router))
}Feature-Critical Flags
For flags that control critical features, add explicit fallback logic:
func shouldEnablePayments() bool {
// Critical feature: payments
flagValue := client.IsEnabled("payments-enabled", true) // Default: enabled
circuitOpen := client.GetCircuitState() == "open"
if circuitOpen {
// Circuit is open, we're using cached values
// For payments, always enable as fail-safe
log.Println("Circuit open, enabling payments as fail-safe")
return true
}
return flagValue
}HTTP Middleware Example
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"os"
"time"
rollgate "github.com/rollgate/sdk-go"
)
var client *rollgate.Client
func main() {
var err error
client, err = rollgate.NewClient(rollgate.Config{
APIKey: os.Getenv("ROLLGATE_API_KEY"),
BaseURL: "https://api.rollgate.io",
RefreshInterval: 30 * time.Second,
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
if err := client.Init(ctx); err != nil {
log.Printf("Rollgate init failed: %v", err)
}
mux := http.NewServeMux()
// Apply feature flag middleware
mux.HandleFunc("/api/checkout", withFlags(checkoutHandler))
mux.HandleFunc("/health", healthHandler)
log.Fatal(http.ListenAndServe(":3000", mux))
}
// Middleware to identify user and inject flags
func withFlags(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Identify user from request context
userID := r.Header.Get("X-User-ID")
if userID != "" {
client.Identify(r.Context(), rollgate.UserContext{
ID: userID,
Email: r.Header.Get("X-User-Email"),
})
}
next(w, r)
}
}
func checkoutHandler(w http.ResponseWriter, r *http.Request) {
if client.IsEnabled("new-checkout", false) {
// New checkout logic
json.NewEncoder(w).Encode(map[string]string{"checkout": "v2"})
return
}
// Legacy checkout logic
json.NewEncoder(w).Encode(map[string]string{"checkout": "v1"})
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
circuit := client.GetCircuitState()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "healthy",
"circuit": circuit,
})
}