sasajuric
Rethinking app env
This is a spin-off from the discussion about the new config proposal. I’m replying to this post by @michalmuskala separately, to avoid noise in that thread.
Currently, it seems to be a bucket for all sorts of things, which includes system configuration. I definitely agree that it’s not a good place for operator configuration.
This sounds vague, could you elaborate what kind of loose coupling do we achieve by e.g. having pubsub: [name: UI.PubSub, adapter: Phoenix.PubSub.PG2] in config?
What is that “everything else”? What’s the criteria for defining a parameter in the config, and not in the code? For example, why should a pubsub name and adapter go to config, but e.g. supervisor name and restart intensity be in the code?
Most Liked
bitwalker
I think a great deal of the abuse of the application env occurs because it is simply easy to do so. It is more difficult to think about how to make your library or application configurable by feeding in parameters to a top-level supervisor or something, than it is to just stuff anything configurable into config.exs and use Application.get_env/3. As long as the easier path is available, there will always be people who take it. Erlang didn’t have quite as much of an issue with this, because it was more of a pain to use the application env for configuration than it was to configure via parameters.
I think a related issue is compile-time configuration. Some of the examples José mentions need configuration inputs at compile-time, and there is no other means to provide them except config.exs, since Mix evaluates it during compilation. Had Mix gone the route of Erlang in regards to application env, the whole discussion would be moot, since config.exs would be limited to runtime configuration. Since it did not, we also have the problem where config.exs has become the catch-all for anything configurable. People are using System.cmd/3 to get a git shorthash in config.exs, rather than doing something like -Dcommit_hash=$(git describe --tags --long) as a flag to the compiler, which is how such things are typically handled in just about every other language I’ve worked with. That isn’t suitable for everything though, and is unwieldy for repeated use. This is definitely a place where config.exs eases a lot of pain - compile-time parameters and global defaults.
I was hoping that by treating config.exs as a runtime config, and having mix.exs handle defining what to include or not include at compile-time, we could get the best of both worlds. It doesn’t really solve the issue of how to prevent people from abusing the app env, but I’m not sure that is something we can really fix at this point, about the only time you could make that kind of change is with a theoretical Elixir 2.0, but its not clear how you could do that without making configuration more inconvenient in general. It seems like a bit of a catch-22 to me.
sasajuric
I think that this cuts to one of the root causes why config scripts are used so much.
So to be clear, my opinion is that config scripts are way overused, and contain a lot of stuff that doesn’t belong there. I consider them a pile of bloat arbitrarily thrown together, and I think that they often make the code more difficult to understand.
IMO, there are a couple of reasons why so much data ends up in config scripts:
- Some of our flagship libraries (Phoenix and Ecto) promote it.
- Some libraries require it.
- There’s no obvious or convenient way to specify variations between different mix envs.
To be completely honest, at my company we also overuse config scripts, precisely for the reasons stated above. So for example, even though we’re aware of if Mix.env() and how/when we can use it, if a constant varies between different envs, we usually just stuff it to config script.
I further believe that this convenience subtly creates a tendency to put even more data into config scripts. So for example, my colleague recently argued that some MFAs belong to config scripts, because they “feel” like config, even though MFA is clearly code, not config. This was for me the direct motivation to write that lengthy post about config scripts, and to actively start questioning them.
To be clear, I used them myself a lot, even though I was never quite comfortable with them, which is why I’m increasingly starting to feel that they are misleading people and causing problems much more then they actually help because:
- Config scripts are not runtime friendly, and cause confusion when used with OTP releases.
- Parameters are not consolidate anymore. For example, endpoint parameters are now specified in at least four config files.
- A bunch of unrelated data is thrown together.
Therefore, I feel that instead of adding the additional complex machinery to address the issue number 1 (which is just one issue of config scripts), it would be much better if runtime configuration through regular code was promoted and assisted.
In particular, I’d like to see:
- Official helper macros to simplify expressing small variations between mix envs in regular code. Perhaps something along the lines of this macro, or something different/better.
mix phx.new, Ecto/Phoenix docs, Elixir docs, and official getting started guides favouring runtime configuration over config scripts.
This will not solve all the problems, but I believe it will solve most of them, and that it will guide the community to write their code and libraries in a better way.
Now, some members from Elixir and Phoenix core team have mentioned that many people were further confused when init/2 callbacks were introduced. Personally, I don’t at all buy that this means that runtime config is confusing. The thing is that prior to init/2 a typical Elixir project had at least four files where parameters were specified (config, dev, test, and prod.exs). And then the fifth one was added. No wonder that people found this even more confusing.
But until we guide people to provide their parameters as much as possible in the regular code, it’s unfair to say that runtime config is confusing.
I should also state that I was not a fan of init/2 when it was proposed. Personally, I felt that endpoint and repo should just take their parameters as argument to start_link and child_spec/1. This has issues with hot code reloading, but that’s an advanced scenario anyway. My impression is that with init/2, Elixir/Ecto/Phoenix team decided to make the runtime configuration more complex to simplify advanced (and arguably infrequent) scenarios at the expense of more complex interface for everyone. My feeling is that this was not a good tradeoff. I suspect that the callback style interface of init/2 also adds to the confusion people have.
In summary, I think that Elixir/Phoenix/Ecto team historically favoured config scripts way more than runtime configs in regular code, and that’s why we’re here. Perhaps, instead of adding more complex machinery to config scripts, a better way would be to assist and promote runtime configuration through regular code and plain old passing of arguments to functions. I feel that this requires much less interventions in the language and that it can take us very far, although it will admittedly take time to move the community to such style of configuration.
sasajuric
Yes, as the title says, this thread is about app env and it’s closely related cousins config scripts.
Just to be clear, I don’t deny that there’s a thing called system configuration. I just think that app env and config scripts are in most cases an inferior mechanism for managing the system configuration. Moreover, I think that the defaults promoted by e.g. phx.new are arbitrary, confusing, and at the same time limiting.
In my impression, a bunch of stuff ends up in config scripts for dubious reasons, and they don’t even help with actual system configuration (assuming you want to run OTP releases). I’ve been personally confused by config scripts, the team I’m a part of has been confused by config scripts, I’ve seen other people being confused by them, and a lot of libraries promoting them for no particular reasons.
I think we need to strongly challenge this approach, and reevaluate do we really need to promote config scripts and app env so much. I don’t say that they are useless, but I think that in most cases they are not the most appropriate choice. I think that we should instead promote regular code as much as possible, and leave the special code for the few cases where it’s really needed.
Popular in Discussions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









