Another idea, which might make the difference between compile-time configuration vs run-time(/on-boot) configuration more clear to the user but only in the instances they have to care about it is to:
- Introduce
Application.Config
which has the same behaviour asMix.Config
but lives in:elixir
instead of:mix
so it is available in Releases. (as in original proposal) - Choose which configuration files (and the order they should have) in the
mix.exs
file by using a:config_paths
key.(as in original proposal) -
Expose whether we want to look at the application’s
mix.exs
information for compile-mode or for run-time/boot-time-mode so that we can in e.g. the implementation of themix.exs
'sproject()
fields (most importantly::config_paths
) potentially choose between different groups of configuration files. This is very similar to how different Mix environments are currently used to e.g. switch between what dependencies you include. - Using
import_config
just like you do today (including string-replacements) is still allowed, but we might decide to deprecate this feature, and definitely show warnings (or potentially hard errors) when the configuration file that was linked to can not currently be found because its string replacement was ‘too smart’, and point the user towards the new way to configure their application using a conditionalconfig_paths:
list.
I think that when we would set it up like this, the abstraction is the least leaky.
Example usage:
- A user that starts an Elixir project does not need to care about the difference between compile-time and run-time because exactly the same configuration is read by default (because the value for
:config_paths
is constant) - If they start using Releases and find out that they want to perform some configuration differently at boot-time, they can change
:config_paths
. - If they try to use configuration that is ‘too smart’ to work on boot, we can in most cases generate a warning during compilation-time (and otherwise a hard error during boot time) that should tell the user to rewrite the configuration using a conditional
:config_paths
setup.
In the simplest case, it would looke like:
def project do
[
...
config_paths: ~w(config/config.exs config/#{Mix.env()}.exs)
...
]
end
Expanded to do different things based on compile-time/boot-time mode:
def project do
[
...
config_paths: ~w{config/base_config.exs} ++ config_paths(Mix.env(), Mix.configuration_target())
...
]
end
def config_paths(_, :compile_time), do: ~w{config/compile_time_overrides.exs}
def config_paths(:dev, :boot_time) do: ~w{config/production_boot_overrides.exs config/production_secrets.exs}
def config_paths(:prod, :boot_time) do: ~w{config/dev_boot_overrides.exs}
This proposed solution is backwards compatible, because:
- switching in an existing project from
Mix.Config
toApplication.Config
is optional (although we probably should makeApplication.Config
the new default for new projects). -
Mix.Config
will just like now work as it always did: Releases only can use it during compiletime, applications running through mix use it at compiletime + boot-time. - When using
Application.Config
, we look at the:config_paths
field in themix.exs
file. This field (and potentially other fields of theproject()
function) might return different results based on the currentMix.configuration_target()
(Maybe we can find a better name forconfiguration_target
?), which will allow Releases to know what files to include for boot-time, and will allow all projects to only expose the correct configuration in the correct configuration environment. -
Application.Config
will complain ifimport_config
is encountered inside a configuration file that is flagged for inclusion into the list of boot-time configuration.Mix.Config
will do no such thing and will happily ‘just not work’ on boot-time for a Release.