From b9c138c19d8a937260e1a8b30ff7940031dc6790 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sat, 7 Aug 2021 21:50:08 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=A1=D0=9A=D0=BE=D0=BD=D1=84=D0=B8=D0=B3?= =?UTF-8?q?=20Elixir=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + posts/elixir_configuration.md | 144 ++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 posts/elixir_configuration.md diff --git a/README.md b/README.md index 231243b..b5ca97d 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ ## Elixir * [Elixir и паттернматчинг](/posts/elixir_patternmatching.md) +* [Пишем конфиг в Elixir приложениях](/posts/elixir_configuration.md) * [spawn процессов (Антипаттерн)](/posts/elixir_spawn_trouble.md) * [GenServer и его тестирование](/posts/elixir_genserver.md) * [Макросы и метапрограммирование (ч.1)](/posts/elixir_macroses_p1.md) diff --git a/posts/elixir_configuration.md b/posts/elixir_configuration.md new file mode 100644 index 0000000..6c261ff --- /dev/null +++ b/posts/elixir_configuration.md @@ -0,0 +1,144 @@ +# Пишем конфиг в Elixir приложениях + +В этой статье мы разберем, какой подход к конфигурированию приложения предпочтительнее выбирать, стоит ли придерживаться каких-либо стандартов в этом деле или нет + +## Как обычно делают (Способ 1) + +В большинстве случаев конфиг пишется по наитию, начинается со слова конфиг, потом дописывается какой-нибудь осмысленный атом, и третий параметр - структура с какими-либо значениями. + +Пример: +```elixir +config :my_app, :slack, + url: "https://hooks.slack.com", + webhook: System.get_env("SLACK_WEBHOOK"), + timeout: 15, + emoji: ":ghost:" +``` + +Дальше где-то в модуле это все вытаскивается таким образом: + +```elixir +Application.get_env(:my_app, :slack)[:webhook] +``` + +## Плюсы и минусы такого подхода + +Один из главных минусов - нет привязки к модулю, в итоге нет точного понимания, какой модуль использует данный конфиг. + +Второй минус вытекает из первого - мы не знаем, сколько модулей использует данный конфиг. Возможно конфиг используется несколькими модулями, это может быть плюсом, но только в случае, если модули используют параметры одинаково. + +Проблемы начнутся, например, когда обоим модулям нужен параметр `url`, но одному он нужен со схемой `(http, https)`, а другому нет. + +---- + +## Как можно делать (Способ 2) + +Думаю, уже по предыдущим абзацам стало ясно, к чему я веду. Вместо атома можно использовать название модуля. + +Пример: +```elixir +config :my_app, MyApp.Integrations.SlackNotification, + url: "https://hooks.slack.com", + webhook: System.get_env("SLACK_WEBHOOK"), + timeout: 15, + emoji: ":ghost:" +``` + +Теперь мы знаем, что конфиг ссылается на модуль `MyApp.Integrations.SlackNotification`, можем сделать вывод, что конфиг используется в уведомлениях и знаем, в каком конкретно модуле. + +Если мы придерживаемся такого правила при написании конфига, то в дальнейшем мы можем облегчить себе работу с конфигом, получать его более удобным способом. + +Для этого на уровне приложения нужно объявить **следующий макрос**: +```elixir +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]` мы можем написать: + +```elixir +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 (например в другой workspace slack'а), при реализации конфига **первым способом** с большой вероятностью наш конфиг превратился бы в такой: + +```elixir +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:" +``` + +**Второй способ** добавил немного строгости и в **худшем случае** конфиг будет следующий: + +```elixir +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:" +``` + +**В лучшем**, программист разобьет модуль интеграции на несколько составляющих, чтобы избежать дублирования конфига: + +```elixir +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 | +| ----------| ------------- | ------------- | +| Переиспользование конфига в разных модулях | ✅ | ❌ | +| Побуждает к разбиению модулей | ❌ | ✅ | +| Понимание, где используется конфиг | ❌ | ✅ | +| Нет лишних абстракций | ✅ | ❌ | From d7b19875c1602469cb4655569a605ea2295f3c74 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 8 Aug 2021 00:31:25 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=82=D0=B5=D0=BA=D1=81=D1=82=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=B9=20=D1=87=D0=B0=D1=81=D1=82=D0=B8,=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D1=84=D1=80=D0=B0=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B8=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1?= =?UTF-8?q?=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/elixir_configuration.md | 43 ++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/posts/elixir_configuration.md b/posts/elixir_configuration.md index 6c261ff..8fe5054 100644 --- a/posts/elixir_configuration.md +++ b/posts/elixir_configuration.md @@ -1,10 +1,10 @@ # Пишем конфиг в Elixir приложениях -В этой статье мы разберем, какой подход к конфигурированию приложения предпочтительнее выбирать, стоит ли придерживаться каких-либо стандартов в этом деле или нет +В этой статье мы разберем, какой подход к конфигурированию приложения предпочтительнее выбирать, стоит ли придерживаться каких-либо стандартов в этом деле или нет. ## Как обычно делают (Способ 1) -В большинстве случаев конфиг пишется по наитию, начинается со слова конфиг, потом дописывается какой-нибудь осмысленный атом, и третий параметр - структура с какими-либо значениями. +В большинстве случаев конфиг пишется чисто интуитивно: начинается со слова config, затем дописывается какой-либо осмысленный атом, а после структура со значениями. Пример: ```elixir @@ -15,7 +15,7 @@ config :my_app, :slack, emoji: ":ghost:" ``` -Дальше где-то в модуле это все вытаскивается таким образом: +Дальше где-то в **модуле с кодом** это все вытаскивается таким образом: ```elixir Application.get_env(:my_app, :slack)[:webhook] @@ -23,17 +23,17 @@ Application.get_env(:my_app, :slack)[:webhook] ## Плюсы и минусы такого подхода -Один из главных минусов - нет привязки к модулю, в итоге нет точного понимания, какой модуль использует данный конфиг. +Один из главных минусов - отсутствует привязка к модулю, в итоге нет точного понимания, какой модуль использует данный конфиг. -Второй минус вытекает из первого - мы не знаем, сколько модулей использует данный конфиг. Возможно конфиг используется несколькими модулями, это может быть плюсом, но только в случае, если модули используют параметры одинаково. +Второй минус вытекает из первого - мы не знаем, **сколько** модулей использует данный конфиг. Возможно он используется несколькими модулями, это может быть плюсом, но только в том случае, если модули используют параметры одинаково. -Проблемы начнутся, например, когда обоим модулям нужен параметр `url`, но одному он нужен со схемой `(http, https)`, а другому нет. +Проблемы начнутся, например, когда этим модулям нужен параметр `url`, но одним он нужен со схемой `(http, https)`, а другим без. ---- ## Как можно делать (Способ 2) -Думаю, уже по предыдущим абзацам стало ясно, к чему я веду. Вместо атома можно использовать название модуля. +Думаю, уже по предыдущим абзацам стало ясно к чему я веду. Вместо атома можно использовать название модуля. Пример: ```elixir @@ -44,9 +44,9 @@ config :my_app, MyApp.Integrations.SlackNotification, emoji: ":ghost:" ``` -Теперь мы знаем, что конфиг ссылается на модуль `MyApp.Integrations.SlackNotification`, можем сделать вывод, что конфиг используется в уведомлениях и знаем, в каком конкретно модуле. +Теперь мы видим, что конфиг ссылается на модуль `MyApp.Integrations.SlackNotification`, можем сделать вывод, что он используется в уведомлениях и знаем, в каком конкретно модуле. -Если мы придерживаемся такого правила при написании конфига, то в дальнейшем мы можем облегчить себе работу с конфигом, получать его более удобным способом. +Если придерживаться такого правила при написании конфига, то в дальнейшем можно облегчить себе работу с ним, получать его более удобным способом. Для этого на уровне приложения нужно объявить **следующий макрос**: ```elixir @@ -59,7 +59,7 @@ defmodule MyApp do end ``` -Теперь в модуле `MyApp.Integrations.SlackNotification` вместо `Application.get_env(:my_app, :slack)[:webhook]` мы можем написать: +Теперь в модуле `MyApp.Integrations.SlackNotification` вместо `Application.get_env(:my_app, :slack)[:webhook]` можно написать: ```elixir defmodule MyApp.Integrations.SlackNotification do @@ -74,18 +74,19 @@ defmodule MyApp.Integrations.SlackNotification do end ``` -Получается, мы добавили немного абстракции, которая позволяет стандартизировать работу с конфигом и как следствие - упросить работу по его получению. +Получается, мы добавили немного абстракции, которая позволяет стандартизировать работу с конфигом и, как следствие, упростить его получение. ## Плюсы и минусы такого подхода У данного подхода есть пара минусов: мы добавили лишнюю абстракцию и лишили себя возможности использовать один и тот же конфиг в нескольких модулях. -Но на самом деле из минусов вытекают плюсы: +Но, на самом деле, из этих минусов вытекают плюсы: - Теперь мы знаем, в каком модуле конфиг используется -- Если конфиг пытается перетечь в несколько модулей, то возможно, стоит пересмотреть архитектуру этих модулей и выделить какой-нибудь главный модуль, который будет хранить основной конфиг. +- Если конфиг пытается перетечь в несколько модулей, то, возможно, стоит пересмотреть их архитектуру и выделить **общее** в отдельный модуль, который будет хранить основной конфиг. -Например, у нас появилась необходимость отправлять уведомления с помощью другого вебхука или с другой emoji (например в другой workspace slack'а), при реализации конфига **первым способом** с большой вероятностью наш конфиг превратился бы в такой: +Например, у нас появилась необходимость отправлять уведомления в другой чат с соответствующим emoji. Хорошим тоном будет использовать для каждого из них отдельный webhook. +При реализации конфига **первым способом** с большой вероятностью он превратился бы в такой: ```elixir config :my_app, :slack, @@ -97,7 +98,7 @@ config :my_app, :slack, emoji_2: ":beer:" ``` -**Второй способ** добавил немного строгости и в **худшем случае** конфиг будет следующий: +**Второй способ** добавил немного строгости и, в **худшем случае**, конфиг будет следующий: ```elixir config :my_app, MyApp.Integrations.SlackNotification1, @@ -132,13 +133,13 @@ config :my_app, MyApp.Integrations.SlackNotification2, # Выводы -Для подведения итогов я составил таблицу, с указанием, какие возможности дает тот или иной подход. +Для подведения итогов я составил таблицу с указанием того, какие возможности дает тот или иной подход. -Я, однозначно, выбираю и использую в своих проектах второй способ, потому что он добавляет прозрачность и стандартизирует подход. +Я однозначно выбираю и использую в своих проектах второй способ, так как он добавляет прозрачность и стандартизирует подход. | - | Способ 1 | Способ 2 | | ----------| ------------- | ------------- | -| Переиспользование конфига в разных модулях | ✅ | ❌ | -| Побуждает к разбиению модулей | ❌ | ✅ | -| Понимание, где используется конфиг | ❌ | ✅ | -| Нет лишних абстракций | ✅ | ❌ | +| Прозрачность применения конфига | ❌ | ✅ | +| Повторное использование конфига в разных модулях | ✅ | ❌ | +| Побуждение к структуризации модулей | ❌ | ✅ | +| Отсутствие лишних абстракций | ✅ | ❌ |