# Feature Flag Patterns and Best Practices

## Common Patterns

### 1. Circuit Breaker Pattern

Combine feature flags with circuit breakers for resilient rollbacks:

```python
from augint_library.resilience import circuit_breaker
from augint_library.feature_flags import feature_flag, FeatureFlags

flags = FeatureFlags()

@feature_flag("use_new_service")
@circuit_breaker(failure_threshold=5, recovery_timeout=60)
def call_new_service():
    """Automatically falls back if new service fails."""
    response = requests.get("https://new-service.api/data")
    response.raise_for_status()
    return response.json()

def call_legacy_service():
    """Fallback to stable legacy service."""
    return legacy_api.get_data()

# If new service fails 5 times, circuit opens and flag is auto-disabled
def auto_disable_on_circuit_open():
    if get_circuit_breakers()["call_new_service"]["state"] == "open":
        flags.set_state("use_new_service", "disabled")
        notify_ops_team("New service circuit breaker opened, flag disabled")
```

### 2. Progressive Rollout Pattern

Gradually increase feature exposure with monitoring:

```python
class ProgressiveRollout:
    def __init__(self, flag_name: str, metrics_client):
        self.flag_name = flag_name
        self.metrics = metrics_client
        self.flags = FeatureFlags()
        self.rollout_stages = [5, 10, 25, 50, 100]
        self.current_stage = 0

    def check_health_metrics(self) -> bool:
        """Check if error rates and latencies are acceptable."""
        error_rate = self.metrics.get_error_rate(self.flag_name)
        p99_latency = self.metrics.get_p99_latency(self.flag_name)

        return error_rate < 0.01 and p99_latency < 500  # ms

    def advance_rollout(self) -> bool:
        """Advance to next rollout stage if healthy."""
        if self.current_stage >= len(self.rollout_stages):
            return False

        if self.check_health_metrics():
            percentage = self.rollout_stages[self.current_stage]
            self.flags.set_state(self.flag_name, f"percentage:{percentage}")
            self.current_stage += 1
            log.info(f"Advanced {self.flag_name} to {percentage}%")
            return True
        else:
            log.warning(f"Health check failed for {self.flag_name}")
            return False

    def rollback(self):
        """Emergency rollback to previous stage."""
        if self.current_stage > 0:
            self.current_stage -= 1
            percentage = self.rollout_stages[self.current_stage - 1] if self.current_stage > 0 else 0
            self.flags.set_state(self.flag_name, f"percentage:{percentage}")
            log.warning(f"Rolled back {self.flag_name} to {percentage}%")

# Usage
rollout = ProgressiveRollout("new_algorithm", metrics_client)

# In your deployment pipeline
@schedule.every(1).hours
def check_rollout():
    rollout.advance_rollout()
```

### 3. Feature Flag Inheritance Pattern

Organize flags hierarchically for better management:

```python
class HierarchicalFeatureFlags(FeatureFlags):
    def is_enabled(self, name: str, context: Optional[Dict] = None) -> bool:
        """Check flag and its parents hierarchically."""
        parts = name.split('.')

        # Check from most specific to least specific
        for i in range(len(parts), 0, -1):
            parent_flag = '.'.join(parts[:i])
            if self.exists(parent_flag):
                return super().is_enabled(parent_flag, context)

        return False

# Usage
flags = HierarchicalFeatureFlags()

# Master switch for all API v2 features
flags.register("api.v2", default="disabled")

# Specific v2 endpoints inherit from parent
flags.register("api.v2.users", default="enabled")  # But can override
flags.register("api.v2.orders", default="enabled")

# Disable all v2 features at once
flags.set_state("api.v2", "disabled")

# Or enable specific endpoints while parent is disabled
flags.set_state("api.v2.users", "percentage:10")
```

### 4. Time-Based Feature Flags

Schedule feature releases and automatic sunsets:

