What is the cleanest way to load and parse en vars during both configuration and execution?

Hi everybody,

First, I am very new to Elixir and to this forum. Please forgive me if my english is bad or if I transgress any rule :upside_down_face:

I need to find a clean way to load environment variables during both configuration and execution phases, to configure the app depending on the environment (prod/dev/etc).

My new team already uses an imperfect solution :
At runtime, we dynamically load environment variables depending on the environment the app is running, thanks to an in-house helper (env.ex) that fetch the env var and parse it to verify its format.
As some of this vars are required on configuration phase, the module is compiled a first time from config.exs, and some variable are loaded later from the config.exs file (second parameter is a default value):

Code.compile_file("lib/our_app/utils/env.ex")
[...]
secondapp_server_url: Env.load("SECOND_APP_URL", "http://localhost:3001")

This solution is working, but the env.ex module is compiled a second time later (during the app compilation), resulting in a warning (double compilation) that we want to get rid of.
Also, they brainstormed before my arriving and concluded that this module has been very useful to them, so they’d like to keep it.

First, we thought that we simply had to put our env.ex in its own new hex package and add it as a dependency to our app. Doing so, I discovered by running mix deps.get that the configs.exs is parsed before deps are fetched and compiled. Thus, the mix deps.get command is crashing:

$ mix deps.get
** (UndefinedFunctionError) function Env.load/2 is undefined (module Env is not available)
    Env.load("SECOND_APP_URL", "http://localhost:3001")
    (stdlib 3.17) erl_eval.erl:685: :erl_eval.do_apply/6
    (stdlib 3.17) erl_eval.erl:893: :erl_eval.expr_list/6
    (stdlib 3.17) erl_eval.erl:237: :erl_eval.expr/5
    (stdlib 3.17) erl_eval.erl:229: :erl_eval.expr/5
    (stdlib 3.17) erl_eval.erl:230: :erl_eval.expr/5
    (stdlib 3.17) erl_eval.erl:893: :erl_eval.expr_list/6
    (stdlib 3.17) erl_eval.erl:408: :erl_eval.expr/5

I can’t find the best way to keep this module for both configuration and execution purpose (I believe transforming it in a .exs file would not make it), or which better practices I should follow to replace it.
So any help would be appreciated !
Thank you !

Why to you need this Env module? You can use System.env within config.exs or runtime.exs.

Tooting my own horn here, but I’ve written a blog post that describes a system of configuration that I personally use, where you have the same configuration in development and production: Simple Configuration Setup for Elixir Projects (v1.11+) – Random Notes – Reading it may provide you with some new thoughts.

But to summarise, you can probably move most of your config to config/runtime.exs like LostKobrakai said. This file is evaluated both in development and production when your system starts up. In that file you can also use your Env module without any issues, as it has already been compiled at that point (just like I use my own env helpers in my blog post). The rest of the “classic” config/*.exs files are for compile time configuration, which is not needed for most things you want to configure.

If you need environment variables in compile time configuration for some reason, then what you are doing now (with the double-compilation warning) is probably the best you can do.

3 Likes

the goal of this Env module is to simplify developers life and reduce risks of errors later, by assuring the validity of the environment variable
as Nicd says, runtime.exs is a good suggestion as we are able to move most our calls to Env in runtime.exs
thank you !

Indeed, as you said we are able to move almost all variables to config/runtime.exs, and keep usage of our Env modules for them. Even without considering Env, I find it cleaner to move them there.
Only one last compilation option remains, at least for now. We will use System.get_env for this one, and time will tell us if we need to go back to double-compilation warning or consider something else.

Thank you very much for your answer, and thank you also for your blog posts.

I also wrote a utility which does casting, validation and inspection of ENV variables according to the predefined schema. You may be interested in some ideas or just in using the library :slight_smile: GitHub

1 Like