featureflag

package module
v1.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 21, 2025 License: MIT Imports: 8 Imported by: 0

README

Go Feature Flag Library

A high-performance, flexible feature flag library for Go applications with support for multiple storage backends, caching, and comprehensive observability.

Features

  • Multiple Storage Backends: In-memory, Redis, and PostgreSQL support
  • High Performance Caching: Optional LRU cache with configurable TTL and size limits
  • Graceful Degradation: Non-existent flags return false instead of errors
  • Comprehensive Observability: Structured logging and metrics collection
  • Thread-Safe: Concurrent access support with optimized hot paths
  • Flexible Configuration: Environment variables, JSON/YAML files, or programmatic setup
  • Production Ready: Extensive testing, benchmarks, and error handling

Installation

go get github.com/ajeet-kumar1087/go-feature-flag

Quick Start

Basic Usage (In-Memory)
package main

import (
    "context"
    "log"
    
    "github.com/ajeet-kumar1087/go-feature-flag/featureflag"
)

func main() {
    // Create client with default configuration (in-memory storage)
    client, err := featureflag.NewClientWithDefaults()
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    ctx := context.Background()

    // Create a feature flag
    flag := featureflag.FeatureFlag{
        Key:         "new-checkout-flow",
        Enabled:     true,
        Description: "Enable the new checkout flow",
        Metadata: map[string]string{
            "team": "payments",
            "rollout": "100%",
        },
    }

    if err := client.SetFlag(ctx, flag); err != nil {
        log.Fatal(err)
    }

    // Check if feature is enabled
    if enabled, _ := client.IsEnabled(ctx, "new-checkout-flow"); enabled {
        // Use new checkout flow
        log.Println("Using new checkout flow")
    } else {
        // Use legacy checkout flow
        log.Println("Using legacy checkout flow")
    }
}
Production Configuration (Redis)
config := featureflag.Config{
    Storage: featureflag.StorageConfig{
        Type: "redis",
        Redis: &featureflag.RedisConfig{
            Addr:     "redis-cluster.example.com:6379",
            Password: "your-redis-password",
            DB:       0,
        },
    },
    Cache: featureflag.CacheConfig{
        Enabled: true,
        TTL:     featureflag.Duration(10 * time.Minute),
        MaxSize: 5000,
    },
    Observability: featureflag.ObservabilityConfig{
        Logging: featureflag.LoggingConfig{
            Enabled: true,
            Level:   "info",
        },
        Metrics: featureflag.MetricsConfig{
            Enabled: true,
        },
    },
}

client, err := featureflag.NewClient(config)
if err != nil {
    log.Fatal(err)
}
defer client.Close()

Storage Backends

In-Memory Storage

Perfect for development, testing, and applications that don't need persistence:

config := featureflag.Config{
    Storage: featureflag.StorageConfig{
        Type: "memory",
    },
}
Redis Storage

Ideal for distributed applications and high-performance scenarios:

config := featureflag.Config{
    Storage: featureflag.StorageConfig{
        Type: "redis",
        Redis: &featureflag.RedisConfig{
            Addr:     "localhost:6379",
            Password: "", // Optional
            DB:       0,  // Redis database number
        },
    },
}
PostgreSQL Storage

Best for applications requiring ACID compliance and complex queries:

config := featureflag.Config{
    Storage: featureflag.StorageConfig{
        Type: "postgres",
        Postgres: &featureflag.PostgresConfig{
            Host:     "localhost",
            Port:     5432,
            Database: "featureflags",
            Username: "postgres",
            Password: "password",
            SSLMode:  "require", // Use "disable" for development
        },
    },
}

Configuration Options

Environment Variables

Set configuration using environment variables:

export FEATUREFLAG_STORAGE_TYPE=redis
export FEATUREFLAG_REDIS_ADDR=localhost:6379
export FEATUREFLAG_CACHE_ENABLED=true
export FEATUREFLAG_CACHE_TTL=10m
export FEATUREFLAG_LOGGING_ENABLED=true
export FEATUREFLAG_LOGGING_LEVEL=info
config := featureflag.LoadConfigFromEnv()
client, err := featureflag.NewClient(config)
Configuration Files
JSON Configuration
{
  "storage": {
    "type": "redis",
    "redis": {
      "addr": "localhost:6379",
      "db": 0
    }
  },
  "cache": {
    "enabled": true,
    "ttl": "5m",
    "max_size": 1000
  },
  "observability": {
    "logging": {
      "enabled": true,
      "level": "info"
    },
    "metrics": {
      "enabled": true
    }
  }
}
YAML Configuration
storage:
  type: postgres
  postgres:
    host: localhost
    port: 5432
    database: featureflags
    username: postgres
    ssl_mode: disable