```python
from datetime import datetime, timezone
from typing import Optional

class TimeBasedFeatureFlags(FeatureFlags):
    def register_scheduled(
        self,
        name: str,
        start_time: datetime,
        end_time: Optional[datetime] = None,
        default: str = "disabled"
    ):
        """Register a flag that activates/deactivates based on time."""
        def time_evaluator(context):
            now = datetime.now(timezone.utc)
            if now < start_time:
                return False
            if end_time and now > end_time:
                return False
            return True

        self.register_evaluator(f"{name}_time_check", time_evaluator)
        self.register(name, default="conditional", evaluator=f"{name}_time_check")

        # Store schedule metadata
        self.set_metadata(name, {
            "scheduled_start": start_time.isoformat(),
            "scheduled_end": end_time.isoformat() if end_time else None,
            "type": "time_based"
        })

# Usage
flags = TimeBasedFeatureFlags()

# Feature launches next Monday at 9 AM
launch_time = datetime(2025, 2, 3, 9, 0, 0, tzinfo=timezone.utc)
flags.register_scheduled("monday_feature_launch", start_time=launch_time)

# Limited-time promotion
promo_start = datetime(2025, 2, 14, 0, 0, 0, tzinfo=timezone.utc)
promo_end = datetime(2025, 2, 21, 23, 59, 59, tzinfo=timezone.utc)
flags.register_scheduled("valentines_promo", promo_start, promo_end)
```

### 5. User Segment Targeting

Target specific user segments with custom evaluators:

```python
class UserSegments:
    def __init__(self, user_service):
        self.user_service = user_service
        self.flags = FeatureFlags()
        self._register_evaluators()

    def _register_evaluators(self):
        # Beta users
        self.flags.register_evaluator("is_beta_user",
            lambda ctx: self.user_service.is_beta(ctx.get("user_id"))
        )

        # Geographic targeting
        self.flags.register_evaluator("is_eu_user",
            lambda ctx: ctx.get("region", "").startswith("eu-")
        )

        # Account age
        self.flags.register_evaluator("is_new_user",
            lambda ctx: self.user_service.account_age_days(ctx.get("user_id")) < 30
        )

        # Combination segments
        self.flags.register_evaluator("is_eu_beta_user",
            lambda ctx: (
                self.flags.evaluate("is_eu_user", ctx) and
                self.flags.evaluate("is_beta_user", ctx)
            )
        )

# Usage
segments = UserSegments(user_service)

# Target beta users only
segments.flags.register("experimental_ui",
                       default="conditional",
                       evaluator="is_beta_user")

# GDPR features for EU users
segments.flags.register("enhanced_privacy_controls",
                       default="conditional",
                       evaluator="is_eu_user")

# Special onboarding for new users
segments.flags.register("guided_onboarding",
                       default="conditional",
                       evaluator="is_new_user")
```

### 6. Kill Switch Pattern

Emergency controls for critical features:

```python
class KillSwitch:
    def __init__(self, flags: FeatureFlags, monitoring):
        self.flags = flags
        self.monitoring = monitoring
        self.thresholds = {}

    def register_kill_switch(
        self,
        feature_name: str,
        error_threshold: float = 0.05,  # 5% error rate
        latency_threshold: float = 1000,  # 1 second
        check_interval: int = 60  # seconds
    ):
        """Register automatic kill switch for a feature."""
        self.thresholds[feature_name] = {
            "error": error_threshold,
            "latency": latency_threshold
        }

        # Ensure feature flag exists
        if not self.flags.exists(feature_name):
            self.flags.register(feature_name, default="enabled")

    def check_and_kill(self, feature_name: str) -> bool:
        """Check metrics and kill feature if thresholds exceeded."""
        if feature_name not in self.thresholds:
            return False

        metrics = self.monitoring.get_metrics(feature_name)
        threshold = self.thresholds[feature_name]

        if (metrics["error_rate"] > threshold["error"] or
            metrics["p99_latency"] > threshold["latency"]):

            self.flags.set_state(feature_name, "disabled")
            self.alert_ops_team(feature_name, metrics)
            return True

        return False

# Usage
kill_switch = KillSwitch(flags, monitoring_client)

# Register critical features with kill switches
kill_switch.register_kill_switch("payment_processor_v2",
                                error_threshold=0.01,  # 1% for payments
                                latency_threshold=500)

# In your monitoring loop
@schedule.every(30).seconds
def monitor_kill_switches():
    for feature in kill_switch.thresholds:
        if kill_switch.check_and_kill(feature):
            log.critical(f"Kill switch activated for {feature}")
```

