feature

package
v0.0.0-...-6067653 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2025 License: Apache-2.0 Imports: 6 Imported by: 0

README

Feature

Feature flag management system for controlled feature rollouts and A/B testing.

Features

  • Flexible Strategies - User targeting, percentage rollouts, environment-based activation
  • Provider Architecture - Pluggable backend storage with in-memory implementation
  • Thread-Safe Operations - Concurrent access with read-write locks
  • Consistent Rollouts - Hash-based percentage distribution ensures stable user experience

Installation

import "github.com/dmitrymomot/saaskit/pkg/feature"

Usage

package main

import (
    "context"
    "log"

    "github.com/dmitrymomot/saaskit/pkg/feature"
)

func main() {
    // Create provider with initial flags
    provider, err := feature.NewMemoryProvider(
        &feature.Flag{
            Name:        "new-ui",
            Description: "Enable redesigned user interface",
            Enabled:     true,
            Strategy:    feature.NewAlwaysOnStrategy(),
        },
    )
    if err != nil {
        log.Fatal(err)
    }
    defer provider.Close()

    // Check if feature is enabled
    ctx := context.Background()
    enabled, err := provider.IsEnabled(ctx, "new-ui")
    if err != nil {
        log.Fatal(err)
    }

    if enabled {
        // Show new UI
    }
}

Common Operations

Percentage-Based Rollout
// Roll out to 25% of users
percentage := 25
flag := &feature.Flag{
    Name:    "experimental-feature",
    Enabled: true,
    Strategy: feature.NewTargetedStrategy(
        feature.TargetCriteria{
            Percentage: &percentage,
        },
        feature.WithUserIDExtractor(func(ctx context.Context) string {
            // Extract user ID from your context
            return ctx.Value("userID").(string)
        }),
    ),
}
User and Group Targeting
// Target specific users and groups
flag := &feature.Flag{
    Name:    "beta-feature",
    Enabled: true,
    Strategy: feature.NewTargetedStrategy(
        feature.TargetCriteria{
            UserIDs:  []string{"user123", "user456"},
            Groups:   []string{"beta-testers", "employees"},
            DenyList: []string{"user789"}, // Always exclude these users
        },
        feature.WithUserIDExtractor(getUserID),
        feature.WithUserGroupsExtractor(getUserGroups),
    ),
}
Environment-Based Features
// Enable in specific environments
flag := &feature.Flag{
    Name:    "debug-mode",
    Enabled: true,
    Strategy: feature.NewEnvironmentStrategy(
        []string{"development", "staging"},
        feature.WithEnvironmentExtractor(func(ctx context.Context) string {
            return os.Getenv("APP_ENV")
        }),
    ),
}
Composite Strategies
// Combine multiple strategies
flag := &feature.Flag{
    Name:    "complex-feature",
    Enabled: true,
    Strategy: feature.NewAndStrategy(
        feature.NewEnvironmentStrategy([]string{"production"}, envExtractor),
        feature.NewTargetedStrategy(
            feature.TargetCriteria{Percentage: &fifty},
            feature.WithUserIDExtractor(getUserID),
        ),
    ),
}

Error Handling

// Package errors:
var (
    ErrFlagNotFound           = errors.New("feature flag not found")
    ErrInvalidFlag            = errors.New("invalid feature flag parameters")
    ErrProviderNotInitialized = errors.New("feature provider not initialized")
    ErrInvalidContext         = errors.New("invalid context for feature evaluation")
    ErrInvalidStrategy        = errors.New("invalid feature rollout strategy")
    ErrOperationFailed        = errors.New("feature operation failed")
)

// Usage:
flag, err := provider.GetFlag(ctx, "unknown")
if errors.Is(err, feature.ErrFlagNotFound) {
    // Flag doesn't exist
}

API Documentation

# Full API documentation
go doc github.com/dmitrymomot/saaskit/pkg/feature

# Specific function or type
go doc github.com/dmitrymomot/saaskit/pkg/feature.Provider

Notes

  • Percentage rollouts use FNV-1a hashing for consistent user bucketing
  • DenyList has highest precedence in TargetedStrategy evaluation hierarchy
  • MemoryProvider creates deep copies to prevent external flag modification

Documentation

Overview

Package feature provides a comprehensive feature flag management system for Go applications.

The feature package enables controlled feature rollouts through flexible strategies including user targeting, percentage-based rollouts, environment-based activation, and composite rules. It follows a provider-based architecture allowing for different backend implementations while maintaining a consistent API.

Architecture

The package is built around three core concepts:

1. Flags - Configuration units that define features and their rollout rules 2. Strategies - Evaluation logic that determines feature availability 3. Providers - Backend storage and retrieval implementations

Feature evaluation happens in two stages: first checking if a flag is globally enabled, then evaluating its strategy against the provided context. This allows for both simple on/off toggles and sophisticated rollout rules.

