#elixir #umbrella
#elixir #umbrella
Вопрос:
Предыстория
У меня есть приложение umbrella, в котором много приложений меньшего размера. Одно из этих приложений, called A
, должно иметь возможность запускать и контролировать другое приложение called B
.
B
будучи самостоятельным приложением, предоставляет общедоступный API и имеет GenServer, отвечающий за получение запросов, которые затем перенаправляются на логические модули и тому подобное.
Проблема
Итак, у меня есть два требования:
- Я должен иметь возможность запускать
B
независимо и работать как обычное автономное приложение. A
должен иметь возможность иметьB
в своих дочерних элементах и перезапускать / управлять им, если возникнет такая необходимость.
Проблема, с которой я сталкиваюсь здесь, заключается в том, что с моим кодом я могу достичь либо 1, либо 2, но не обоих.
Код
Итак, ниже приведен важный код для приложения B
:
application.ex
defmodule B.Application do
@moduledoc false
use Application
alias B.Server
alias Plug.Cowboy
@test_port 8082
@spec start(any, nil | maybe_improper_list | map) :: {:error, any} | {:ok, pid}
def start(_type, args) do
# B.Server is a module containing GenServer logic and callbacks
children = children([Server])
opts = [strategy: :one_for_one, name: B.Supervisor]
Supervisor.start_link(children, opts)
end
end
server.ex (упрощенный)
defmodule B.Server do
use GenServer
alias B.HTTPClient
#############
# Callbacks #
#############
@spec start_link(any) :: :ignore | {:error, any} | {:ok, pid}
def start_link(_args), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)
@impl GenServer
@spec init(nil) :: {:ok, %{}}
def init(nil), do: {:ok, %{}}
@impl GenServer
def handle_call({:place_order, order}, _from, _state), do:
{:reply, HTTPClient.place_order(order), %{}}
@impl GenServer
def handle_call({:delete_order, order_id}, _from, _state), do:
{:reply, HTTPClient.delete_order(order_id), %{}}
@impl GenServer
def handle_call({:get_all_orders, item_name}, _from, _state), do:
{:reply, HTTPClient.get_all_orders(item_name), %{}}
##############
# Public API #
##############
def get_all_orders(item_name), do:
GenServer.call(__MODULE__, {:get_all_orders, item_name})
def place_order(order), do:
GenServer.call(__MODULE__, {:place_order, order})
def delete_order(order_id), do:
GenServer.call(__MODULE__, {:delete_order, order_id})
end
И вот точка входа B
b.пример
defmodule B do
@moduledoc """
Port for http client.
"""
alias B.Server
defdelegate place_order(order), to: Server
defdelegate delete_order(order_id), to: Server
defdelegate get_all_orders(item_name), to: Server
@doc false
defdelegate child_spec(args), to: Server
end
b.ex
по сути, это фасад для сервера с некоторой дополнительной контекстной информацией, такой как спецификации, определения типов и т. Д. (Здесь опущено для краткости).
Как A
управлять жизненным циклом?
Насколько я понимаю, деревья наблюдения указаны в application.ex
файле приложений. Итак, насколько я понимаю, я создал этот файл приложения для A
:
defmodule A.Application do
@moduledoc false
use Application
alias B
def start(_type, _args) do
children = [B]
opts = [strategy: :one_for_one, name: A.Supervisor]
Supervisor.start_link(children, opts)
end
end
Который должен работать, за исключением того, что это не так.
Когда внутри A
папки, если я запускаю iex -S mix
, вместо хорошего запуска я получаю следующую ошибку:
** (Mix) Could not start application a: A.Application.start(:normal, []) returned an error: shutdown: failed to start child: B.Server
** (EXIT) already started: #PID<0.329.0>
Мое текущее понимание проблемы заключается в том, что A
файл application.ex конфликтует с файлом B
приложения.
Вопросы
- Как мне исправить этот конфликт?
Ответ №1:
Если я правильно понял требование, A
хочет в конечном итоге остановить B
приложение и B
процесс возрождения контролируется,
Application.stop/1
делает именно это.
defmodule A.Application do
...
def start(_type, _args) do
Application.stop(:b) # ⇐ THIS
children = [B]
opts = [strategy: :one_for_one, name: A.Supervisor]
Supervisor.start_link(children, opts)
end
end
Обратите внимание, что ожидается имя приложения, как в mix.exs
файле (и позже в ×××.app
файле после компиляции).)
Комментарии:
1.
A
не хочет останавливатьB
и перезапускать его.A
хочет запуститьB
и контролировать его как часть своего дерева наблюдения. Проблема здесь в том, что приA
попытке сделать это он взрывается, потомуB
что уже был запущен (потомуB
что это само по себе зонтичное приложение). Такое поведение противоречиво, и я не уверен, что это архитектурная проблема из моего приложения umbrella, или если в моем решении чего-то не хватает.2. После долгих размышлений об архитектуре моего проекта я пришел к выводу, что это неверно и нуждается в изменении. Мое приложение
B
должно быть библиотекой с полным состоянием, а не приложением elixir. Однако, поскольку ваш ответ технически исправит проблему, я приму его. Спасибо, что нашли время, чтобы прочитать мой пост!