umbrella 应用程序中的监督树冲突

Supervision tree conflict in an umbrella app

背景

我有一个综合应用,里面有很多小应用。其中一个名为 A 的应用程序需要能够旋转和监督另一个名为 B 的应用程序。

B,本身就是一个应用程序,公开了一个 public API 并有一个 GenServer,负责接收请求,然后将其重定向到逻辑模块等.

问题

所以,我有两个要求:

  1. 我必须能够独立启动 B 并将其作为普通的独立应用程序运行。
  2. A 必须能够在其子代中具有 B 和 restart/manage 它,如果出现这种需要。

我在这里遇到的问题是,使用我的代码我可以实现 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.ex

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 文件中。)