### 7. Feature Flag Bundling

Group related features together:

```python
class FeatureBundle:
    def __init__(self, name: str, flags: FeatureFlags):
        self.name = name
        self.flags = flags
        self.features = []

    def add_feature(self, flag_name: str, default: str = "disabled"):
        """Add a feature to the bundle."""
        self.features.append(flag_name)
        self.flags.register(flag_name, default=default)
        return self

    def enable_all(self):
        """Enable all features in the bundle."""
        for feature in self.features:
            self.flags.set_state(feature, "enabled")

    def disable_all(self):
        """Disable all features in the bundle."""
        for feature in self.features:
            self.flags.set_state(feature, "disabled")

    def set_percentage_all(self, percentage: int):
        """Set all features to same percentage."""
        for feature in self.features:
            self.flags.set_state(feature, f"percentage:{percentage}")

# Usage
dashboard_v2 = FeatureBundle("dashboard_v2", flags)
dashboard_v2.add_feature("dashboard_v2_layout") \
            .add_feature("dashboard_v2_charts") \
            .add_feature("dashboard_v2_filters") \
            .add_feature("dashboard_v2_export")

# Roll out entire dashboard at once
dashboard_v2.set_percentage_all(10)

# Or enable specific features
flags.set_state("dashboard_v2_charts", "enabled")
```

### 8. A/B Testing with Variants

Support multiple variants beyond binary on/off:

```python
from enum import Enum
from typing import Any

class Variant(Enum):
    CONTROL = "control"
    VARIANT_A = "variant_a"
    VARIANT_B = "variant_b"
    VARIANT_C = "variant_c"

class ABTestingFlags(FeatureFlags):
    def get_variant(
        self,
        experiment_name: str,
        context: Dict[str, Any],
        variants: List[Tuple[Variant, int]]  # [(variant, weight)]
    ) -> Variant:
        """Get variant for user based on weights."""
        # Ensure weights sum to 100
        total_weight = sum(weight for _, weight in variants)
        if total_weight != 100:
            raise ValueError(f"Variant weights must sum to 100, got {total_weight}")

        # Consistent hashing for user
        user_id = context.get("user_id", "anonymous")
        hash_input = f"{experiment_name}:{user_id}"
        hash_value = int(hashlib.md5(hash_input.encode()).hexdigest()[:8], 16)
        bucket = hash_value % 100

        # Assign variant based on bucket
        cumulative = 0
        for variant, weight in variants:
            cumulative += weight
            if bucket < cumulative:
                return variant

        return variants[-1][0]  # Fallback

# Usage
ab_flags = ABTestingFlags()

# Define experiment with 3 variants
checkout_variants = [
    (Variant.CONTROL, 34),    # 34% - Original checkout
    (Variant.VARIANT_A, 33),  # 33% - One-page checkout
    (Variant.VARIANT_B, 33),  # 33% - Progressive checkout
]

# Get variant for user
user_context = {"user_id": "user123", "region": "us-west"}
variant = ab_flags.get_variant("checkout_experiment", user_context, checkout_variants)

# Route to appropriate implementation
if variant == Variant.CONTROL:
    return render_original_checkout()
elif variant == Variant.VARIANT_A:
    return render_onepage_checkout()
elif variant == Variant.VARIANT_B:
    return render_progressive_checkout()

# Track metrics by variant
track_event("checkout_viewed", {
    "experiment": "checkout_experiment",
    "variant": variant.value,
    "user_id": user_context["user_id"]
})
```

### 9. Canary Deployment Pattern

Deploy to specific infrastructure first:

```python
class CanaryDeployment:
    def __init__(self, flags: FeatureFlags):
        self.flags = flags

    def register_canary(
        self,
        feature_name: str,
        canary_hosts: List[str],
        canary_percentage: int = 100
    ):
        """Enable feature only on canary hosts."""
        def canary_evaluator(context):
            hostname = context.get("hostname", socket.gethostname())

            # Check if on canary host
            if hostname not in canary_hosts:
                return False

            # Even on canary, might only want percentage
            if canary_percentage < 100:
                return self.flags._evaluate_percentage(
                    canary_percentage,
                    f"{feature_name}:{hostname}"
                )

            return True

        self.flags.register_evaluator(f"{feature_name}_canary", canary_evaluator)
        self.flags.register(feature_name,
                           default="conditional",
                           evaluator=f"{feature_name}_canary")

# Usage
canary = CanaryDeployment(flags)

# Deploy new service to specific hosts first
canary.register_canary(
    "new_caching_layer",
    canary_hosts=["web-001", "web-002"],  # 2 of 50 hosts
    canary_percentage=100  # Full traffic on canary hosts
)

# After validation, expand to percentage of all hosts
flags.set_state("new_caching_layer", "percentage:10")

# Finally, full rollout
flags.set_state("new_caching_layer", "enabled")
```

### 10. Feature Flag Analytics

Track feature adoption and impact:

```python
class FeatureFlagAnalytics:
    def __init__(self, flags: FeatureFlags, analytics_client):
        self.flags = flags
        self.analytics = analytics_client
        self._original_is_enabled = flags.is_enabled
        self._instrument_flags()

    def _instrument_flags(self):
        """Wrap is_enabled to track usage."""
        def instrumented_is_enabled(name: str, context: Optional[Dict] = None):
            start_time = time.time()
            result = self._original_is_enabled(name, context)
            duration = time.time() - start_time

            # Track evaluation
            self.analytics.track("feature_flag_evaluated", {
                "flag_name": name,
                "result": result,
                "duration_ms": duration * 1000,
                "user_id": context.get("user_id") if context else None,
                "context": context
            })

            return result

        self.flags.is_enabled = instrumented_is_enabled

    def get_adoption_rate(self, flag_name: str, time_window: str = "1d") -> float:
        """Calculate feature adoption rate."""
        evaluations = self.analytics.query(
            event="feature_flag_evaluated",
            filters={"flag_name": flag_name},
            time_window=time_window
        )

        if not evaluations:
            return 0.0

        enabled_count = sum(1 for e in evaluations if e["result"])
        return enabled_count / len(evaluations) * 100

    def get_impact_metrics(self, flag_name: str, metric_name: str) -> Dict:
        """Compare metrics between flag enabled/disabled cohorts."""
        return self.analytics.compare_cohorts(
            cohort_a={"feature_flag": flag_name, "enabled": True},
            cohort_b={"feature_flag": flag_name, "enabled": False},
            metric=metric_name
        )

# Usage
analytics = FeatureFlagAnalytics(flags, analytics_client)

# Get adoption metrics
adoption = analytics.get_adoption_rate("new_checkout")
print(f"New checkout adoption: {adoption:.1f}%")

# Compare conversion rates
impact = analytics.get_impact_metrics("new_checkout", "conversion_rate")
print(f"Conversion rate lift: {impact['lift']:.1f}%")
```

## Best Practices Summary

1. **Start Small**: Begin with simple enabled/disabled flags
2. **Use Percentages**: Gradually roll out risky changes
3. **Monitor Everything**: Track both technical and business metrics
4. **Clean Up**: Remove flags after full rollout
5. **Document Intent**: Clear descriptions and metadata
6. **Test Both Paths**: Always test with flag enabled AND disabled
7. **Fail Safe**: Default to the safest behavior
8. **Automate Rollbacks**: Use kill switches for critical features
9. **Segment Wisely**: Target the right users at the right time
10. **Measure Impact**: Use analytics to validate changes

## Anti-Patterns to Avoid

1. **Flag Sprawl**: Too many flags become unmanageable
2. **Permanent Flags**: Flags that never get removed
3. **Complex Dependencies**: Flags depending on other flags
4. **Missing Context**: Not providing user context for consistency
5. **No Monitoring**: Rolling out without watching metrics
6. **All-or-Nothing**: Not using gradual rollouts
7. **Hardcoded Percentages**: Not making them configurable
8. **Flag Logic in Multiple Places**: Centralize flag checks
9. **No Cleanup Plan**: Always plan when to remove flags
10. **Ignoring Performance**: Not caching flag evaluations in tight loops