cache:
  enabled: true
  ttl: 15m
  max_size: 2000

Load from file:

config, err := featureflag.LoadConfigFromFile("config.yaml")
if err != nil {
    log.Fatal(err)
}

client, err := featureflag.NewClient(config)

Advanced Features

Caching

The library includes an optional LRU cache that can significantly improve performance:

config := featureflag.Config{
    Cache: featureflag.CacheConfig{
        Enabled: true,
        TTL:     featureflag.Duration(10 * time.Minute), // Cache entries expire after 10 minutes
        MaxSize: 5000,                                   // Maximum 5000 entries in cache
    },
}
Default Flags

Automatically load flags when the client starts:

config := featureflag.Config{
    DefaultFlags: []featureflag.FeatureFlag{
        {
            Key:         "maintenance-mode",
            Enabled:     false,
            Description: "Enable maintenance mode",
        },
        {
            Key:         "new-feature-gate",
            Enabled:     true,
            Description: "Master gate for new features",
        },
    },
}
Observability

Enable logging and metrics for production monitoring:

config := featureflag.Config{
    Observability: featureflag.ObservabilityConfig{
        Logging: featureflag.LoggingConfig{
            Enabled: true,
            Level:   "info", // debug, info, warn, error
        },
        Metrics: featureflag.MetricsConfig{
            Enabled: true,
        },
    },
}

// Get metrics
metrics := client.GetMetrics()
fmt.Printf("Cache hit rate: %.2f%%\n", 
    float64(metrics.CacheHits)/float64(metrics.FlagChecks)*100)

API Reference

Client Interface
type Client interface {
    // Check if a feature flag is enabled (primary method)
    IsEnabled(ctx context.Context, key string) (bool, error)
    
    // Get complete flag information
    GetFlag(ctx context.Context, key string) (*FeatureFlag, error)
    
    // Create or update a flag
    SetFlag(ctx context.Context, flag FeatureFlag) error
    
    // Delete a flag
    DeleteFlag(ctx context.Context, key string) error
    
    // Get all flags
    GetAllFlags(ctx context.Context) ([]FeatureFlag, error)
    
    // Health check
    HealthCheck(ctx context.Context) error
    
    // Get metrics (if enabled)
    GetMetrics() MetricsSnapshot
    
    // Clean shutdown
    Close() error
}
FeatureFlag Structure
type FeatureFlag struct {
    Key         string            `json:"key"`
    Enabled     bool              `json:"enabled"`
    Description string            `json:"description,omitempty"`
    CreatedAt   time.Time         `json:"created_at"`
    UpdatedAt   time.Time         `json:"updated_at"`
    Metadata    map[string]string `json:"metadata,omitempty"`
}

Examples

See the examples/ directory for comprehensive examples:

Performance

The library is optimized for high-performance scenarios:

  • Hot Path Optimization: IsEnabled() is optimized for maximum throughput
  • Concurrent Access: Thread-safe with minimal locking overhead
  • Caching: Optional LRU cache with configurable TTL and size limits
  • Graceful Degradation: Non-existent flags return false without errors

Typical performance (with caching enabled):

  • 1M+ flag checks per second on modern hardware
  • Sub-microsecond latency for cached flags
  • Minimal memory allocation in hot paths

Requirements

  • Go: 1.19 or higher
  • Redis: 6.0 or higher (if using Redis storage)
  • PostgreSQL: 12 or higher (if using PostgreSQL storage)

Database Setup

PostgreSQL

Run the schema creation script:

psql -d your_database -f db/postgres_schema.sql

Or create the table manually:

CREATE TABLE IF NOT EXISTS feature_flags (
    key VARCHAR(255) PRIMARY KEY,
    enabled BOOLEAN NOT NULL DEFAULT false,
    description TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    metadata JSONB
);

