How to Add Feature Flags in a Phoenix Application Using fun_with_flags

A row of multi-colored, triangular flags on a line, with a blue sky in the background
Syed Murtza

Engineer

Syed Murtza

What is a Feature Flag?

In software development, feature flags are used to enable or disable a feature at runtime without deployment of the code. This separates the deployment process from the release of new features.

Feature flags can be called “feature toggles”, “feature flippers”, “release flags”, “release toggles” or “conditional features”.

Feature flags are useful in a crisis. For example, a team released a feature to production only to discover that it wasn’t quite ready. Perhaps it ended up failing to meet users’ expectations, or perhaps it didn’t quite work as intended. Rather than calling for emergency meetings or deployments to disable the feature, we can simply turn off the toggle.

fun_with_flags is a popular package that lets developers easily add feature flag support to their Phoenix applications. This blog post will walk through different ways to configure the library in order to demonstrate different use cases for feature flags. In the simplest way, we can enable/disable a feature for everyone but fun_with_flags provides us with more granular and precise control over which feature should be enabled or disabled for which type of structs.

fun_with_flags provides extensive information in the README file. It can be configured with redis or relational databases to store flag information. For simplicity, we will configure it with postgres to persist the flags. It will not make calls to the DB for every lookup. In fact, it will create a local cache of the flags information in ETS for faster lookups. At runtime, if we change the state of the flags, then it synchronizes the change as well.

fun_with_flags allows developers to fine-tune the scope of feature flags using something called gate.

Different gates can achieve different specifications, including:

Boolean Gate

This gate is the simplest one, as we can enable/disable a feature globally (for every struct).

Actor Gate

This gate is used to enable/disable a feature for certain structs that meet specific conditions. For example, enable/disable a certain feature for a company named DockYard or for a user whose id is 99.

Group Gate

This gate is used for any number of structs that meet a specific condition. For example, we can enable/disable a feature for all employees who belong to a company named DockYard.

Percentage of Time Gate

This gate will enable/disable a feature for a percentage of the time. For example, it will enable a feature for a company DockYard for 50% of the requests and disable the same feature for the remaining 50% of the requests.

Percentage of Actor Gate

This gate will enable/disable a feature for a percentage of actors. For example, it will enable/disable a certain feature for 50% of companies. If we have 1,000 companies in our database, it will enable a feature for 500 companies and disable it for the other 500. However, if a feature is disabled for a specific company then that feature will be disabled regardless of the requests.

Let’s Create a Demo Application

mix phx.new app

Add some scaffolding for a Company entity.

mix phx.gen.html Companies Company companies name number_of_employees:integer

Add the following line (route) in router.ex.

resources "/companies", CompanyController

Let’s add some data to play with by adding the following snippet in seeds.exs

alias App.Companies
for c <- 1..50 do
 company_attrs = %{
   name: "Company #{c}",
   number_of_employees: Enum.random(1..1000)
 }
 {:ok, company} = Companies.create_company(company_attrs)
end

Open the lib/app_web/templates/layout/root.html.heex template and replace the <header> tag with the following snippet.

<header>
 <section class="container">
   <nav>
     <ul>
       <li><%= link "Companies", to: Routes.company_path(@conn, :index) %></li>
     </ul>
   </nav>
 </section>
</header>

Let’s Set Up fun_with_flags in Our Phoenix Application

Add the following dependency to the deps function in mix.exs:

{:fun_with_flags, "~> 1.10.1"}

Add the following in config.exs.

config(:fun_with_flags, :cache_bust_notifications, enabled: false)
config :fun_with_flags, :persistence,
 adapter: FunWithFlags.Store.Persistent.Ecto,
 repo: App.Repo

Let’s add fun_with_flags_ui web dashboard in mix.exs so we can easily add/delete or enable/disable flags.

{:fun_with_flags_ui, "~> 0.8"}

Add the following scope in router.ex.

scope path: "/feature-flags" do
 pipe_through :browser
 forward "/", FunWithFlags.UI.Router, namespace: "feature-flags"
end

Fetch all the dependencies of the application by running the following command in the terminal:

mix deps.get

Create a new migration file to add a table to save the state of feature flags in the database.

mix ecto.gen.migration add_feature_flags_table

