How to make processes that persistently map to stored data?

Assuming you are using GenServers, you may register your processes under any term (instead of just atoms),
here is the relevant part of the GenServer docs:

Name Registration

Both start_link/3 and start/3 support the GenServer to register a name on start via the :name option. Registered names are also automatically cleaned up on termination. The supported values are:

an atom - the GenServer is registered locally with the given name using Process.register/2.

{:global, term}- the GenServer is registered globally with the given term using the functions in the :global module.

{:via, module, term} - the GenServer is registered with the given mechanism and name. The :via option expects a module that exports register_name/2, unregister_name/1, whereis_name/1 and send/2. One such example is the :global module which uses these functions for keeping the list of names of processes and their associated pid’s that are available globally for a network of Erlang nodes.

So you could do something like:

:global example
defmodule ElixirForum.ExampleAgent do
  use GenServer
  
  def start_link(agent_id) do
    GenServer.start_link(__MODULE__, nil, name: {:global, {:example_agent, agent_id}})
  end
  
  def whereis(agent_id) do
    :global.whereis_name({:example_agent, agent_id})
  end
  
  def do_something(agent_pid, data) do
    GenServer.call(agent_pid, {:do_something, data})
  end
end

And use it like:

some_agent_id
|> ElixirForum.ExampleAgent.whereis
|> ElixirForum.ExampleAgent.do_something(some_data)

If you dont want to register the process globally you can use via tuples. They work the same way, you simply have to provide a module that exports the appropriate functions for handling process registration (listed in the docs), such as :gproc

:gproc actually requires keys to be triplets with the following form: {type, scope, key} In the example i use :n for type which indicates a unique registration (only one process with the given key) and :l for scope which indicates a local registration.

via_tuple example
defmodule ElixirForum.ExampleAgent do
  use GenServer
  
  def start_link(agent_id) do
    GenServer.start_link(__MODULE__, nil, name: {:via, :gproc, {:n, :l, {:example_agent, agent_id}}})
  end
  
  def whereis(agent_id) do
    :gproc.whereis_name({:n, :l, {:example_agent, agent_id}})
  end
  
  def do_something(agent_pid, data) do
    GenServer.call(agent_pid, {:do_something, data})
  end
end

Usage remains the same:

some_agent_id
|> ElixirForum.ExampleAgent.whereis
|> ElixirForum.ExampleAgent.do_something(some_data)

Also, you might want to check out pg2 for handling process groups if that makes sense in your application :wink:

7 Likes