CREATE INDEX IF NOT EXISTS idx_feature_flags_enabled ON feature_flags(enabled);
CREATE INDEX IF NOT EXISTS idx_feature_flags_updated_at ON feature_flags(updated_at);

Testing

Run the test suite:

# Unit tests
go test ./featureflag

# Integration tests (requires Redis and PostgreSQL)
go test ./featureflag -tags=integration

# Benchmarks
go test ./featureflag -bench=.

# Coverage
go test ./featureflag -cover

Migration Guide

From HTTP Service Approach

If you're migrating from an HTTP-based feature flag service:

  1. Replace HTTP calls with direct client calls:

    // Old HTTP approach
    resp, err := http.Get("http://feature-service/flags/my-feature")
    
    // New client approach
    enabled, err := client.IsEnabled(ctx, "my-feature")
    
  2. Update configuration:

    • Replace HTTP endpoints with storage configuration
    • Configure caching for better performance than HTTP calls
    • Enable observability for monitoring
  3. Handle errors differently:

    • HTTP errors become storage/validation errors
    • Network timeouts become context cancellation
    • 404 responses become graceful false returns
  4. Performance improvements:

    • Eliminate network latency
    • Add caching for frequently accessed flags
    • Reduce serialization overhead

Best Practices

Flag Naming
  • Use descriptive, lowercase names with hyphens: new-checkout-flow
  • Include context when helpful: mobile-app-dark-mode
  • Avoid abbreviations: authentication-enabled not auth-on
Error Handling
// IsEnabled provides graceful degradation
enabled, err := client.IsEnabled(ctx, "my-feature")
if err != nil {
    // Log error but continue with default behavior
    log.Printf("Flag check failed: %v", err)
    enabled = false // Safe default
}

if enabled {
    // New feature code
} else {
    // Legacy/default code
}
Production Configuration
  • Use persistent storage (Redis or PostgreSQL)
  • Enable caching with appropriate TTL
  • Enable observability (logging and metrics)
  • Set up health checks
  • Use environment variables for sensitive configuration
Performance Optimization
  • Use IsEnabled() for hot paths (optimized for speed)
  • Use GetFlag() only when you need metadata
  • Configure appropriate cache settings for your workload
  • Monitor cache hit rates and adjust TTL accordingly

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Write tests for your changes
  4. Ensure all tests pass (go test ./...)
  5. Run linting (golangci-lint run)
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package featureflag provides a comprehensive feature flag library for Go applications. This package offers a simple, single-import interface to all feature flag functionality including multiple storage backends, caching, observability, and flexible configuration.

Basic usage:

import "github.com/ajeet-kumar1087/go-feature-flag"

// Create a client with default settings (memory storage, caching enabled)
client, err := featureflag.NewClient(featureflag.DefaultConfig())
if err != nil {
	log.Fatal(err)
}
defer client.Close()

// Create and set a feature flag
flag := featureflag.FeatureFlag{
	Key:         "new-feature",
	Enabled:     true,
	Description: "Enable new feature",
}
client.SetFlag(ctx, flag)

// Check if feature is enabled
if enabled, _ := client.IsEnabled(ctx, "new-feature"); enabled {
	// Use new feature
}

Advanced usage with custom configuration:

config := featureflag.Config{
	Storage: featureflag.StorageConfig{
		Type: "redis",
		Redis: &featureflag.RedisConfig{
			Addr: "localhost:6379",
		},
	},
	Cache: featureflag.CacheConfig{
		Enabled: true,
		TTL:     featureflag.Duration(10 * time.Minute),
		MaxSize: 1000,
	},
}
client, err := featureflag.NewClient(config)

Index

Constants

View Source
const (
	ErrorTypeNotFound   = core.ErrorTypeNotFound
	ErrorTypeValidation = core.ErrorTypeValidation
	ErrorTypeStorage    = core.ErrorTypeStorage
	ErrorTypeCache      = core.ErrorTypeCache
	ErrorTypeConnection = core.ErrorTypeConnection
	ErrorTypeTimeout    = core.ErrorTypeTimeout
	ErrorTypeAuth       = core.ErrorTypeAuth
	ErrorTypeRateLimit  = core.ErrorTypeRateLimit
	ErrorTypeClient     = core.ErrorTypeClient
	ErrorTypeInternal   = core.ErrorTypeInternal
)

