62 lines
feature/service.go
Feature flag storage and per-user evaluation using a deterministic bucket hash.
// Package feature manages feature flags and percentage-based user rollouts.package featureimport ( "hash/fnv" "sync")
// Flag configures a named feature toggle.// Percentage controls what fraction of users see the feature: 0 disables it for all,// 100 enables it for all, and values in between use a deterministic per-user bucket.type Flag struct { Name string Percentage int // 0–100 Enabled bool // true overrides Percentage and enables for all users}
// Service stores and evaluates feature flags.type Service struct {mu sync.RWMutex
flags map[string]Flag}
// NewService returns a Service with no configured flags.func NewService() *Service { return &Service{flags: make(map[string]Flag)}}
// Set adds or replaces a flag configuration.func (s *Service) Set(name string, f Flag) {s.mu.Lock()
defer s.mu.Unlock()f.Name = name
s.flags[name] = f
}
// Evaluate reports whether flagName is active for userID.// Returns false if the flag is not configured.// If Enabled is true, returns true for all users.// Otherwise, a deterministic bucket is computed as fnv64(userID + ":" + flagName) mod 100;// the flag is active when bucket < Percentage.func (s *Service) Evaluate(flagName, userID string) bool {s.mu.RLock()
f, ok := s.flags[flagName]
s.mu.RUnlock()
if !ok { return false}
if f.Enabled { return true}
if f.Percentage <= 0 { return false}
if f.Percentage >= 100 { return true}
h := fnv.New64a()
h.Write([]byte(userID)) bucket := int(h.Sum64() % 100) return bucket < f.Percentage}