Cross dependency PubSub

I would like to have my library be able to publish messages on a channel which is reachable by the main application using it as a dependency.

One thing that I thought of is using a config.exs to specify my endpoint. i.e.

config :my_library,
  app_endpoint: MyApp.Endpoint,

and then do something like this

endpoint = Application.get_env(:my_library, :app_endpoint)
endpoint.broadcast!("lib-channel", "new_message", [])

However, this does not seem very clean. Is there a nicer way to do it?

It is library or application (in OTP terms)?

Honestly, I am not exactly sure as I don’t know OTP terms well enough.
It has a file structure created by mix new however, it has no application.ex and it’s components are started from the app using it. Therefore, I would say it is a library.

In that case, if you want to broadcast some messages then I assume 2 situations how this can happen:

  • You force user of your application to start some processes - then pass name of the broadcast module as an argument there (also remember to avoid named processes whenever you can and rather use Phoenix.PubSub directly instead of doing endpoint.broadcast!/3 call).
  • You fire the broadcast from the function, then why care about the broadcast at all and do not instead return value to the user to broadcast on their own.

Can you elaborate the second point? I am not sure I understood it completely.

Ok, examples:

You provide process for user to start

For example you have GenServer that will send broadcast message each second:

defmodule MyLib.Sender do
  use GenServer

  def start_link(opts) do
    pubsub = Keyword.fetch!(opts, :pubsub)
    GenServer.start_link(__MODULE__, pubsub)
  end

  def init(pubsub), do: {:ok, pubsub, {:continue, :send}}

  def handle_info(:ping, state), do: {:noreply, state, {:continue, :send}}

  def handle_continue(:send, pubsub) do
    Phoenix.PubSub.broadcast!(pubsub, "lib-channel", "new_message")
    Process.send_after(self(), :ping, 1000)
    {:noreply, pubsub}
  end
end

And now your user will need to add {MyLib.Sender, pubsub: MyApp.Endpoint} to their supervision tree, and everything will work as expected.

You provide function

If you have something like:

def foo do
  endpoint = Application.get_env(:my_library, :app_endpoint)
  endpoint.broadcast!("lib-channel", "new_message", [])
end

Then you can just do:

def foo, do: "new_message"

And force user to dispatch it on their own, or:

def foo(pubsub) do
  Phoenix.PubSub.broadcast!(pubsub, "lib-channel", "new_message")
end
4 Likes

Thank you! It seems to me the first option is nicer.