Предисловие

На этот очевидный пост меня побудили люди, которые перешли из мира интерпретируемых языков, но так и не поняли, куда они попали.

В чём проблема-то?

А проблема в том, что elixir — язык компилируемый. Поэтому писать в config.exs выражение System.get_env — полнейшая глупость.

config :hello_project,
  some_super_configurable_value: System.get_env("SOME_SUPER_CONFIGURABLE_VALUE")

или

config :hello_project,
  some_super_configurable_value: System.get_env("SOME_SUPER_CONFIGURABLE_VALUE") || "some default value"

Вот такой код я зачастую вижу от недавних рельсоделов.

К чему это приведёт? А вот к чему.

Компилируемый конфиг

Конфиг, что характерно, тоже скомпилируется. А это значит, что код System.get_env выполнится во время компиляции. И в конфигурацию попадёт значение из окружения сборки приложения, а не исполнения.

Кулхак для обхода ситуации

Сообщество elixir-программистов нашло выход. Выход не в виде переделывания сборки языка, а в виде соглашения.

Для динамического (времени исполнения программы, а не времени компиляции) чтения переменных окружения принято писать в конфиг значение {:system, "SOME_VARIABLE_NAME"}, где SOME_VARIABLE_NAME — имя переменной окружения.

Но просто так это не сработает. Нужно, чтобы читающий код умел такие случаи обрабатывать.

Вот пример кода обработчика:

defmodule ReadConfig
  def read_config({:system, environment_variable_name}) do
    System.get_env(environment_variable_name)
  end

  def read_config(value) do
    value
  end
end

Соответственно, значение получаем в коде не так:

Application.get_env(:my_application, :my_configuration)

а так:

ReadConfig.read_config(Application.get_env(:my_application, :my_configuration))

(Приведённый код является условным и намеренно ухудшеным/упрощённым)

А теперь про библиотеки

И вот тут начинается самое интересное. Для того, чтобы это всё работало не только в вашем коде, но и в библиотечном, нужно, чтобы библиотека поддерживала такое же чтение конфига для {:system, "SOME_VAR_NAME"}. С этим, на самом деле, всё не очень хорошо.

Пример из phoenix, который мне много чего сломал:

url: [host: {:system, "HOST"}, port: {:system, "PORT"}, scheme: "http"],

А если я хочу крутить протоколом доступа из переменных окружения? А вот нельзя. Пиши руками в конфиге. Или пиши в dev/prod/test конфиги отдельно. Стыд-то какой! Поменял протокол — собирай и деплой приложение. И не важно, что https терминируется за 3-4 узла до elixir-части, а тут он только для генерации ссылок написан. Эх, Крис, зачем так-то?

Примеров полно. Как хороших библиотек, так и не очень.

Отсюда вывод такой: если будете писать свой пакет в экосистеме elixir, то, пожалуйста, пишите его с учётом динамической конфигурации всего, что вообще конфигурируется. Это спасёт многих разработчиков от истерики и костылей.

Вместо заключения

Я думал, что описанное мной выше (про компилируемость языка) является очевидным. Но по какой-то причине бывшие рельсоделы не могут это понять. А жаль.