The Provider interface is organized into three logical method groups:

  • Evaluation methods: IsEnabled, GetFlag
  • Management methods: ListFlags, CreateFlag, UpdateFlag, DeleteFlag
  • Lifecycle methods: Close

Usage

Basic feature flag setup:

import "github.com/dmitrymomot/saaskit/pkg/feature"

// Create a provider with initial flags
provider, err := feature.NewMemoryProvider(
	&feature.Flag{
		Name:        "new-ui",
		Description: "Enable redesigned user interface",
		Enabled:     true,
		Strategy:    feature.NewAlwaysOnStrategy(),
	},
)
if err != nil {
	log.Fatal(err)
}
defer provider.Close()

// Check if feature is enabled
enabled, err := provider.IsEnabled(ctx, "new-ui")
if err != nil {
	// Handle error
}
if enabled {
	// Show new UI
}

Strategies

The package provides several built-in strategies:

AlwaysStrategy - Returns a constant value (on/off) TargetedStrategy - Enables features for specific users, groups, or percentages EnvironmentStrategy - Activates features in specific environments CompositeStrategy - Combines multiple strategies with AND/OR logic

Example of percentage-based rollout:

percentage := 25
flag := &feature.Flag{
	Name:    "experimental-feature",
	Enabled: true,
	Strategy: feature.NewTargetedStrategy(
		feature.TargetCriteria{
			Percentage: &percentage,
		},
		feature.WithUserIDExtractor(getUserID),
	),
}

Context Extractors

The package uses extractor functions to retrieve evaluation data from context, maintaining decoupling between the feature system and application logic:

func getUserID(ctx context.Context) string {
	if user, ok := ctx.Value("user").(*User); ok {
		return user.ID
	}
	return ""
}

strategy := feature.NewTargetedStrategy(
	criteria,
	feature.WithUserIDExtractor(getUserID),
)

Error Handling

The package defines specific errors for different failure scenarios:

flag, err := provider.GetFlag(ctx, "unknown")
if errors.Is(err, feature.ErrFlagNotFound) {
	// Flag doesn't exist
}

All errors follow consistent naming patterns and can be checked using errors.Is.

Performance Considerations

The MemoryProvider uses read-write locks for thread-safe concurrent access. For high-throughput applications, consider caching IsEnabled results at the application level to reduce lock contention.

Percentage-based rollouts use consistent hashing (FNV-1a) to ensure users always receive the same feature state across evaluations.

The package includes comprehensive benchmarks for performance monitoring:

  • Provider operations (IsEnabled, ListFlags) under various scenarios
  • Strategy evaluation performance for all strategy types
  • Concurrent access patterns

Memory optimizations include pre-allocated slice capacity in ListFlags when filtering by tags, reducing allocations during filtering operations.

Run benchmarks with: go test -bench=. ./pkg/feature/...

Examples

See the package examples and README.md for detailed usage patterns including composite strategies, environment-based features, and user targeting.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrFlagNotFound           = errors.New("feature flag not found")
	ErrInvalidFlag            = errors.New("invalid feature flag parameters")
	ErrProviderNotInitialized = errors.New("feature provider not initialized")
	ErrInvalidContext         = errors.New("invalid context for feature evaluation")
	ErrInvalidStrategy        = errors.New("invalid feature rollout strategy")
	ErrOperationFailed        = errors.New("feature operation failed")
)

Functions

This section is empty.

Types

type AlwaysStrategy

type AlwaysStrategy struct {
	Value bool
}

func (*AlwaysStrategy) Evaluate

func (s *AlwaysStrategy) Evaluate(ctx context.Context) (bool, error)

type CompositeStrategy

type CompositeStrategy struct {
	Strategies []Strategy
	Operator   string // "and" or "or"
}

func (*CompositeStrategy) Evaluate

func (s *CompositeStrategy) Evaluate(ctx context.Context) (bool, error)

Evaluate combines multiple strategies with short-circuit evaluation. "and": returns false on first false result, "or": returns true on first true result.

type EnvironmentExtractor

type EnvironmentExtractor func(ctx context.Context) string

Extractor function types for retrieving data from context. These allow users to define how to extract feature flag evaluation data from their application's context, maintaining decoupling from the feature package.

type EnvironmentStrategy

type EnvironmentStrategy struct {
	EnabledEnvironments []string
	// contains filtered or unexported fields
}

func (*EnvironmentStrategy) Evaluate

func (s *EnvironmentStrategy) Evaluate(ctx context.Context) (bool, error)

type EnvironmentStrategyOption

type EnvironmentStrategyOption func(*EnvironmentStrategy)

func WithEnvironmentExtractor

func WithEnvironmentExtractor(extractor EnvironmentExtractor) EnvironmentStrategyOption

type Flag

