Skip to main content

Feature Flags System

Overview

The FishingLog backend includes a comprehensive feature flag system similar to LaunchDarkly, allowing you to control feature availability across different environments (Development, Staging, Production). This enables you to:

  • Turn features on/off per environment
  • Gradually roll out features using percentage-based rollouts
  • Target specific users for beta testing
  • Control features through the admin panel without code deployments

Architecture

Components

  1. FeatureFlag Entity - Database entity storing feature flag configuration
  2. FeatureFlagService - Service for checking feature availability
  3. AdminFeatureFlagController - Admin API for managing feature flags
  4. RequireFeatureAttribute - Authorization attribute for protecting endpoints

Key Features

  • Environment-Specific Control: Enable/disable features per environment (Development, Staging, Production)
  • Percentage Rollouts: Gradually release features to a percentage of users
  • User Targeting: Target specific users for beta testing
  • Caching: Feature flags are cached for 5 minutes to reduce database queries
  • Audit Logging: All changes are logged for compliance and debugging

Usage

Protecting Controllers/Endpoints

Use the [RequireFeature] attribute to protect entire controllers or individual endpoints:

[ApiController]
[Route("api/[controller]")]
[Authorize]
[RequireFeature("Circles")] // Feature must be enabled
public class CircleController : ControllerBase
{
// All endpoints require Circles feature to be enabled
}

Or protect individual endpoints:

[HttpPost]
[RequireFeature("Tournaments")]
public async Task<ActionResult> CreateTournament(...)
{
// Only accessible if Tournaments feature is enabled
}

Checking Features in Code

Inject FeatureFlagService and check features programmatically:

public class MyService
{
private readonly FeatureFlagService _featureFlagService;

public MyService(FeatureFlagService featureFlagService)
{
_featureFlagService = featureFlagService;
}

public async Task DoSomething(Guid userId)
{
var isEnabled = await _featureFlagService.IsFeatureEnabledAsync("Circles", userId);

if (isEnabled)
{
// Feature is enabled for this user/environment
}
}
}

Synchronous Check (Uses Cache)

For performance-critical code that runs frequently:

// Uses cached value (must have been checked async first)
var isEnabled = _featureFlagService.IsFeatureEnabled("Circles", userId);

Admin Panel API

Get All Feature Flags

GET /api/admin/feature-flags
GET /api/admin/feature-flags?category=Social

Get Specific Feature Flag

GET /api/admin/feature-flags/{key}

Get Current Environment Flags

GET /api/admin/feature-flags/current-environment

Create Feature Flag

POST /api/admin/feature-flags
Content-Type: application/json

{
"key": "NewFeature",
"name": "New Feature",
"description": "Description of the feature",
"category": "Social",
"isEnabledInDevelopment": true,
"isEnabledInStaging": true,
"isEnabledInProduction": false,
"rolloutPercentage": null,
"targetUserIds": null,
"isActive": true,
"reason": "Initial creation"
}

Update Feature Flag

PUT /api/admin/feature-flags/{key}
Content-Type: application/json

{
"isEnabledInProduction": true,
"rolloutPercentage": 25,
"reason": "Rolling out to 25% of users"
}

Delete Feature Flag

DELETE /api/admin/feature-flags/{key}

Feature Flag Configuration

Basic Configuration

  • Key: Unique identifier (e.g., "Circles", "Tournaments")
  • Name: Human-readable name
  • Description: What the feature does
  • Category: Grouping (e.g., "Social", "Tournaments", "Gear")

Environment Flags

  • IsEnabledInDevelopment: Enable in Development environment
  • IsEnabledInStaging: Enable in Staging environment
  • IsEnabledInProduction: Enable in Production environment

Advanced Features

  • RolloutPercentage: 0-100, gradually roll out to percentage of users
  • TargetUserIds: Array of user IDs for beta testing (overrides rollout percentage)
  • IsActive: Can disable the feature flag entirely

Example: Circles Feature

The Circles feature is configured as follows:

  • Development: ✅ Enabled
  • Staging: ✅ Enabled
  • Production: ❌ Disabled

This allows testing in dev/staging while keeping it disabled in production until there are enough users.

Cache Management

Feature flags are cached for 5 minutes. The cache is automatically invalidated when:

  • A feature flag is created
  • A feature flag is updated
  • A feature flag is deleted

You can manually invalidate the cache:

_featureFlagService.InvalidateCache(); // All flags
_featureFlagService.InvalidateCache("Circles"); // Specific flag

Best Practices

  1. Use Descriptive Keys: Use clear, consistent naming (e.g., "Circles", "Tournaments", "Charters")
  2. Document Features: Always provide a description explaining what the feature does
  3. Use Categories: Group related features together
  4. Gradual Rollouts: Use percentage rollouts for new features
  5. Beta Testing: Use TargetUserIds for early access testing
  6. Monitor Changes: Review audit logs regularly
  7. Clean Up: Remove feature flags for features that are fully rolled out

Migration and Seeding

Feature flags are automatically seeded on database initialization. The initial seed includes:

  • Circles: Enabled in Dev/Staging, disabled in Production

To add more feature flags, update FeatureFlagSeeder.cs or use the admin API.

Security

  • Feature flag management requires AdminOnly authorization
  • All changes are logged in the audit log
  • Feature flags cannot be modified by regular users
  • The RequireFeature attribute returns 404 (not 403) to hide disabled features

Future Enhancements

Potential future improvements:

  • Scheduled enable/disable dates
  • A/B testing support
  • Feature flag analytics
  • Frontend integration for conditional UI rendering
  • Webhook notifications on flag changes