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.
Implement a Boolean Flag
Let’s add a new flag via the fun_with_flags
web dashboard.
- Go to
http://localhost:4000/feature-flags
- Click on the
New Flag
button at the top right side of the navigation bar. - Add
uppercase_company_name
and click on theCreate
button.
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.
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
.
- Go to
http://localhost:4000/feature-flags
- Click on the
uppercase_company_name
link. - Click on
enable
button in theDefault state
section.
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
- Go to
http://localhost:4000/feature-flags
- Click on the
New Flag
button at top right side of the navigation bar. - Add
enable_edit_button
and click on theCreate
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.
- Go to
http://localhost:4000/feature-flags
- Click on
enable_edit_button
link - Click on
enable
button inDefault 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
.
- Go to
http://localhost:4000/feature-flags
- Click on the
enable_edit_button
link - In the
Actors
section, addcompany:6
in theactor ID
field then click on thedisabled
radio button, then theAdd
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
- Go to
http://localhost:4000/feature-flags
- Click on the
New Flag
button at the top right side of the navigation bar. - Add
hide_employees
and click on theCreate
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 %>
- Go to
http://localhost:4000/feature-flags
- Click on the
hide_employees
link - In the
Groups
section, addshow_employees
text in theGroup name
field then click on theenabled
radio button then theAdd
button.
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
- Go to
http://localhost:4000/feature-flags
- Click on the
New Flag
button at the top right side of the navigation bar. - Add
show_50_percentage_of_time
and click on theCreate
button. - Add
0.5
in the input field of thePercentage
section, click the% of time
radio button, then clickAdd
.
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
- Go to
http://localhost:4000/feature-flags
- Click on the
New Flag
button at the top right side of the navigation bar. - Add
show_in_70_percentage_of_companies
and click on theCreate
button. - Add
0.7
in the input field of thePercentage
section, click the% of actors
radio button then clickAdd
.
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
- Go to
http://localhost:4000/feature-flags
- Click on any flag you wish to remove.
- Click on the
Delete Flag
button in theDanger Zone
section or you can click theClear
button in theDefault state
.
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.