Skip to content

Feature Flagging System

Feature flags (also known as feature toggles or feature switches) are a powerful technique for modifying system behavior without changing code. Bonsai's tree-based rule engine is an excellent fit for implementing a sophisticated feature flagging system.

Why Use Bonsai for Feature Flags?

Bonsai offers several advantages for feature flagging:

  • Conditional Logic: Feature flags can be based on various factors like user attributes, environment, or time
  • Hierarchical Structure: Features can be organized in a hierarchical structure
  • Dynamic Updates: Feature flags can be updated without code changes or redeployment
  • Versioning: Changes to feature flags can be tracked and potentially reverted
  • Performance: Efficient evaluation for high-throughput scenarios

Basic Feature Flag Implementation

Here's a simple example of implementing feature flags with Bonsai:

// Create a Bonsai instance
Bonsai<Context> bonsai = BonsaiBuilder.builder()
    .withBonsaiProperties(BonsaiProperties.builder().build())
    .withEdgeStore(new InMemoryEdgeStore())
    .withKeyTreeStore(new InMemoryKeyTreeStore())
    .withKnotStore(new InMemoryKnotStore())
    .build();

// Create enabled and disabled knots
Knot enabledKnot = bonsai.createKnot(
    ValuedKnotData.builder().booleanValue(true).build(),
    Map.of("description", "Feature enabled")
);

Knot disabledKnot = bonsai.createKnot(
    ValuedKnotData.builder().booleanValue(false).build(),
    Map.of("description", "Feature disabled")
);

// Create a feature flag knot
Knot newUIFeatureKnot = bonsai.createKnot(
    ValuedKnotData.builder().build(),
    Map.of("description", "New UI Feature Flag")
);

// Enable for beta users
bonsai.addVariation(newUIFeatureKnot.getId(), Variation.builder()
    .knotId(enabledKnot.getId())
    .filters(List.of(
        Filter.builder()
            .path("$.user.betaProgram")
            .operator(Operator.EQUALS)
            .value(true)
            .build()
    ))
    .build());

// Default to disabled
bonsai.addVariation(newUIFeatureKnot.getId(), Variation.builder()
    .knotId(disabledKnot.getId())
    .filters(List.of())
    .build());

// Map to a key
bonsai.createMapping("features.newUI", newUIFeatureKnot.getId());

// Evaluate the feature flag
Context context = Context.builder()
    .documentContext(JsonPath.parse("{\"user\": {\"betaProgram\": true}}"))
    .build();
KeyNode result = bonsai.evaluate("features.newUI", context);
boolean isEnabled = result.getValue().getBooleanValue(); // true

Advanced Feature Flag Implementation

For a more sophisticated feature flagging system, you can implement:

Multiple Conditions

Enable features based on multiple conditions:

// Enable for beta users in specific regions
bonsai.addVariation(newUIFeatureKnot.getId(), Variation.builder()
    .knotId(enabledKnot.getId())
    .filters(List.of(
        Filter.builder()
            .path("$.user.betaProgram")
            .operator(Operator.EQUALS)
            .value(true)
            .build(),
        Filter.builder()
            .path("$.user.region")
            .operator(Operator.IN)
            .value(List.of("US-WEST", "EU-CENTRAL"))
            .build()
    ))
    .build());

Percentage Rollouts

Enable features for a percentage of users:

// Enable for 10% of users
bonsai.addVariation(newUIFeatureKnot.getId(), Variation.builder()
    .knotId(enabledKnot.getId())
    .filters(List.of(
        Filter.builder()
            .path("$.user.id")
            .operator(Operator.MODULO)
            .value(10)
            .build(),
        Filter.builder()
            .path("$.user.id.modulo")
            .operator(Operator.LESS_THAN)
            .value(1)
            .build()
    ))
    .build());

Time-Based Rollouts

Enable features based on time:

// Enable during business hours
bonsai.addVariation(newUIFeatureKnot.getId(), Variation.builder()
    .knotId(enabledKnot.getId())
    .filters(List.of(
        Filter.builder()
            .path("$.request.time.hour")
            .operator(Operator.GREATER_THAN_EQUAL)
            .value(9)
            .build(),
        Filter.builder()
            .path("$.request.time.hour")
            .operator(Operator.LESS_THAN)
            .value(17)
            .build()
    ))
    .build());

Feature Dependencies

Enable features based on other features:

// Enable if another feature is enabled
bonsai.addVariation(newUIFeatureKnot.getId(), Variation.builder()
    .knotId(enabledKnot.getId())
    .filters(List.of(
        Filter.builder()
            .path("$.features.parentFeature")
            .operator(Operator.EQUALS)
            .value(true)
            .build()
    ))
    .build());

