Best way to interact with GenServer's from Plug.Router

I have a router, built on Plug.Router that I’m working with that needs interact with a GenServer. My GenServer's API has the method MyRegistry.find/2 which expects the server (pid or name) to query against and the ID of the item to find in the registry.

In my router, I am trying to avoid hard coding the registry name in my calls (MyRegistry.find(:my_registry, params["id"])), but I’m not sure of how best to provide my router with the name of the registry to pull from. I’ve tried to use the init method in the past on the router, but Plug.Router doesn’t accept arbitrary values in the options.

1 Like

You can decorate the GenServer(Registry) with a Service module:

defmodule GenServerTest do
    use GenServer
    
    def start_link do
        GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
    end
    
    def init(:ok) do
        {:ok, []}
    end
    
    def find(pid, value) do
        GenServer.call(pid, {:find, value})
    end
    
    def handle_call({:find, value}, _from, state) do
        IO.puts "found #{value}" 
        {:reply, value, state}
    end
end

defmodule GenServerService do
    def find(value) do
        GenServerTest.find(GenServerTest, value) 
        # this will call GenServerTest.find(Process.whereis(GenServerTest), value)
        # Process.whereis(GenServerTest) -> will return pid of GenServerTest
    end
end

or inside plug, you can store the GenServer pid/atom inside an attribute:

defmodule MyPlugdo
  import Plug.Conn

  @genserver_pid Process.whereis(GenServerTest)
  ...
end

If your GenServer name will change you will modify one attribute or one Service module

1 Like

But that still doesn’t accomplish what I’m looking for, which is to be able to run my test suite which lights up an instance of MyRegistry for the router to use to run the tests against and inject the name of the registry into the router.

2 Likes

I have similar confusion. Did you every determine an efficient manner to call the GenServer from your router without naming the GenServer specifically?

1 Like

Why not add a plug that puts the PID or name in :private?

1 Like

something like

defmodule RegistryPlug do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, opts) do
    registry = Keyword.get(opts, :registry) || :some_fallback_name

    put_private(conn, :my_registry, registry)
  end
end

Then you can decide on how you want to get that information from the conn.

def find(conn, arg) do
  %{my_registry: registry} = conn[:private]

  MyRegistry.find(registry, arg)
end
1 Like

This still requires a specific name for the GenServer. However, I believe I can use the same logic and store the PID value instead assuming this works in a Module with use Plug.Router.

1 Like