Использование модуля Registry в Elixir 1.4

Одно из нововведений в Elixir 1.4, которого многие с нетерпением ждали, — это модуль Registry. Из этой статьи вы узнаете, как можно извлечь пользу из этого замечательного модуля, который теперь является частью стандартной библиотеки.

Если вы только приступили к изучению Elixir и принципов OTP, то количество идентификаторов процессов (PID) наверняка покажется вам неисчислимым, особенно при использовании REPL (iex). Для модели акторов сотни или даже тысячи выполняемых в системе процессов — обычное дело. Один из способов связаться с этими процессами — отслеживать их при запуске по PID или заданным именам. Модуль Registry предлагает другой способ сделать это без использования внешних библиотек.

Предположим, вы занимаетесь продажей виджетов своим клиентам (далее — пользователям). Перед вами стоит задача разработать интерфейс пользователя (UI) для системы реального времени, чтобы он отображал учётные записи тех, кто разместил заказ в текущий день, и основную информацию по каждой учётной записи. Если пользователь не заказывал ничего в течение 24 часов, то его учётная запись удалялась бы из списка в UI.

Одно из быстрых решений — создать процесс сразу после того, как пользователь разместит заказ и реализовать функцию, которая по прошествии 24 часов удаляла бы процессы, не задействованные в новых заказах. Но как в таком случае понять, с какой учётной записью связан тот или иной процесс? С этой задачей отлично справляется модуль Registry, работающий практически как DNS-поиск по имени, а в нашем случае по account_id, и предоставляющий ссылку на исполняемый процесс.

Elixir Registry Реестр будет своего рода адресной книгой для процессов (изображение © 2010 by Tomasz Sienicki)

Прежде всего нужно будет запустить реестр и присвоить ему имя с помощью функции Registry.start_link. Далее просто добавим нужный реестр к дереву супервизоров приложения. Обратите внимание на то, что мы создаём реестр :unique.

children = [
  supervisor(Registry, [:unique, :account_process_registry])
]

Теперь у вас есть свой реестр под названием :account_process_registry, который будет отслеживать идентификаторы процессов. Остаётся только реализовать функцию, которая будет возвращать кортеж :via в соответствии с переданным ей значением. GenServer упрощает работу с кортежем :via и обеспечивает автоматическую привязку имени (account_id) к процессу.

defmodule Account do
  use GenServer

  def start_link(account_id) do
    name = via_tuple(account_id)
    GenServer.start_link(__MODULE__, [account_id], name: name)
  end

  defp via_tuple(account_id) do
    {:via, Registry, {:account_process_registry, account_id}}
  end

  def order_placed(account_id) do
    GenServer.call(via_tuple(account_id), :order_placed)
  end

  # genserver callbacks not shown for simplicity

end

Функция via_tuple вместе с модулем Registry позволяют создавать процесс для заданного account_id и осуществлять последующие вызовы процесса по имени, а не по PID. И больше не придётся иметь дело с «загадочными» PID или разрабатывать собственную систему назначения имён внутри функции start_link. Чтобы найти процесс среди тысяч других процессов, достаточно будет всего лишь иметь его account_id.

iex> Account.start_link(5)
{:ok, #PID<0.114.0>}

iex> Account.start_link(20)
{:ok, #PID<0.118.0>}

iex> Account.order_placed(20)
1

А что если снова запустить процесс для account_id № 5?

iex> Account.start_link(5)
{:error, {:already_started, #PID<0.114.0>}}

Реестр напомнит нам, что процесс для account_id № 5 уже отслеживается, о чём будет сигнализировать сообщение :already_started.

Неплохое начало. Теперь можно программно создавать нужный процесс с именем или идентификатором, не опасаясь того, что процесс для данного идентификатора уже существует: система об этом предупредит. Ещё одно преимущество в том, что, если возникнет ошибка и процесс перезапустится по какой-либо причине, система зарегистрирует новый PID для исходного имени. Стандартная ссылка на определённый PID так бы не сработала.

Что же делать дальше? А дальше останется лишь осуществить вызовы в GenServer и обновить состояние. Возможность идентификации процессов программно на Elixir — отличное решение, позволяющее разработчику больше времени уделить бизнес-логике. Конечно, чтобы передать данные в интерфейс, всё равно придётся реализовать веб-сокет, но сегодня речь не об этом. (Подсказка: рекомендую для создания простых сокетов использовать cowboy).

Если покопаться в примере на Github, то в модуле RegistrySample.AccountSupervisor можно отыскать несколько полезных функций для взаимодействия с реестром. Функции вроде find_or_create_process/1 можно использовать, чтобы облегчить работу с процессами.

Можете смело адаптировать этот код для своих целей и продолжать изучать возможности Registry!

© 2020 / Россия Любые мысли и вопросы пишите на elixir@wunsh.ru.