type Flag struct {
	Name        string    `json:"name"`
	Description string    `json:"description,omitempty"`
	Enabled     bool      `json:"enabled"`
	Strategy    Strategy  `json:"strategy,omitempty"`
	Tags        []string  `json:"tags,omitempty"`
	CreatedAt   time.Time `json:"created_at,omitzero"`
	UpdatedAt   time.Time `json:"updated_at,omitzero"`
}

Flag represents a feature flag with its configuration.

type MemoryProvider

type MemoryProvider struct {
	// contains filtered or unexported fields
}

MemoryProvider is an in-memory implementation of the Provider interface. All operations create deep copies to prevent external modification of stored flags.

func NewMemoryProvider

func NewMemoryProvider(initialFlags ...*Flag) (*MemoryProvider, error)

func (*MemoryProvider) Close

func (m *MemoryProvider) Close() error

func (*MemoryProvider) CreateFlag

func (m *MemoryProvider) CreateFlag(ctx context.Context, flag *Flag) error

func (*MemoryProvider) DeleteFlag

func (m *MemoryProvider) DeleteFlag(ctx context.Context, flagName string) error

func (*MemoryProvider) GetFlag

func (m *MemoryProvider) GetFlag(ctx context.Context, flagName string) (*Flag, error)

func (*MemoryProvider) IsEnabled

func (m *MemoryProvider) IsEnabled(ctx context.Context, flagName string) (bool, error)

func (*MemoryProvider) ListFlags

func (m *MemoryProvider) ListFlags(ctx context.Context, tags ...string) ([]*Flag, error)

func (*MemoryProvider) UpdateFlag

func (m *MemoryProvider) UpdateFlag(ctx context.Context, flag *Flag) error

type Provider

type Provider interface {
	// Evaluation methods
	IsEnabled(ctx context.Context, flagName string) (bool, error)
	GetFlag(ctx context.Context, flagName string) (*Flag, error)

	// Management methods
	ListFlags(ctx context.Context, tags ...string) ([]*Flag, error)
	CreateFlag(ctx context.Context, flag *Flag) error
	UpdateFlag(ctx context.Context, flag *Flag) error
	DeleteFlag(ctx context.Context, flagName string) error

	// Lifecycle methods
	Close() error
}

Provider is the interface that all feature flag providers must implement.

type Strategy

type Strategy interface {
	// Evaluate determines if the feature should be enabled for a specific context.
	// Context should contain data required by the strategy (user ID, groups, etc.).
	Evaluate(ctx context.Context) (bool, error)
}

Strategy defines different ways to roll out a feature.

func NewAlwaysOffStrategy

func NewAlwaysOffStrategy() Strategy

func NewAlwaysOnStrategy

func NewAlwaysOnStrategy() Strategy

func NewAndStrategy

func NewAndStrategy(strategies ...Strategy) Strategy

func NewEnvironmentStrategy

func NewEnvironmentStrategy(environments []string, opts ...EnvironmentStrategyOption) Strategy

func NewOrStrategy

func NewOrStrategy(strategies ...Strategy) Strategy

func NewTargetedStrategy

func NewTargetedStrategy(criteria TargetCriteria, opts ...TargetedStrategyOption) Strategy

type TargetCriteria

type TargetCriteria struct {
	UserIDs    []string `json:"user_ids,omitempty"`
	Groups     []string `json:"groups,omitempty"`
	Percentage *int     `json:"percentage,omitempty"`
	// AllowList overrides all other criteria except DenyList
	AllowList []string `json:"allow_list,omitempty"`
	// DenyList overrides all other criteria (highest precedence)
	DenyList []string `json:"deny_list,omitempty"`
}

TargetCriteria defines targeting criteria for a flag.

type TargetedStrategy

type TargetedStrategy struct {
	Criteria TargetCriteria
	// contains filtered or unexported fields
}

func (*TargetedStrategy) Evaluate

func (s *TargetedStrategy) Evaluate(ctx context.Context) (bool, error)

Evaluate determines feature enablement using a strict precedence hierarchy: 1. DenyList (highest) - blocks access regardless of other criteria 2. AllowList - grants access overriding user/group/percentage rules 3. UserIDs - direct user targeting 4. Groups - group membership targeting 5. Percentage - consistent hash-based rollout (lowest precedence)

type TargetedStrategyOption

type TargetedStrategyOption func(*TargetedStrategy)

func WithUserGroupsExtractor

func WithUserGroupsExtractor(extractor UserGroupsExtractor) TargetedStrategyOption

func WithUserIDExtractor

func WithUserIDExtractor(extractor UserIDExtractor) TargetedStrategyOption

type UserGroupsExtractor

type UserGroupsExtractor func(ctx context.Context) []string

Extractor function types for retrieving data from context. These allow users to define how to extract feature flag evaluation data from their application's context, maintaining decoupling from the feature package.

type UserIDExtractor

type UserIDExtractor func(ctx context.Context) string

Extractor function types for retrieving data from context. These allow users to define how to extract feature flag evaluation data from their application's context, maintaining decoupling from the feature package.

Jump to

Keyboard shortcuts

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