Background
I have an umbrella app that has many smaller apps inside. One of this apps, called A, needs to be able to spin and supervise another app, called B.
B, being an app in its own right, exposes a public API and has a GenServer, responsible for receiving requests that it then redirects to the logic modules and such.
Issue
So, I have two requirements:
- I must be able to launch
Bindependently and have it work as a normal standalone app. Amust be able to haveBin its children and restart/manage it, should such a need arise.
The problem I have here, is that with my code I can either achieve 1 or 2, but not both.
Code
So, the following is the important code for app 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 (simplified)
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
And here is the entrypoint of 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 is basically a facade for the Server, with some extra context information such as specs, type definitions, etc (omitted here for the sake of brevity).
How does A manage the lifecycle?
It is my understanding that supervision trees are specified in the application.ex file of apps. So, from my understanding, I have created this application file for 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
Which should work, except it doesn't.
When inside A's folder, if I run iex -S mix, instead of having a nice launch I get the following error:
** (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>
My current understanding of the issue is that A's application.ex file is conflicting with B's application file.
Questions
- How do I fix this conflict?
If I understood the requirement correctly,
Awants to ultimately stopBapplication and respawnBprocess supervised,Application.stop/1is doing exactly this.Note it expects the name of the application, as in
mix.exsfile (and later in the×××.appfile after compilation.)