README.md

# Horde.Process

An opinionated but configurable means of quickly creating `GenServer` modules that are intended to be managed and distributed via `Horde`.

I found myself re-writing the same boilerplate code to create Horde-compatible processes; calling `GenServer.start_link` the same way every time, handling common return values, generating a "via tuple" for the process name, etc. Rather than having to copy/paste each change, I've put that encapsulation of common functionality into a single (albiet very small) library. Maybe it can make it's way into the Horde library at some point just so people have an easy time jumping into (what I think is) the common use case.

It may not fit your particular needs, despite me trying to make it configurable and also able to handle the straightforward usage out-of-the-box. If that's the case, you can open an issue or submit a PR.

## Installation

Add it to your `mix.exs` dependencies.

```elixir
def deps do
  [
    {:horde_process, "~> 0.1.0"},
  ]
end
```

You can read the full documentation at [HexDocs](https://hexdocs.pm/horde_process).

## Usage

Every Horde Process is assumed to be a `GenServer`. At some point I might remove this particular detail so that it can be mix and matched in other ways, but for now they are assumed to be `GenServer` processes.

To make a custom Horde Process simply `use Horde.Process` and pass in the required supervisor and registry modules.

```elixir
defmodule MyApp.User.Process do
  use Horde.Process, supervisor: MyApp.User.HordeSupervisor, registry: MyApp.User.HordeRegistry

  @impl Horde.Process
  def process_id(%{"user_id" => user_id}), do: user_id
  def process_id(%{user_id: user_id}), do: user_id
  def process_id(user_id) when is_binary(user_id), do: user_id

  @impl Horde.Process
  def child_spec(user_id) do
    %{
      id: user_id,
      start: {__MODULE__, :start_link, [user_id]},
      restart: :transient,
      shutdown: 10_000
    }
  end

  @impl GenServer
  # Set up the process state quickly and have `handle_continue/2` do the rest.
  def init(user_id) do
    Process.flag(:trap_exit, true)
    {:ok, user_id, {:continue, :init}}
  end

  @impl GenServer
  def handle_continue(:init, user_id) do
    {:ok, user} = MyApp.User.fetch(user_id)
    {:noreply, user}
  end
end
```

Read the documentation for `Horde.Process` to learn more about why using `{:continue, term}` is actually an important detail of having efficient Horde Processes.

In the above example we implement the two required Horde Process callbacks, `process_id/1` and `child_spec/1`, and then also implement our GenServer callbacks, `init/1` and `handle_continue/2`.

The custom module now has some additional functions which get generated by `use Horde.Process`; `fetch/1`, `call/2`, etc. If you wanted to get a user process, or start it if one was not already running, you could do the following:

```elixir
{:ok, pid} = MyApp.User.Process.get(user_id)
```

Now you can call the process as you normally would with any other PID. But let's assume you wanted to use `cast` or `call` functions against the process. Rather than fetching the PID and then passing that through to `GenServer.cast` or something, `Horde.Process` imports some additional helper functions.

```elixir
{:ok, reply} = MyApp.User.Process.call!(user_id, :do_something)
```

Or maybe you don't want to force a user process to start up if one isn't already running:

```elixir
{:ok, reply} = MyApp.User.Process.call(user_id, :do_something)
```

Because the reply from the process is wrapped by `call!/2`, if `GenServer.call` would normally reply with something like `{:ok, reply}` then the return value of `call!/2` would be `{:ok, {:ok, reply}}`. The first term of the tuple is just specifying whether a process was registered and could receive the message. This means that you could receive error replies that should be matched on `{:ok, {:error, err}}`. If the first term in the tuple is not `:ok` then it means something went wrong with Horde, not with the request itself.