Open the newly created file priv/repo/migrations/*_add_feature_flags_table.exs and replace its content with the following snippet.

defmodule FunWithFlags.Dev.EctoRepo.Migrations.CreateFeatureFlagsTable do
 use Ecto.Migration
 def up do
   create table(:fun_with_flags_toggles, primary_key: false) do
     add :id, :bigserial, primary_key: true
     add :flag_name, :string, null: false
     add :gate_type, :string, null: false
     add :target, :string, null: false
     add :enabled, :boolean, null: false
   end
   create index(
            :fun_with_flags_toggles,
            [:flag_name, :gate_type, :target],
            unique: true,
            name: "fwf_flag_name_gate_target_idx"
          )
 end
 def down do
   drop table(:fun_with_flags_toggles)
 end
end

Let’s run the following command in the terminal to drop, create, migrate and populate the database with our sample data.

mix ecto.reset

Run the following command in the terminal to start the application.

iex -S mix phx.server

Open http://localhost:4000/ and click on the Companies link in the browser and you will see a list of companies.

Companies List

Implement a Boolean Flag

Let’s add a new flag via the fun_with_flags web dashboard.

  1. Go to http://localhost:4000/feature-flags Companies List
  2. Click on the New Flag button at the top right side of the navigation bar.
  3. Add uppercase_company_name and click on the Create button. Companies List

Now the new flag is inserted in the fun_with_flags_toggles database table, so we can use this flag in our code. The browser will be redirected to the flag information page. Companies List

Open templates/company/show.html.heex in the text editor and replace <%= @company.name %> with the following code snippet:

<%= if FunWithFlags.enabled?(:uppercase_company_name) do %>
 <%= String.upcase(@company.name) %>
<% else %>
 <%= @company.name %>
<% end %>

Now restart the application by pressing CTRL+c twice in the terminal then run iex -S mix phx.server.

  1. Go to http://localhost:4000/feature-flags
  2. Click on the uppercase_company_name link.
  3. Click on enable button in the Default state section. Companies List

Now go back to http://localhost:4000/, click on the Companies link then click on Show link of any company. Because the flag is enabled, you will see that the company name is capitalized on every company’s show page.

Implement an Actor Flag for Company

Let’s create a file feature_flags_impl.ex in the lib/app/ directory with the following content.

defimpl FunWithFlags.Actor, for: App.Companies.Company do
 def id(%{id: id}) do
   "company:#{id}"
 end
end
  1. Go to http://localhost:4000/feature-flags
  2. Click on the New Flag button at top right side of the navigation bar.
  3. Add enable_edit_button and click on the Create button.

Open templates/company/show.html.heex in text editor and replace <span><%= link "Edit", to: Routes.company_path(@conn, :edit, @company) %></span> | with the following code snippet:

<%= if FunWithFlags.enabled?(:enable_edit_button, for: @company) do %>
 <span><%= link "Edit", to: Routes.company_path(@conn, :edit, @company) %></span> |
<% end %>

Now restart the application and click on the Companies link then click on the show link for any row. You will see that the edit button is hidden for every company because flags are disabled by default.

  1. Go to http://localhost:4000/feature-flags
  2. Click on enable_edit_button link
  3. Click on enable button in Default state

Click on Companies then click on the show link for any row. You will see that the edit button displays for every company.

Now we want to hide the edit button only for Company 6.

  1. Go to http://localhost:4000/feature-flags
  2. Click on the enable_edit_button link
  3. In the Actors section, add company:6 in the actor ID field then click on the disabled radio button, then the Add button.

Now click the show button for Company 6. You will see the edit button is hidden.

Implement a Group Flag

Let’s add the following code at the end of the file lib/app/feature_flags_impl.ex.

defimpl FunWithFlags.Group, for: App.Companies.Company do
 def in?(%{number_of_employees: number_of_employees}, "show_employees"),
   do: number_of_employees > 600
end
  1. Go to http://localhost:4000/feature-flags
  2. Click on the New Flag button at the top right side of the navigation bar.
  3. Add hide_employees and click on the Create button.

Open templates/company/show.html.heex in your text editor and replace

<li>
 <strong>Number of employees:</strong>
 <%= @company.number_of_employees %>
</li>

with the following code snippet:

<%= if FunWithFlags.enabled?(:hide_employees, for: @company) do %>
 <li>
   <strong>Number of employees:</strong>
   <%= @company.number_of_employees %>
 </li>
<% end %>
  1. Go to http://localhost:4000/feature-flags
  2. Click on the hide_employees link
  3. In the Groups section, add show_employees text in the Group name field then click on the enabled radio button then the Add button. Companies List

Now restart the application and click on the Companies link then the show link for any row. You will see that number_of_employees is hidden for every company that has less than 600 number_of_employees.

Implement a Percentage of Time Flag

  1. Go to http://localhost:4000/feature-flags
  2. Click on the New Flag button at the top right side of the navigation bar.
  3. Add show_50_percentage_of_time and click on the Create button.
  4. Add 0.5 in the input field of the Percentage section, click the % of time radio button, then click Add.

Open templates/company/show.html.heex in your text editor and add the following code snippet at the end of the file:

<%= if FunWithFlags.enabled?(:show_50_percentage_of_time) do %>
 <h3>This text will appear 50% of the time (requests)</h3>
<% end %>

Now restart the application and click on Companies, then click on the show link for any row. You will see that the text will show/hide 50% of the time (requests) for the same company.

Implement a Percentage of Actor Flag

  1. Go to http://localhost:4000/feature-flags
  2. Click on the New Flag button at the top right side of the navigation bar.
  3. Add show_in_70_percentage_of_companies and click on the Create button.
  4. Add 0.7 in the input field of the Percentage section, click the % of actors radio button then click Add.

Open templates/company/index.html.heex in your text editor and replace <td><%= company.number_of_employees %></td> with the following code snippet at the end of the file.

<%= if FunWithFlags.enabled?(:show_in_70_percentage_of_companies, for: company) do %>
 <td><%= company.number_of_employees %></td>
<% else %>
 <td></td>
<% end %>

Now restart the application and click on the Companies link. You will see that number_of_employees is shown for 70% of the companies.

How to Remove a Flag

  1. Go to http://localhost:4000/feature-flags
  2. Click on any flag you wish to remove.
  3. Click on the Delete Flag button in the Danger Zone section or you can click the Clear button in the Default state. Companies List

Conclusion

We can build a simple system with feature flags by using Environment Variables or with a GenServer module which can save the state in ETS, Redis, or Postgres.

Every new package or piece of code we add to our applications adds to the performance of the application, but the benefits of using a library like fun_with_flags outweigh the overhead. I see its use case in almost every application where we need to make quick decisions at runtime.

And that’s it! Now you can quickly and easily enable or disable features in your Phoenix application.

Whether you need engineering prowess, next-level design, UXD that delights, or product strategy to bring it all together, DockYard can help. Contact us today to learn how we can help you reach your goals, faster.

Newsletter

Stay in the Know

Get the latest news and insights on Elixir, Phoenix, machine learning, product strategy, and more—delivered straight to your inbox.

Narwin holding a press release sheet while opening the DockYard brand kit box