Skip to content

Latest commit

 

History

History
145 lines (107 loc) · 7.29 KB

elixir_configuration.md

File metadata and controls

145 lines (107 loc) · 7.29 KB

Пишем конфиг в Elixir приложениях

В этой статье мы разберем, какой подход к конфигурированию приложения предпочтительнее выбирать, стоит ли придерживаться каких-либо стандартов в этом деле или нет.

Как обычно делают (Способ 1)

В большинстве случаев конфиг пишется чисто интуитивно: начинается со слова config, затем дописывается какой-либо осмысленный атом, а после структура со значениями.

Пример:

config :my_app, :slack,
  url: "https://hooks.slack.com",
  webhook: System.get_env("SLACK_WEBHOOK"),
  timeout: 15,
  emoji: ":ghost:"

Дальше где-то в модуле с кодом это все вытаскивается таким образом:

Application.get_env(:my_app, :slack)[:webhook]

Плюсы и минусы такого подхода

Один из главных минусов - отсутствует привязка к модулю, в итоге нет точного понимания, какой модуль использует данный конфиг.

Второй минус вытекает из первого - мы не знаем, сколько модулей использует данный конфиг. Возможно он используется несколькими модулями, это может быть плюсом, но только в том случае, если модули используют параметры одинаково.

Проблемы начнутся, например, когда этим модулям нужен параметр url, но одним он нужен со схемой (http, https), а другим без.


Как можно делать (Способ 2)

Думаю, уже по предыдущим абзацам стало ясно к чему я веду. Вместо атома можно использовать название модуля.

Пример:

config :my_app, MyApp.Integrations.SlackNotification,
  url: "https://hooks.slack.com",
  webhook: System.get_env("SLACK_WEBHOOK"),
  timeout: 15,
  emoji: ":ghost:"

Теперь мы видим, что конфиг ссылается на модуль MyApp.Integrations.SlackNotification, можем сделать вывод, что он используется в уведомлениях и знаем, в каком конкретно модуле.

Если придерживаться такого правила при написании конфига, то в дальнейшем можно облегчить себе работу с ним, получать его более удобным способом.

Для этого на уровне приложения нужно объявить следующий макрос:

defmodule MyApp do
  defmacro get_module_config(key) do
    quote do
      Application.get_env(:my_app, __MODULE__)[unquote(key)]
    end
  end
end

Теперь в модуле MyApp.Integrations.SlackNotification вместо Application.get_env(:my_app, :slack)[:webhook] можно написать:

defmodule MyApp.Integrations.SlackNotification do
  require MyApp
  ...

  def send(text) do
    url = MyApp.get_module_config(:url)
    timeout = MyApp.get_module_config(:timeout)
    ...
  end
end

Получается, мы добавили немного абстракции, которая позволяет стандартизировать работу с конфигом и, как следствие, упростить его получение.

Плюсы и минусы такого подхода

У данного подхода есть пара минусов: мы добавили лишнюю абстракцию и лишили себя возможности использовать один и тот же конфиг в нескольких модулях.

Но, на самом деле, из этих минусов вытекают плюсы:

  • Теперь мы знаем, в каком модуле конфиг используется
  • Если конфиг пытается перетечь в несколько модулей, то, возможно, стоит пересмотреть их архитектуру и выделить общее в отдельный модуль, который будет хранить основной конфиг.

Например, у нас появилась необходимость отправлять уведомления в другой чат с соответствующим emoji. Хорошим тоном будет использовать для каждого из них отдельный webhook. При реализации конфига первым способом с большой вероятностью он превратился бы в такой:

config :my_app, :slack,
  url: "https://hooks.slack.com",
  timeout: 15,
  webhook_1: System.get_env("SLACK_WEBHOOK_1"),
  emoji_1: ":ghost:"
  webhook_2: System.get_env("SLACK_WEBHOOK_2"),
  emoji_2: ":beer:"

Второй способ добавил немного строгости и, в худшем случае, конфиг будет следующий:

config :my_app, MyApp.Integrations.SlackNotification1,
  url: "https://hooks.slack.com",
  webhook: System.get_env("SLACK_WEBHOOK_1"),
  timeout: 15,
  emoji: ":ghost:"

config :my_app, MyApp.Integrations.SlackNotification2,
  url: "https://hooks.slack.com",
  webhook: System.get_env("SLACK_WEBHOOK_2"),
  timeout: 15,
  emoji: ":beer:"

В лучшем, программист разобьет модуль интеграции на несколько составляющих, чтобы избежать дублирования конфига:

config :my_app, MyApp.Integrations.SlackNotificationBase,
  timeout: 15, 
  url: "https://hooks.slack.com"

config :my_app, MyApp.Integrations.SlackNotification1,
  webhook: System.get_env("SLACK_WEBHOOK_1"),
  emoji: ":ghost:"

config :my_app, MyApp.Integrations.SlackNotification2,
  webhook: System.get_env("SLACK_WEBHOOK_2"),
  emoji: ":beer:"

Выводы

Для подведения итогов я составил таблицу с указанием того, какие возможности дает тот или иной подход.

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

- Способ 1 Способ 2
Прозрачность применения конфига
Повторное использование конфига в разных модулях
Побуждение к структуризации модулей
Отсутствие лишних абстракций