FunWithFlags Documentation

repository·master·Indexed 22 days ago

https://github.com/tompave/fun_with_flags

An Elixir library for managing feature flags with distributed synchronization. It features a two-level caching mechanism using ETS and persistent backends (Redis or Ecto), supporting complex rollout strategies via gates such as Actors, Groups, and percentage-based rollouts (time or actors). It includes a priority-based resolution system and an optional web dashboard via FunWithFlags.UI.

Tokens
8.9K
Snippets
29
Records
41
Agent score
77%

What's inside FunWithFlags

  1. Overview of FunWithFlags

    master

    FunWithFlags is an Elixir OTP application designed for managing feature flags (feature toggles) at runtime. It allows you to control application features by toggling boolean values associated with specific names without requiring a redeployment or server restart.

    Key features include:

    • Two-level storage: Uses a persistent backend (Redis or a relational database via Ecto) for synchronization across nodes, combined with a local ETS table cache for high-performance lookups.
    • Distributed synchronization: When flags are modified, nodes are notified via PubSub to reload their local ETS caches.
    • Granular control: Beyond simple global toggles, you can use "gates" to implement complex rules like targeting specific actors, groups, or percentages of users/time.
  2. What are Feature Flags and Gates?

    master

    A Feature Flag is a boolean value associated with a name used to enable or disable application functionality at runtime.

    Gates are rules applied to flags to provide fine-grained control. Instead of a simple global on/off switch, gates allow you to define conditions such as:

    • Enabling a feature only for specific users (Actor Gate).
    • Enabling a feature for specific segments (Group Gate).
    • Rolling out a feature to a specific percentage of users (Percentage of Actors Gate).
    • Enabling a feature for a specific percentage of time (Percentage of Time Gate).
  3. Understand Gate Priority and Interactions

    master

    Feature flags resolve based on a priority order from most to least specific: Actors > Groups > Boolean > Percentage.

    Rules for resolution:

    1. Actor gates are the highest priority and act as overrides for everything else.
    2. Group gates override Boolean gates but are overridden by Actor gates.
    3. Boolean gates override Percentage gates.
    4. Percentage gates are checked last and only if no other gate (Actor, Group, or Boolean) is enabled.
    5. Conflicting Groups: If an entity belongs to multiple groups with conflicting statuses, disabled group gates take precedence over enabled ones.
    6. Mutual Exclusivity: %-of-time and %-of-actors gates are mutually exclusive. Setting one will replace the other.
  4. How FunWithFlags caching works

    master

    FunWithFlags uses an ETS (Erlang Term Storage) cache on each application node to minimize expensive round-trips to the database (Redis, PostgreSQL, or MySQL). This makes flag queries significantly faster (10x-40x depending on the DB).

    To maintain consistency across multiple application nodes, FunWithFlags employs a three-tier synchronization strategy:

    1. PubSub: The library emits change notifications via PubSub. All nodes subscribe to the same channel and reload their local ETS cache when a change is detected.
    2. TTL (Time-To-Live): If PubSub fails, the cache uses a configurable TTL to periodically refresh data from the database.
    3. Direct DB Access: If caching is disabled, the library reads directly from the database for every check (similar to the Flipper Ruby gem behavior).
  5. Configure persistence adapters

    master

    FunWithFlags supports multiple persistence layers. You must include the corresponding dependency in your mix.exs file:

    • Redis: Include :redix.
    • SQL (PostgreSQL, MySQL, etc.): Include :ecto_sql and a compatible Ecto adapter such as :postgrex, :mariaex, or :myxql.

    If you are using Redis and want to avoid using Redis' built-in PubSub for cache invalidation, you can optionally include :phoenix_pubsub.

  6. Query and toggle feature flags with FunWithFlags

    master

    The primary way to interact with feature flags is using FunWithFlags.enabled?/2. You can check if a flag is enabled globally or for a specific entity (actor). To change the state of a flag, use enable/1, disable/1, or clear/1.

    Supported gate types:

    • Boolean: Global on/off.
    • Actors: On/off for specific entities (requires implementing FunWithFlags.Actor).
    • Groups: On/off for categories of entities (requires implementing FunWithFlags.Group).
    • %-of-Time: Globally on for a percentage of calls (pseudo-random).
    • %-of-Actors: Globally on for a percentage of specific actors (deterministic/consistent).
    # Check if a flag is enabled
    FunWithFlags.enabled?(:cool_new_feature)
    
    # Enable a flag globally
    {:ok, true} = FunWithFlags.enable(:cool_new_feature)
    
    # Disable a flag globally
    {:ok, false} = FunWithFlags.disable(:cool_new_feature)
  7. Use the FunWithFlags Web Dashboard

    master
    If you require a graphical control panel to manage your feature flags, you can use FunWithFlags.UI. This is an optional extension that is implemented as a Plug, meaning it can be embedded into an existing Phoenix or Plug application, or it can be served as a standalone service.
  8. Run benchmarks for FunWithFlags

    master

    The repository includes benchmark scripts to test performance under different configurations. You can run these scripts directly or modify them for specific scenarios.

    To run benchmarks, you must ensure the local build directory for the library is cleared to avoid interference, then execute the scripts using mix run while providing the necessary environment variables for persistence and caching.

    ### Example with Redis
    ```bash
    rm -r _build/dev/lib/fun_with_flags/ &&
    PERSISTENCE=redis CACHE_ENABLED=true mix run benchmarks/flag.exs

    Running the benchmarks with Ecto

    rm -r _build/dev/lib/fun_with_flags/ &&
    PERSISTENCE=ecto RDBMS=postgres CACHE_ENABLED=false mix run benchmarks/persistence.exs
  9. Install FunWithFlags via mix

    master

    To install FunWithFlags, add it to your mix.exs dependencies. Note that adapter dependencies (like redix or ecto_sql) are optional and must be explicitly included based on your chosen persistence layer.

    def deps do
      [
        {:fun_with_flags, "~> 1.13.0"},
    
        # either:
        {:redix, "~> 0.9"},
        # or:
        {:ecto_sql, "~> 3.0"},
    
        # optionally, if you don't want to use Redis' builtin pubsub
        {:phoenix_pubsub, "~> 2.0"},
      ]
    end
  10. How Actor gates work and their priority

    master

    Actor gates allow you to target specific entities for feature flags. This is useful for:

    • Showcasing work-in-progress features to specific users.
    • Gradually rolling out functionality by country.
    • Dynamically disabling features in specific contexts (e.g., disabling a feature in a specific country if a critical error is detected).

    Priority: Actor gates take precedence over all other gate types. They function as toggle overrides; if an actor is explicitly enabled or disabled for a flag, that decision overrides any percentage-based or boolean gates.

  11. Understand Flag evaluation priority and group conflicts

    master

    When evaluating a flag for a specific item using [for: item], FunWithFlags follows a strict hierarchy to ensure safety:

    1. Actor Gates: First priority. If an actor gate returns a result, it is used immediately.
    2. Group Gates: Second priority. If no actor gate applies, group gates are checked.
      • Conflict Resolution: If an item belongs to multiple groups, disabled groups take precedence. If any group gate explicitly returns {:ok, false}, the entire flag evaluation returns false. An enabled group result is only returned if no subsequent group gates are disabled.
    3. Global Gates: Final fallback. If no actor or group gates apply, the system checks:
      • percentage_of_actors_gate (if available)
      • boolean_gate
      • percentage_of_time_gate
  12. How Group gates interact with other gate types

    master

    FunWithFlags uses a specific hierarchy to resolve whether a feature is enabled for a given actor. When evaluating a feature flag, the priority is as follows:

    1. Actor Gates: Highest priority. If a gate is specifically enabled or disabled for a specific actor, this result is used.
    2. Group Gates: Medium priority. If no actor-specific rule exists, the library checks if the actor belongs to a group that has the feature enabled/disabled.
    3. Boolean Gates: Lowest priority. If no actor or group rules apply, the global boolean state of the flag is used.