Feature Configuration

Provide configuration for features:

// Create configuration knots
Knot defaultConfigKnot = bonsai.createKnot(
    ValuedKnotData.builder().jsonValue("{\"theme\":\"light\",\"layout\":\"standard\"}").build(),
    Map.of("description", "Default UI configuration")
);

Knot betaConfigKnot = bonsai.createKnot(
    ValuedKnotData.builder().jsonValue("{\"theme\":\"dark\",\"layout\":\"compact\"}").build(),
    Map.of("description", "Beta UI configuration")
);

// Create a configuration knot
Knot uiConfigKnot = bonsai.createKnot(
    ValuedKnotData.builder().build(),
    Map.of("description", "UI Configuration")
);

// Beta configuration for beta users
bonsai.addVariation(uiConfigKnot.getId(), Variation.builder()
    .knotId(betaConfigKnot.getId())
    .filters(List.of(
        Filter.builder()
            .path("$.user.betaProgram")
            .operator(Operator.EQUALS)
            .value(true)
            .build()
    ))
    .build());

// Default configuration
bonsai.addVariation(uiConfigKnot.getId(), Variation.builder()
    .knotId(defaultConfigKnot.getId())
    .filters(List.of())
    .build());

// Map to a key
bonsai.createMapping("features.uiConfig", uiConfigKnot.getId());

Hierarchical Feature Flags

Organize feature flags in a hierarchical structure:

// Create a map of feature flags
Knot featureFlagsKnot = bonsai.createKnot(
    MapKnotData.builder()
        .keyMapping(Map.of(
            "newUI", newUIFeatureKnot.getId(),
            "newCheckout", checkoutFeatureKnot.getId(),
            "newSearch", searchFeatureKnot.getId()
        ))
        .build(),
    Map.of("description", "Feature Flags")
);

// Map to a key
bonsai.createMapping("features", featureFlagsKnot.getId());

// Evaluate all feature flags
KeyNode result = bonsai.evaluate("features", context);
Map<String, KeyNode> featureFlags = result.getKeyNodeMap();
boolean newUIEnabled = featureFlags.get("newUI").getValue().getBooleanValue();
boolean newCheckoutEnabled = featureFlags.get("newCheckout").getValue().getBooleanValue();
boolean newSearchEnabled = featureFlags.get("newSearch").getValue().getBooleanValue();

Feature Flag Service

In a real application, you would typically implement a service layer for feature flags:

@Service
public class FeatureFlagService {
    private final Bonsai<Context> bonsai;

    @Autowired
    public FeatureFlagService(Bonsai<Context> bonsai) {
        this.bonsai = bonsai;
    }

    public boolean isFeatureEnabled(String featureKey, User user) {
        // Create a context with user data
        Context context = Context.builder()
            .documentContext(JsonPath.parse(user))
            .build();

        // Evaluate the feature flag
        try {
            KeyNode result = bonsai.evaluate("features." + featureKey, context);
            return result.getValue().getBooleanValue();
        } catch (BonsaiError e) {
            // Handle errors (e.g., feature flag not found)
            return false;
        }
    }

    public <T> T getFeatureConfig(String featureKey, User user, Class<T> configClass) {
        // Create a context with user data
        Context context = Context.builder()
            .documentContext(JsonPath.parse(user))
            .build();

        // Evaluate the feature configuration
        try {
            KeyNode result = bonsai.evaluate("features." + featureKey + ".config", context);
            String json = result.getValue().getJsonValue();
            return new ObjectMapper().readValue(json, configClass);
        } catch (Exception e) {
            // Handle errors (e.g., feature config not found, invalid JSON)
            return null;
        }
    }
}

Feature Flag Management UI

For a complete feature flagging system, you would typically implement a management UI that allows non-technical users to:

  • Create and manage feature flags
  • Define conditions for enabling features
  • Monitor feature flag usage
  • A/B test features
  • Gradually roll out features

While implementing a UI is beyond the scope of this guide, Bonsai's API provides all the necessary operations to support such a UI.

Best Practices

  • Use a consistent naming convention: Use a consistent naming convention for feature flag keys
  • Document feature flags: Document the purpose and conditions of each feature flag
  • Clean up old feature flags: Remove feature flags that are no longer needed
  • Test feature flags: Test both enabled and disabled states of feature flags
  • Monitor feature flag usage: Track which features are enabled for which users
  • Consider performance: Optimize feature flag evaluation for high-throughput scenarios
  • Implement caching: Cache feature flag results to improve performance
  • Handle errors gracefully: Provide sensible defaults when feature flags cannot be evaluated