Gov1.2.1Backend

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

main.go
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

OptionTypeDefaultDescription
APIKeystringrequiredServer API key (rg_server_...)
BaseURLstring"https://api.rollgate.io"API base URL
RefreshIntervaltime.Duration30sPolling interval, 0 to disable
EnableStreamingboolfalseUse SSE for real-time updates (opt-in)
Timeouttime.Duration5sHTTP 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:

Graceful degradation
// 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

middleware.go
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,
	})
}