tompave / fun_with_flags Goto Github PK
View Code? Open in Web Editor NEWFeature Flags/Toggles for Elixir
Home Page: https://hexdocs.pm/fun_with_flags/FunWithFlags.html
License: MIT License
Feature Flags/Toggles for Elixir
Home Page: https://hexdocs.pm/fun_with_flags/FunWithFlags.html
License: MIT License
Currently, if you specify :redis config options in your main config, there is no way to overwrite this config in a different environment. For example, when writing a test config and overriding the adapter defined by :fun_with_flags, :persistence
, the adapter gets overwritten but it still attempts to make a connection to a Redis instance as there is still the :fun_with_flags, :redis
config that is defined on the main config file but not in say a test.exs
config.
ie
config.exs
config :fun_with_flags, :redis,
host: <host>
port: <port>
test.exs
config :fun_with_flags, :cache_bust_notifications, enabled: false
config :fun_with_flags, :persistence, adapter: MyApp.CustomFlagStore
Merged config would be the following:
#Still attempts to connect to redis as this is defined even though a non redis adapter is defined below
config :fun_with_flags, :redis,
host: <host>
port: <port>
config :fun_with_flags, :cache_bust_notifications, enabled: false
config :fun_with_flags, :persistence, adapter: MyApp.CustomFlagStore
Specifying a customer persistence adapter should not attempt to make a connection to ecto or redis
Hey guys, Im trying to use Elixir 1.13 with this package, but Im getting the following error:
==> fun_with_flags
Compiling 21 files (.ex)
== Compilation error in file lib/fun_with_flags/store/persistent/ecto.ex ==
** (UndefinedFunctionError) function Ecto.Query.from/2 is undefined or private. However there is a macro with the same name and arity. Be sure to require Ecto.Query if you intend to invoke this macro
(ecto 3.7.1) expanding macro: Ecto.Query.from/2
lib/fun_with_flags/store/persistent/ecto.ex:29: FunWithFlags.Store.Persistent.Ecto.get/1
could not compile dependency :fun_with_flags, "mix compile" failed. Errors may have been logged above. You can recompile this dependency with "mix deps.compile fun_with_flags", update it with "mix deps.update fun_with_flags" or clean it with "mix deps.clean fun_with_flags"
These are the versions Im running:
Erlang/OTP 24 [erts-12.1.5] [source] [64-bit] [smp:6:6] [ds:6:6:10] [async-threads:1] [jit]
Elixir 1.13.0 (compiled with Erlang/OTP 24)
fun_with_flags: 1.8.0
Ecto: 3.7.1
phoenix: 1.6.4
Any clue?
Thanks!
This line means that umbrella apps won't work unless they have the application where the config was initially defined as a dependency:
The ecto_table_name
config is evaluated at compile time here: https://github.com/tompave/fun_with_flags/blob/master/lib/fun_with_flags/store/persistent/ecto/record.ex#L11
However this uses Application.get_env
in Config.ecto_table_name
by way of Config.persistence_config
.
The result is that changing the :fun_with_flags, :persistence, :ecto_table_name
config has no effect unless FunWithFlags is recompiled.
I would expect FunWithFlags to reflect the config changes, as it does with the rest of the persistence settings.
I found some info here that shows how to set a table name at runtime for an existing schema (with Ecto.put_meta/2
).
If this sounds right, I could put together a PR.
Hi,
Do you have any suggestions for ensuring that everyone's development / test environments are in sync when folks add new feature flags? We'd love some form of a version controlled config file that at the very least indicates the presence of flags, and even better, the desired flag state / gate / actor, so that each developer doesn't have to know what flags to set in their DB.
Cheers,
Oliver
fun_with_flags
1.6.0 fails to compile under elixir-1.13.0-rc.0
, with the following error:
$ mix deps.compile fun_with_flags
==> fun_with_flags
Compiling 7 files (.ex)
== Compilation error in file lib/fun_with_flags/protocols/actor.ex ==
** (CompileError) lib/fun_with_flags/protocols/actor.ex:116: undefined function defdelegate/2 (there is no such import)
Per the Elixir 1.13.0 release notes:
[Protocol] Add defdelegate to the list of unallowed macros inside protocols as protocols do not allow function definitions
Is there a reason why group names must be atoms?
We are trying to release a feature to some users via a "bucket" method. Something like "users_bucket:1"
, where all the users where rem(id, 10) == 1
would have access to the feature. But with only atom groups, implementing this is kinda cumbersome. Also, kinda unsafe, as a lot of dynamic atoms would be created, and we all know how the BEAM feels about having atoms dynamically generated π
Exploring the codebase, I couldn't find anything that explains the validation. Did I miss any points? If not, can I submit a PR removing the need for atom group names? I can come up with something to maintain backwards compatibility ^^
Any plans on adding a simple Web UI to this project?
I would suggest creating the web UI as part of this project for ease of setup.
Hi and thanks for your work on this library.
I get compiler warnings when I first compile the code:
==> fun_with_flags
Compiling 20 files (.ex)
warning: function Demo.Repo.all/1 is undefined (module Demo.Repo is not available)
Found at 3 locations:
lib/fun_with_flags/store/persistent/ecto.ex:23
lib/fun_with_flags/store/persistent/ecto.ex:157
lib/fun_with_flags/store/persistent/ecto.ex:166
warning: function Demo.Repo.delete_all/1 is undefined (module Demo.Repo is not available)
Found at 3 locations:
lib/fun_with_flags/store/persistent/ecto.ex:92
lib/fun_with_flags/store/persistent/ecto.ex:119
lib/fun_with_flags/store/persistent/ecto.ex:144
warning: function Demo.Repo.insert/2 is undefined (module Demo.Repo is not available)
lib/fun_with_flags/store/persistent/ecto.ex:193
warning: function Demo.Repo.one/1 is undefined (module Demo.Repo is not available)
lib/fun_with_flags/store/persistent/ecto.ex:44
warning: function Demo.Repo.transaction/1 is undefined (module Demo.Repo is not available)
lib/fun_with_flags/store/persistent/ecto.ex:42
warning: function Demo.Repo.update/2 is undefined (module Demo.Repo is not available)
lib/fun_with_flags/store/persistent/ecto.ex:200
because it looks like this module attribute gets compiled before my app is actually ready (since it gets compiled after fun_with_flags):
Outside of the warning, it doesn't appear to cause any issues but I thought I would bring it up in case it was unknown.
The Notification module has to wait on the Application that contains the PubSub to start otherwise it fails, now this is handled with retries but they break really easily and littler tests.
I know that the notifications are now optional but I think the right approach is just to have the Application supervisor that spawns the PubSub also spawn the the Notification module.
Here are the errors:
FunWithFlags: Cannot subscribe to Phoenix.PubSub process MyApp.PubSub (exception: %ArgumentError{message: "argument error"})
FunWithFlags: retrying to subscribe to Phoenix.PubSub, attempt 2
We have a case where we'd like the ability to toggle a numeric range as a flag. While this isn't purely a feature flag, it has overlap with FWF in the sense that we need a UI to edit values at runtime. A hypothetical API could look like this:
FunWithFlags.enable(:high_quality_threshold, values: %{
enabled_value: 10..25,
disabled_value: 0..0
})
FunWithFlags.enabled?(:high_quality_threshold) # true
FunWithFlags.value(:high_quality_threshold) # 10..25
FunWithFlags.disable(:high_quality_threshold)
FunWithFlags.enabled?(:high_quality_threshold) # false
FunWithFlags.value(:high_quality_threshold) # 0..0
This hasn't been fully thought through with regards to how this would interact with actors, gates etc. Furthermore, any type aside from booleans, strings, ints, and nil may be out of scope for an initial attempt.
We're likely going to be building this functionality on top of FWF for our own purposes, but I'd like to know if there would be interest in a PR for this feature with the intent of getting it in the base library/UI library. If so, I'd also like to hear any thoughts you may have on API design.
Phoenix 1.5.0-rc0 is now out with Phoenix PubSub 2.0, however fun_with_flags currently depends on phoenix_pubsub "~> 1.0".
Hi,
I'm setting this up in a Phoenix application. Like this:
config :fun_with_flags, :cache,
enabled: true,
ttl: 900
config :fun_with_flags, :persistence,
adapter: FunWithFlags.Store.Persistent.Ecto,
repo: Plus.Repo,
ecto_table_name: "fun_with_flags_toggles"
config :fun_with_flags, :cache_bust_notifications,
enabled: true,
adapter: FunWithFlags.Notifications.PhoenixPubSub,
client: Plus.PubSub
Migration is setup, ran and I checked that the table is there.
However when I use the API, the error looks like the repo is not configured:
iex βΆ FunWithFlags.enabled?(:my_feat)
βΆβΆβΆ
** (RuntimeError) The NullEctoRepo doesn't implement this. You must configure a proper repo to persist flags with Ecto.
(fun_with_flags 1.5.1) lib/fun_with_flags/store/persistent/ecto/null_repo.ex:9: FunWithFlags.NullEctoRepo.all/1
(fun_with_flags 1.5.1) lib/fun_with_flags/store/persistent/ecto.ex:32: FunWithFlags.Store.Persistent.Ecto.get/1
(fun_with_flags 1.5.1) lib/fun_with_flags/store.ex:15: FunWithFlags.Store.lookup/1
(fun_with_flags 1.5.1) lib/fun_with_flags.ex:77: FunWithFlags.enabled?/2
Checking the config in the same iex session:
iex|2 βΆ Application.get_env(:fun_with_flags, :persistence)
[
adapter: FunWithFlags.Store.Persistent.Ecto,
repo: Plus.Repo,
ecto_table_name: "fun_with_flags_toggles"
]
What am I missing?
First, congrats on the lib! I really like the features and it's being super useful :)
One thing that I was feeling the need, is to somehow configure a set of the available flags, something like this:
config :fun_with_flags,
permitted_flags: [
:feature_a,
:feature_b,
:feature_c,
]
I can see 2 advantages of having this:
FunWithFlags.enabled?(:feature_a)
or FunWithFlags.enable(:feature_a)
, we validate against the permitted flags. Useful to ensure there's not typo in the flag name, for example.The 1st advantage could required a relatively big refactoring and for me is not that important. It's just a plus, I think.
The 2nd advantage I really like to have it, since it makes easier to work only with the UI. This could be implemented in 2 ways, I think:
all_flag_names/0
and all_flags/0
I guess I could "override" the storage I'm using, creating my own adapter and doing these logics for the all_flag_names/0
and all_flags/0
, but having this in the library can be easier for users :)
Do you think this is a valid feature to having in this library?
Great lib! Found a small typo, thought I'd remove it. #37. Hope this helps!
Production servers in my scenario cannot run iex, so DevOps can't toggle the feature flags using the functions described in the documentation.
In essence, my question is, will toggling the flag in Redis store have the desired effect with compiled code?
Hey,
first of all, thanks for release this great library for us all. I think I've found an issue when using Fun With Flags with a multi tenant app. I've implemented my multi tenant model following this ecto guide: https://hexdocs.pm/ecto/multi-tenancy-with-foreign-keys.html.
The problem is when I try to enable/disable a flag, I got the error:
** (RuntimeError) expected org_id or skip_org_id to be set
Obviously, the error occurs because FWF doesn't know anything about my tenant model. I propose to add a fun_with_flags: true
option for every call of Ecto.Repo
inside the lib. By doing that, I can figure out which queries are originated from Fun With Flags and execute these queries outside my tenant model, like this:
defmodule MyApp.Repo do
use Ecto.Repo, otp_app: :my_app
require Ecto.Query
def prepare_query(operation, query, opts) do
cond do
opts[:skip_org_id] || opts[:schema_migration] || opts[:fun_with_flags] ->
{query, opts}
org_id = opts[:org_id] ->
{Ecto.Query.where(query, org_id: ^org_id), opts}
true ->
raise "expected org_id or skip_org_id to be set"
end
end
end
I can work on that and send you a pull request. Do you think there's a chance for you to merge it?
Hello @tompave
Is v1.4.0 going to be released soon(ish)?
I really like the changes you introduced with the behavior for Persistance
that allows to create custom adapters, curious if it's going to be available in the upcoming days to try it out.
Hey there!
Im trying to use the lib but it seems that I can't make it work with the basic configuration.
[error] FunWithFlags: It looks like you're trying to use FunWithFlags.Store.Persistent.Redis to persist flags, but you haven't added its optional dependency to the Mixfile of your project.
In my config.exs
I have:
# FunWithFlags configuration.
config :fun_with_flags, :persistence,
adapter: FunWithFlags.Store.Persistent.Ecto,
repo: EventsApi.Repo
I do have in my mix file
{:ecto_sql, "~> 3.0}",
{:phoenix_pubsub, "~> 1.1"},
Elixir version:
Erlang/OTP 22 [erts-10.4.4] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe] [dtrace]
Elixir 1.9.2 (compiled with Erlang/OTP 20)
I haven't found anything else in the docs that need to be changed. What am I doing wrong?
Thanks
I have a Phoenix Umbrella project with a bunch of apps using PostgreSQL. Each app is configured with a @schema_prefix schema attribute so that they can all access the same database, but using different schemas. That way I can keep each app's data logically separated from the others, without the need to create multiple databases in the same PostgreSQL instance, for example.
However, when I try do use FunWithFlags
within one of these apps, the lib tries to find the table on the default public
schema, and of course, it can't find it and it errors out. Is there a way to configure this in FunWithFlags
? I've tried a few things like including the schema name in the option ecto_table_name
, but with no success.
If we disable the cache in config via setting the enabled
flag to false
then the app crashes when trying to save a new feature flag:
exited in: GenServer.call(FunWithFlags.Store.Cache, {:put, %FunWithFlags.Flag{gates: [%FunWithFlags.Gate{enabled: false, for: nil, type: :boolean}], name: :asd}}, 5000) ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
Is this setting meant to be used in some other way?
Thank you for creating this library. I use it often. I recently added rules for groups and the dialyzer caught that I wasn't using atoms for groups. On further investigation it looks as though FunWithFlagsUI inserts the group rule into the database as a String and is passed to the in?
function as a String (https://github.com/tompave/fun_with_flags/blob/master/lib/fun_with_flags/protocols/group.ex#L68). But, the specs specify that the group should be an atom
. As far as I can tell it comes from the database unchanged:
iex(99)> FunWithFlags.get_flag(:bar)
2019-12-04 14:11:39.911 [info] [application=ecto_sql module=Ecto.Adapters.SQL function=log/4 pid=<0.1619.0> ] QUERY OK source="fun_with_flags_toggles" db=0.7ms
SELECT DISTINCT f0.`flag_name` FROM `fun_with_flags_toggles` AS f0 []
%FunWithFlags.Flag{
gates: [
%FunWithFlags.Gate{enabled: false, for: nil, type: :boolean},
%FunWithFlags.Gate{
enabled: true,
for: "inserted_at<2019-11-8",
type: :group
}
],
name: :bar
}
The documentation also shows the usage as strings (https://github.com/tompave/fun_with_flags#group-gate). My guess is that the spec should be atom() | String.t()
or term()
.
It would be great to have the ability to track who toggled what features and to what. This can be useful both from a troubleshooting standpoint but also to satisfy auditing/compliance requirements.
It would also be lovely if this worked with https://github.com/tompave/fun_with_flags_ui, the more seamless the better. As such this proposal encompasses both libraries, although the changes to FunWithFlags
do stand on their own.
FunWithFlags
changes.I'd like to add a new function:
FunWithFlags.add_audit_log(action, flag_name, current_user, opts)
It would be called like so:
FunWithFlags.add_audit_log(:enable, :my_feature, 1234, [for_percentage_of: {:time, ratio}])
Under the hood this would call a new callback on the Persistent
behavior:
c:FunWithFlags.Store.Persistent.add_audit_log/4
which we'd implement for both
Redix and Ecto.
These function implementations would simply persist an audit log to either a new redis key or a new postgres table. This would require updating the readme to include this new table in a migration (shouldn't be difficult to make this optional).
Additionally, it could be useful to also add another function to the Persistent
behavior which wraps other things in a transaction. This would be useful in fun_with_flags_ui
portion of these changes to ensure that we don't have situations where an audit log gets added but the feature flag doesn't change and vice versa. Thinking a function signature would look something like:
c:FunWithFlags.Store.Persistent.transaction/1
which would just call Ecto.Transaction
or Redix.transaction_pipeline/3
essentially.
All of the above changes I believe can stand alone as useful features to FunWithFlags
.
FunWithFlags.UI
changes.There are a number of places in FunWithFlagsUI
that call FunWithFlags.enable/2
,FunWithFlags.disable/2
, and FunWithFlags.clear/2
directly.
The idea is to wrap those calls in new functions defined in FunWithFlags.UI.Utils
that call out to the previously mentioned FunWithFlags.Store.Persistent.transaction/1
and then calls FunWithFlags.enable/2
,FunWithFlags.disable/2
, or FunWithFlags.clear/2
, as well as FunWithFlags.add_audit_log/4
.
The one missing piece at this point is the method of retrieving the current user.
Would love feedback on this next part:
We could make some assumptions or have some opinions.
We could enforce that the current user is always in conn.assigns.fun_with_flags_current_user
and leave it up to the users of FunWithFlags.UI
to add a plug which puts the current user there.
Or we could allow users to define a function that accepts the %Plug.Conn{}
struct as an argument and spits out the current user. This is way more flexible, and doesn't force users to add something to assigns
, but it's slightly more elaborate to configure. You'd have to pass a reference to this function into application config.
We could also go even further and define a behavior with a callback that accepts a %Plug.Conn{}
struct and outputs the current user, and then force users to implement that callback on a module that they can then pass in as config. This is also a bit elaborate.
Regardless of which one we go with, we'd want to configure this behavior in application config and ensure that if no :audit_logging
config is supplied (should be the state of all things that have implemented this to date), then things simply work and no audit logging is attempted. This should be an opt-in feature.
The above is all we need to get audit logging implemented and working fairly seemlessly, but we still wouldn't have a way to actually view the persisted audit logs without looking in Redis or Postgres itself. So we'd probably want to add a UI in FunWithFlags.UI
for viewing the audit logs related to a given key. This could happen separately from all of the other changes.
What do you think of this proposal? I think I understand enough about this project at this point to make these changes, but super open to feedback!
If you think this sounds like a reasonable new feature, I'd be happy to implement it. The only breaking changes I could think of is the additional callbacks on the FunWithFlags.Store.Persistent
behavior, but at the very least it should warn people that have written custom persistence modules at compile time that they need to implement a new thing.
I'm confident that this can be built in a way that doesn't disrupt existing users (beyond those that have custom persistence) and allows a user to not configure audit logging and simply ignore this feature.
This issue is cropping up randomly when trying to run our Phoenix server. Sometimes it fails hard, sometimes it works fine. It's unclear if this is an actual issue with FunWithFlags
, or if it's a knock-on issue from something else, so please bear with me. Also, the issue appears to involve Logger
to some extent.
elixir: "~> 1.6"
Logger
config:
config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]
FunWithFlags
config:
config :fun_with_flags, :cache,
enabled: true,
ttl: 300
config :fun_with_flags, :persistence,
adapter: FunWithFlags.Store.Persistent.Ecto,
repo: Planswell.Api.Repo
config :fun_with_flags, :cache_bust_notifications,
enabled: true,
adapter: FunWithFlags.Notifications.PhoenixPubSub,
client: lanswell.Api.PubSub
Stack trace:
11:42:17.890 [error] GenServer FunWithFlags.Notifications.PhoenixPubSub terminating
** (RuntimeError) Tried to subscribe to Phoenix.PubSub process Planswell.Api.PubSub 5 times. Giving up.
(fun_with_flags) lib/fun_with_flags/notifications/phoenix_pubsub.ex:83: FunWithFlags.Notifications.PhoenixPubSub.subscribe/1
(fun_with_flags) lib/fun_with_flags/notifications/phoenix_pubsub.ex:117: FunWithFlags.Notifications.PhoenixPubSub.handle_info/2
(stdlib) gen_server.erl:616: :gen_server.try_dispatch/4
(stdlib) gen_server.erl:686: :gen_server.handle_msg/6
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: {:subscribe_retry, 6}
==> common
=ERROR REPORT==== 26-Mar-2018::11:42:18 ===
** Generic server 'Elixir.FunWithFlags.Notifications.PhoenixPubSub' terminating
** Last message in was {subscribe_retry,2}
** When Server state == <<"FUUOChZ9VDiTI3sxNTIyLCA3ODkzNywgNjgyNzA5fQ">>
** Reason for termination ==
** {#{'__exception__' => true,'__struct__' => 'Elixir.RuntimeError',
message =>
<<"cannot use Logger, the :logger application is not running">>},
[{'Elixir.Logger.Config','__data__',0,
[{file,"lib/logger/config.ex"},{line,53}]},
{'Elixir.Logger',bare_log,3,[{file,"lib/logger.ex"},{line,613}]},
{'Elixir.FunWithFlags.Notifications.PhoenixPubSub',handle_info,2,
[{file,"lib/fun_with_flags/notifications/phoenix_pubsub.ex"},
{line,116}]},
{gen_server,try_dispatch,4,[{file,"gen_server.erl"},{line,616}]},
{gen_server,handle_msg,6,[{file,"gen_server.erl"},{line,686}]},
{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}
=INFO REPORT==== 26-Mar-2018::11:42:18 ===
application: fun_with_flags
exited: shutdown
type: temporary
** (Mix) Could not start application api: {:not_running, :fun_with_flags}
G'day! Having already had to use at least one tri-state variation on our previous solution, I'm nervous about solving the same kind of problem by strapping together FWF's boolean flags. All off is fine, but >1 on would be confusing.
Don't suppose we could support any atom, not just :true
and :false
? It'd need an Ecto migration to migrate that boolean enabled
to a string state
β¦Β what else?
Hello π
when working with FunWithFlags
in our test suite we noticed that we have to recompile the fun_with_flags
dependency every time we changed the config around the storage provider (we wanted to simply disable the cache
in the test environment).
** (exit) exited in: GenServer.call(FunWithFlags.Store.Cache, {:put, %FunWithFlags.Flag{gates: [%FunWithFlags.Gate{enabled: true, for: "user:...", type: :actor}], name: :foo}}, 5000)
** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't starte
This happens, because the config is stored in a module variable:
https://github.com/tompave/fun_with_flags/blob/master/lib/fun_with_flags.ex#L30
So when we changed the config to disable caching, it would still use the StoreProvider
instead of the SimpleStoreProvider
, but with the cache process not started.
Would it make sense to move this call from a module variable @store
into a function store()
, so that the config is still changeable without needing to recompile the project?
I can happily help out and open a PR if you agree :)
Also thank you for creating this great project btw π. It really is a joy to use and helps us a great deal in our workflows.
Cheers
Andi
FunWithFlags is using defdelegate
within a defprotocol
block, which is not officially supported and will break in a future Elixir release. See: elixir-lang/elixir#10877 (comment)
Hi!
We've been using your library for some time - it's wonderful, thanks for all the effort you put into it.
We've started noticing a strange pattern when using FunWithFlags
for unit tests. Our setup consists of a postgres table that is used as the backend for the library, where all the flags are saved. We opted for the db backend in tests since Phoenix
offers isolation for accessing the database during tests, while there is no such thing for redit.
However, the fact that your library implements a caching mechanism via ETS
seems to provide shared behaviour between tests, which we want to avoid.
Thus we set the following params inside our test config:
config :fun_with_flags, :cache, enabled: false
And we keep getting crashes because the library tries to call the cache genserver anyway... The offending function seems to be this one, which gets called after each operation of reading/saving flags, whatever the value of the :cache
property is.
I think it would be pretty easy to check the config before calling Cache.put
or Cache.get
, in order to avoid crashes... What do you think?
In order to make the use of the new FunWithFlags.Supervisor
, I added runtime: false
to both fun_with_flags
and fun_with_flags_ui
, but it failed to run in production with an error that FunWithFlags.Supervisor
is not found.
The following seems to fix the problem:
releases: [
myapp: [
applications: [fun_with_flags: :load, fun_with_flags_ui: :load]
]
]
Is there something I'm missing or should the above be added to the docs for the people who are using mix release
to package their app?
Currently Molasses is the only feature flag library in Elixir available that has Postgres backing as an option, though I consider the abstraction of groups much cleaner in this library. Would it be possible to add a persistence layer for Postgres?
Looking through the code, I was looking for where that would get hooked in. as some sort of generic "Store Protocol/Behaviour", to see if that would even be possible with the current implementation, but the current stores (simple_store.ex and store.ex) don't seem to reference one. FunWithFlags.Store.Persistent
seems to be hard coded to start a Redis worker, so perhaps that being made into a pluggable interface of sorts would more easily allow this, potentially opening the door to Mongo adapters or RethinkDB adapters or even Application config backed adapters.
Howdy! I'm attempting to integrate fun_with_flags
into a Phoenix app that's using a MySQL database. Unfortunately, at the moment it doesn't quite behave as expected:
iex(1)> FunWithFlags.enable(:test_flag)
** (ArgumentError) The :conflict_target option is not supported in insert/insert_all by MySQL
(ecto_sql) lib/ecto/adapters/mysql/connection.ex:972: Ecto.Adapters.MySQL.Connection.error!/2
(ecto_sql) lib/ecto/adapters/mysql/connection.ex:149: Ecto.Adapters.MySQL.Connection.insert/6
(ecto_sql) lib/ecto/adapters/mysql.ex:208: Ecto.Adapters.MySQL.insert/6
(ecto) lib/ecto/repo/schema.ex:651: Ecto.Repo.Schema.apply/4
(ecto) lib/ecto/repo/schema.ex:264: anonymous fn/15 in Ecto.Repo.Schema.do_insert/3
(fun_with_flags) lib/fun_with_flags/store/persistent/ecto.ex:193: FunWithFlags.Store.Persistent.Ecto.do_insert/3
(fun_with_flags) lib/fun_with_flags/store/persistent/ecto.ex:71: FunWithFlags.Store.Persistent.Ecto.put/2
(fun_with_flags) lib/fun_with_flags/store.ex:36: FunWithFlags.Store.put/2
(fun_with_flags) lib/fun_with_flags.ex:185: FunWithFlags.enable/2
It should be possible to add this option to the insert statement dynamically, based on the result of Repo.__adapter__
(AFAIK, MySQL is the special case here) - but I'm not certain if that's the best way to approach this.
If you'd welcome a patch implementing the above suggestion, let me know.
Any thoughts on the ability to pass in an actor scoring function into configs. I'm using a Ruby feature flag lib (rollout) in a different service that uses a different scoring algorithm. It would be nice to have them share the same algorithm.
Something along these lines: https://github.com/SalesLoft/fun_with_flags/pull/1/files
I'm happy to add tests and clean it up.
This isn't a bug, but I did fumble through getting fun_with_flags set up for a while. I turns out I wasn't planning on using caching because I was working in dev/test environment trying to set up fun_with_flags with Ecto. I kept getting an error message despite setting Ecto as my adapter saying that I was missing a Redis dependency.
It turns out this error was specifically because I had neglected to turn caching off and it defaults to Redis.
Do you think this is better addressed in the README or modifying the error message to reflect that you can also simply disable caching.
Since fun_with_flags sets the config at compile time, when the config is changed, you have to do a mix deps.clean fun_with_flags
. If you don't, when you compile the app, you get a
ERROR! the application :fun_with_flags has a different value set for key :cache during runtime compared to compile time. Since this application environment entry was marked as compile time, this difference can lead to different behaviour than expected:
* Compile time value was not set
* Runtime value was set to: [enabled: true]
To fix this error, you might:
* Make the runtime value match the compile time one
* Recompile your project. If the misconfigured application is a dependency, you may need to run "mix deps.compile fun_with_flags --force"
* Alternatively, you can disable this check. If you are using releases, you can set :validate_compile_env to false in your release configuration. If you are using Mix to start your system, you can pass the --no-validate-compile-env flag
==> fun_with_flags
could not compile dependency :fun_with_flags, "mix compile" failed. Errors may have been logged above. You can recompile this dependency with "mix deps.compile fun_with_flags", update it with "mix deps.update fun_with_flags" or clean it with "mix deps.clean fun_with_flags"
This causes problems with our CI run, and requires us to issue a mix deps.clean fun_with_flags
as one of the commands so our builds won't get stuck.
The docs specify the following:
The percentage gates can only express an enabled state, as disabling something for a percentage of time or actors is logically equivalent to enabling it for the complementary percentage.
However, because actor gates override group gates, there is a hole in certain contexts.
For instance, if we want to allow all members of a group to have access to a new feature, but limit that group to a subset of actors, this is currently not possible as the actor gate overrides. For this to work, one would have to enable the group, but disable a % of the actors.
An alternative strategy, albeit one with significant change, would be to evaluate all flags, and only pass if all resolve true
. The same override functionality as currently exists could be achieved by providing a veto
flag on a given gate.
Perhaps we're simply missing something about the setup though, and this type of subset limiting already exists.
Would it be possible to add schema support for ecto via prefixes so funwithflags can live in a separate schema in postgres. Useful in a multi-tenancy situation.
I can work around it by customizing the persistence adapter, but it is a fairly simple fix, and happy to submit a patch (I don't know about how it would work in MySQL). Will require changes in two files: ecto.ex and config.ex and also introduce a new name called ecto_table_prefix
lobo
Anything planned for the next release? I need the content of #54
Do you have any feature in mind to include? Maybe I can help you π
Would be super nice to have the prefix for keys configurable. I can make a PR.
I was previously using the fun_with_flags_in_memory
library for getting/setting feature flags in our test environment. The repo now seem to have been deleted, and the package isn't compatible with 1.5 as it depends on 1.4.0. Would it make sense to bring this store into the main library, explicitly for people unit testing? If not, is there another preferred option for this use case?
The only benefits of atom names I can come up with are slightly faster comparison and less memory usage, but I don't think they would make a considerable difference for a real app. And I didn't even take into account that flag names are already converted to strings (and vice versa) by persistence adapters.
The issues with atom names arise when considering flag names that come from external input (suppose I want to provide this library to non-elixir services by wrapping it in JSON API). Because we cannot just convert arbitrary strings to atoms, it would require using String.to_existing_atom/1
and rescuing from exceptions in case the flag name does not exist (as you already do in the web UI).
erlang 23.1
elixir 1.11.3-otp-23
fun_with_flags 1.6.0
When running our application in a production release FWF doesn't seem to be accessing our configs/persistence adapter and is raising an exception when trying to call FunWithFlags.all_flags()
.
Config:
config :fun_with_flags, :persistence,
adapter: FunWithFlags.Store.Persistent.Ecto,
repo: MyApp.Repo
Exception raised when visiting the /feature-flags/flags
endpoints of the UI:
function FunWithFlags.Store.Persistent.Redis.all_flags/0 is undefined (module FunWithFlags.Store.Persistent.Redis is not available)
The application runs without issues in the local dev
environment. I see in 1.5.1 there were some related changes and that there is an open issue related to running the app in a production release but I haven't been able to identify the cause, any insight would be greatly appreciated.
Thanks!
On Elixir 1.9.1, Erlang 21, when I try to get the project working with only the
{:fun_with_flags, "~> 1.4.1"},
line to mix.exs
and then add the following configuration to a brand new phoenix project:
config :fun_with_flags, :cache_bust_notifications,
enabled: true,
adapter: FunWithFlags.Notifications.PhoenixPubSub,
client: Flaggy.PubSub
config :fun_with_flags, :persistence,
adapter: FunWithFlags.Store.Persistent.Ecto,
repo: Flaggy.Repo
(Flaggy
is the name of the phoenix app)
(I'm trying to avoid redis entirely)
I get the following compile error:
code/elixir/flaggy mix phx.server
Compiling 13 files (.ex)
Generated flaggy app
[error] FunWithFlags: It looks like you're trying to use FunWithFlags.Store.Persistent.Redis to persist flags, but you haven't added its optional dependency to the Mixfile of your project.
[info] Application fun_with_flags exited: exited in: FunWithFlags.Application.start(:normal, [])
** (EXIT) an exception was raised:
** (UndefinedFunctionError) function FunWithFlags.Store.Persistent.Redis.worker_spec/0 is undefined (module FunWithFlags.Store.Persistent.Redis is not available)
FunWithFlags.Store.Persistent.Redis.worker_spec()
(fun_with_flags) lib/fun_with_flags/application.ex:28: FunWithFlags.Application.persistence_spec/0
(fun_with_flags) lib/fun_with_flags/application.ex:17: FunWithFlags.Application.children/0
(fun_with_flags) lib/fun_with_flags/application.ex:10: FunWithFlags.Application.start/2
(kernel) application_master.erl:277: :application_master.start_it_old/4
** (Mix) Could not start application fun_with_flags: exited in: FunWithFlags.Application.start(:normal, [])
** (EXIT) an exception was raised:
** (UndefinedFunctionError) function FunWithFlags.Store.Persistent.Redis.worker_spec/0 is undefined (module FunWithFlags.Store.Persistent.Redis is not available)
FunWithFlags.Store.Persistent.Redis.worker_spec()
(fun_with_flags) lib/fun_with_flags/application.ex:28: FunWithFlags.Application.persistence_spec/0
(fun_with_flags) lib/fun_with_flags/application.ex:17: FunWithFlags.Application.children/0
(fun_with_flags) lib/fun_with_flags/application.ex:10: FunWithFlags.Application.start/2
(kernel) application_master.erl:277: :application_master.start_it_old/4
When I add redix
to mix.exs everything works normally. It's not terrible to add the redix library, but I would like to not, as I'm not actually using it for anything.
I set up FunWithFlags with the following config. (I'm running a low volume site on a single node, and was happy to take the performance hit to not have to set up Redis.)
config :fun_with_flags, :cache, enabled: false
config :fun_with_flags, :cache_bust_notifications, enabled: false
config :fun_with_flags, :persistence,
adapter: FunWithFlags.Store.Persistent.Ecto,
repo: Poplar.Repo
It worked absolutely fine in dev, but when I deployed to production (compiled into a release) I got the following exception on boot:
13:26:29.420 [info] Application fun_with_flags exited: exited in: FunWithFlags.Application.start(:normal, [])
** (EXIT) an exception was raised:
** (UndefinedFunctionError) function FunWithFlags.Store.Persistent.Redis.worker_spec/0 is undefined (module FunWithFlags.Store.Persistent.Redis is not available)
FunWithFlags.Store.Persistent.Redis.worker_spec()
(fun_with_flags) lib/fun_with_flags/application.ex:16: FunWithFlags.Application.children/0
(fun_with_flags) lib/fun_with_flags/application.ex:10: FunWithFlags.Application.start/2
(kernel) application_master.erl:277: :application_master.start_it_old/4
Is this a problem with my configuration, or have I hit a bug in the library? Thanks!
This project uses pubsub now, but the issue of what to do if a node joins, how to tell if the local cache is good, etc, is one of those notoriously sticky wickets.
If it used Phoenix.Tracker
, it could have stronger guarantees about whether a feature 'should be' on or off cluster-wide, without worrying as much about keeping a local ets in sync itself -- at a cost of potentially increased latency in rolling out flag changes (just because of time required to be sure of consensus compared to straight pubsub w/o consensus, not because Tracker's not performant at running tons of feature flag lookups, which it would be). But tuning the tracker ttls downwards works really well, and a set of feature flags, even per-user ones, is the sort of thing that Tracker should be good at given its biggest use case.
That stronger consistency could also be helpful for other features, like occasionally persisting the feature flags, because you'd know you're persisting something consistent, not a local cache that might be out of sync.
Hi!
I guess I have a good use case to use this lib in my application but I found the documentation just overwhelmingly wordy. Also, I could not quite comprehend what configuration I need after reading the whole README file - I still don't quite know how to configure this for my use-case. For example: Do I need to configure cache_bust_notifications
, PubSub adapters, and whatnot to just use this with Phoenix and Ecto?.
One thing that I've missed is a Getting Started section that goes about installing and configuring the library. Right now, both the configuration and installation sections are almost at the end of the README file and a lot of things seem to be explained in parallel to each other.
My suggestion is to extract the installation, configuration, and usage sections into a Get Started, struct and explain them in a little bit more focused fashion and let the miscellaneous part at the end (Web Dashboard, Origin, Caching, PubSub, etc). If possible, provide a complete installation/ configuration example for the simplest use-case, using the Ecto adapter in a Phoenix app, and then talk about using Redis and extensibility.
ΒΉBTW: Why don't you use Ecto or something like Mnesia as the default configuration for persistence? I think that there's a lot of Elixir libs that over-rely on external dependencies to do this without actually needing it. I'm not quite sure why would Redisbe necessary for something like this.
Hi - I want to use this library in our app, however because we run it in multiple different environments from a single release, all our config comes in through environment variables (we also do this to supply passwords without putting them in hardwired config).
While it's possible to provide a Redis URL using the {:system, url}
magic, it's kind of clunky trying to munge the individual bits of config (SSL, password, server etc) into another environment variable before startup, and seems like pointless effort when internally Redix is just going to split them all up again. Plus it still doesn't allow any other bits (TTL etc) to be configured dynamically.
There's generally two ways to fix this that I've seen - my preferred one is to add generalised support using Confex. That requires no changes to the config spec, but allows each individual field to be specified as {:system, :type, "ENV_VAR", default}
.
However some people don't like having an added dependency in their library, so an alternative is to allow something in the config like {:callback, Module, :function}
and have the application provide a function which returns config.
I'm happy to implement whichever of those you prefer (or some alternative) - just let me know.
Apparently ElasticCache needs a specific option set for tls to work. See https://hexdocs.pm/redix/Redix.html#module-ssl .
In a release you cannot serialize the configuration needed. I have a patch that adds a configuration option to set the socket options before connecting to Redix. Wanted to start a discussion on options before submitting a PR though.
Hello there!
Im looking for a way to get a list of enabled features by actor, is there any already supported?
Thanks!
right now, it seems like the table name is hardcoded to "fun_with_flags_toggles". It'd be nice to be able to use a different name
I was getting an error message when enabling a flag from postgrex:
** (Postgrex.Error) ERROR 23502 (not_null_violation): null value in column "id" violates not-null constraint
In my application I have Ecto configured to use UUIDs by default. You could add some logic to the Record
field to figure this out probably, although, it may be simpler to modify the migration for the table to explicitly set the primary key to an serial
type:
create table(:fun_with_flags_toggles, primary_key: false) do
add :id, :serial, primary_key: true
add :flag_name, :string
add :gate_type, :string
add :target, :string
add :enabled, :boolean
end
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.