Mix config evolutions

Can you explain a bit more? As far as I can tell, Ecto is configured using Mix.Config and a config.exs file.

From the Ecto documentation (2.1.4):

Where the configuration for the Repo must be in your application environment, usually defined in your config/config.exs

It says “usually”, but are there other options? If so, the documentation doesn’t mention them.

How can I configure my repo without using a config.exs file?

Thanks.

The changes happen on two different levels.

Internally: it means that Ecto will internally expect configurations to be set at runtime rather than compile time.

Externally: it means that Ecto will provide a proper API to do runtime configuration. The only way to do runtime configuration in Ecto 2.0 was by using url: {:system, "DATABASE_URL"} which has two big problems: it only works for the :url parameter and it only allows the configuration to be read from the system environment. If you need to read it from elsewhere, you are out of luck.

Ecto 2.1 now provides the init/2 callback inside your repo for runtime configuration:

def init(_, config) do
  {:ok, Keyword.put(config, :url, System.get_env("DATABASE_URL"))}
end

Now you have the flexibility to do whatever you want. Also note Ecto allows the configuration to be given at the moment you call start_link on the Repo.

5 Likes

Hi, I have a few questions on the direction that’s been mentioned above.

The two problems I’ve found so far with {:system, "MY_VAR"} are that:

  • it’s not standard and requires custom code (that people are extracting in libraries)
  • Reading with System.get_env("MY_VAR") is slower than reading from the Application env (but benchmarks show that caching it in the wrapper speeds thing up).

Both problems could be solved by adding a layer between the Elixir API and the underlying Erlang calls.

So I guess that a different way to frame the problem is that we have a centralized configuration API that does not currently support runtime configuration, which is a common requirement, as opposed to saying that “runtime configuration should be moved to runtime”.

I can see how this would work when starting supervisors and processes. If a dependency requires configuration (e.g. API tokens, URLs, flags, etc), they can be read from the ENV and passed to the supervision tree, then something will either store the config in a GenServer’s state or a ETS table, depending on how frequently it needs to be accessed. (please correct me if I’m wrong)
I am not sure how it would work for libraries that don’t start processes though.

init runtime configuration only works for supervised dependencies, it will not work for things like:

  plug Plug.Session,
    signing_salt: "how to manage this setting"

Maybe applications could accept a module instead of values (or both) ?

  plug Plug.Session,
    handler: MyApp.MyConfigHandler
defmodule MyApp.MyConfigHandler do
  # use MyApp.CompileTimeSettings
  def setting({:plug, :session, :cookie, :signing_salt}) do
    # - "very secret signing salt"
    # - System.get_env("COOKIE_SIGNING_SALT")
    # - MyExternalServiceAPI.get(:signing_salt)
    # - or whatever
  end

  def setting(tuple) do
    IO.inspect tuple, label: "Show me available settings"
    nil
  end
end

I would be very happy to be able to configure my dependencies this way.