So I am building a feature flag solution in my company. This aims at hiding/changing the results of some user actions depending on wether some flags are set.
The flags will be saved in the DB. I can’t change that.
My question is simple: should I allow the use of feature flags in the core of my app? Or should I only allow them to be used in the interface?
My guess is that my core should be agnostic from flags but after all, feature flags are part of the business logic, aren’t they?
Example : Should I use flags like this (without them in the core):
def index(conn, _params) do
my_resource = case FeatureFlag.enabled?("my_flag", get_session(conn)) do
true -> MyAppCore.new_implementation
false -> MyAppCore.old_implementation
end
render(conn, "index.html", resource: my_resource)
end
Or with flags used in the core:
def index(conn, _params) do
my_resource = MyAppCore.my_implementation(get_session(conn))
render(conn, "index.html", resource: my_resource)
end
function in the context :
def my_implementation(session) do
my_resource = case FeatureFlag.enabled?("my_flag", session) do
true -> new_implementation
false -> old_implementation
end
end
This is a personal style choice, but I prefer to keep business logic out of controllers and use pattern matching. I guess this depends on what patterns already are the norm in your app.
def index(conn, _params) do
enabled = FeatureFlag.enabled?("my_flag", session)
my_resource = MyAppCore.my_implementation(enabled get_session(conn))
render(conn, "index.html", resource: my_resource)
end
def my_implementation(true session) do
new_implementation
end
def my_implementation(false, session) do
old_implementation
end
After some thought I’m leaning toward keeping any feature flag logic away from the core. Only functions to enable/disable/check flags would be in the core.
My main point being the following : if I have a web client and a mobile app client, if I put some flags logic in the core, it will make things more complicated if I want to use only flags in one of my clients.
Your solution with adding a enabled boolean argument in my core function would be quite clean, but I could even get rid of it if I only maintain feature flags logic in controllers for example.
The only drawback, if it is really one, is that it would make my controllers fatter. But is it really an issue?
Since you’re pushing alot of your business logic into core and outside of your controllers, it sounds like you have a service layer (more or less). If this is the case, I think you could argue that you’re overloading your controller method. This is going to be an oversimplification from 1000 ft, but: If the mobile app and web client share the same business logic in the service layer and they require different flags why not have two separate routes and controller handlers?
def index_web(conn, _params) do
enabled = FeatureFlag.enabled?("my_web_flag", session)
my_resource = MyAppCore.my_implementation(enabled, :undefined, get_session(conn))
render(conn, "index.html", resource: my_resource)
end
def index_mobile(conn, _params) do
enabled = FeatureFlag.enabled?("my_mobile_flag", session)
my_resource = MyAppCore.my_implementation(:undefined, enabled, get_session(conn))
render(conn, "index.html", resource: my_resource)
end
def my_implementation(true, _, session) do
web_implementation
end
def my_implementation(_, true, session) do
mobile_implementation
end
def my_implementation(_, _, session) do
fallback_implemtation
end
Ofcourse, if everything is live and running and the app is widely deployed, the choice might have already been made for you and you’ll need to get the flags in the controller based on whatever is in the session you’re using to differentiate mobile and web clients.
the choice might have already been made for you and you’ll need to get the flags in the controller
That is indeed the case.
Both interfaces for web and mobile are mixed together, idealy I would have loved to push the split even further by having a my_app_web and a my_app_mobile folder.