Error type constants

Variables

View Source
var (
	// ErrFlagNotFound indicates a feature flag was not found
	ErrFlagNotFound = core.ErrFlagNotFound

	// ErrInvalidFlag indicates an invalid feature flag
	ErrInvalidFlag = core.ErrInvalidFlag

	// ErrStorageFailure indicates a storage operation failed
	ErrStorageFailure = core.ErrStorageFailure

	// ErrInvalidConfig indicates invalid configuration
	ErrInvalidConfig = core.ErrInvalidConfig

	// ErrClientClosed indicates the client is closed
	ErrClientClosed = core.ErrClientClosed

	// ErrConnectionFailure indicates a connection failure
	ErrConnectionFailure = core.ErrConnectionFailure

	// ErrTimeout indicates an operation timeout
	ErrTimeout = core.ErrTimeout
)

Common error variables

Functions

func IsNotFoundError

func IsNotFoundError(err error) bool

IsNotFoundError checks if an error is a "not found" error.

func IsRetryableError

func IsRetryableError(err error) bool

IsRetryableError checks if an error indicates a retryable operation.

func NewCache

func NewCache(maxSize int, ttl time.Duration) *cache.Cache

NewCache creates a new cache instance with the specified configuration. Used internally by the cached store wrapper.

Types

type CacheConfig

type CacheConfig = config.CacheConfig

CacheConfig defines caching configuration

type Client

type Client = core.Client

Client is the main interface for feature flag operations

func NewClient

func NewClient(cfg Config) (Client, error)

NewClient creates a new feature flag client with the given configuration. This is the main entry point for using the feature flag library.

func NewClientWithDefaults

func NewClientWithDefaults() (Client, error)

NewClientWithDefaults creates a new feature flag client with default configuration. Uses in-memory storage with caching enabled - suitable for development and testing.

type Config

type Config = config.Config

Config holds all configuration options for the feature flag library

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a configuration with sensible defaults. Uses in-memory storage, caching enabled, and observability disabled.

type Duration

type Duration = config.Duration

Duration is a wrapper around time.Duration that supports JSON/YAML marshaling

type ErrorType

type ErrorType = core.ErrorType

ErrorType represents different categories of errors

func ClassifyError

func ClassifyError(err error) ErrorType

ClassifyError determines the error type based on the underlying error.

type FeatureFlag

type FeatureFlag = core.FeatureFlag

FeatureFlag represents a feature flag with metadata and validation

type FeatureFlagError

type FeatureFlagError = core.FeatureFlagError

FeatureFlagError provides detailed error information

type MetricsSnapshot

type MetricsSnapshot = core.MetricsSnapshot

MetricsSnapshot provides a point-in-time view of metrics

type ObservabilityConfig

type ObservabilityConfig = config.ObservabilityConfig

ObservabilityConfig defines logging and metrics configuration

type PostgresConfig

type PostgresConfig = config.PostgresConfig

PostgresConfig defines PostgreSQL-specific configuration

type RedisConfig

type RedisConfig = config.RedisConfig

RedisConfig defines Redis-specific configuration

type StorageConfig

type StorageConfig = config.StorageConfig

StorageConfig defines storage backend configuration

type Store

type Store = core.Store

Store defines the interface for feature flag storage backends

func NewCachedStore

func NewCachedStore(store Store, cacheConfig CacheConfig) Store

NewCachedStore creates a cached store wrapper around any store implementation. Improves performance by caching frequently accessed feature flags.

func NewMemoryStore

func NewMemoryStore() Store

NewMemoryStore creates a new memory-based feature flag store. Suitable for development, testing, and single-instance deployments.

func NewPostgresStore

func NewPostgresStore(cfg *PostgresConfig) (Store, error)

NewPostgresStore creates a new PostgreSQL-based feature flag store. Suitable for production deployments requiring ACID compliance and complex queries.

func NewRedisStore

func NewRedisStore(cfg *RedisConfig) (Store, error)

NewRedisStore creates a new Redis-based feature flag store. Suitable for production deployments requiring persistence and scalability.

Directories

Path Synopsis
featureflag

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL