Repeater-style Client

We call any client that repeats messages from another service's endpoint a repeater client.

This is a useful pattern when one wishes to ferry messages (without changing them) from one Phoenix.Endpoint to another when the services are different, or when the services are the same but endpoint clustering is not possible or desired.

In order to make this happen, we will create a client that

  • connects to the remove service
  • subscribes to the topic we wish to repeat
  • broadcasts messages as they happen to this service's endpoint

Tutorial

We start with an empty client (db79a32)

defmodule MyApp.RepeaterClient do
  @moduledoc """
  A repeater-kind of client which re-broadcasts messages from another service
  into this service's endpoint
  """

  use Slipstream

  def start_link(config) do
    Slipstream.start_link(__MODULE__, config, name: __MODULE__)
  end

  @impl Slipstream
  def init(config), do: {:ok, connect!(config)}
end

This client is pretty standard. It receives configuration from its supervisor, so it could be started in a lib/my_app/application.ex file with something like:

def MyApp.Application do
  use Application

  def start(_type, _args) do
    children = [
      {MyApp.RepeaterClient, uri: "ws://service-b.local:4000/socket/websocket"},
      MyAppWeb.Endpoint
    ]

    Supervisor.start_link(children, strategy: :one_for_one)
  end
end

First thing's first, let's make sure we join our desired topic upon connection by adding a Slipstream.handle_connect/1 implementation that calls Slipstream.join/3 (37b392e)

@topic "rooms:lobby"

@impl Slipstream
def handle_connect(socket), do: {:ok, join(socket, @topic)}

And let's add an empty implementation of Slipstream.handle_message/4 (3855594)

@impl Slipstream
def handle_message(topic, event, payload, socket) do
  {:ok, socket}
end

Slipstream.handle_message/4 is invoked whenever the remote server performs a Phoenix.Channel.push/3, or whenever the topic to which the client is subscribed receives a broadcast. Broadcasts are sent to all subscribers of a topic without a Phoenix.Channel explicitly needing to use Phoenix.Channel.push/3, but the interface is the same for Slipstream clients: Slipstream.handle_message/4 will be invoked with the broadcast.

Now for the "repeater" part, we will publish these broadcasts or pushes onto our service's endpoint using Phoenix.Endpoint.broadcast/3 (01b5568)

@endpoint SlipstreamWeb.Endpoint

@impl Slipstream
def handle_message(topic, event, payload, socket) do
  @endpoint.broadcast(topic, event, payload)

  {:ok, socket}
end

And that's all! Each Phoenix.Channel.push/3 or Phoenix.Endpoint.broadcast/3 published on the topic will be (re)broadcasted on the endpoint in the service of the repeater. See examples/repeater/client.ex for the full code of this example client and a small test case at test/slipstream/examples/repeater_test.exs.