Elixir/Phoenix PubSub messages between different applications

2 years ago

I recently picked up Elixir and Phoenix as development tools and i'm loving them. The Erlang virtual machine (BEAM) where code runs is an amazingly simple, yet powerful, system that allows for some really hard things like inter-process communication to become simple.

Phoenix's PubSub makes this process even simpler. The only problem is that the documentation at times is not that simple to follow, and i have to admit i lost more time than i wanted getting it to work properly.

The problem

My application consists of two Erlang applications: Phoenix, and a Broadway app for doing some pipeline processing. Ideally i would love for the Broadway worker to be able to notify the user in realtime in the browser of what it is doing. After much fiddling around i managed to make it work using LiveView.

The Solution

In my Broadway worker (or in your case in any other application running on the Erlang VM) you can send events to PubSub like this:

Phoenix.PubSub.broadcast!(
  HappyPhoenixApp.PubSub,
  "notifications",
  "Doing some work"
)

Where HappyPhoenixApp.PubSub is the name of the PubSub you declare when configuring the Phoenix endpoint in config.exs and notifications is the name of the topic you are sending the message to.

The best way to subscribe to this topic in LiveView is in your view's mount function, that gets run when the view is initialized, like so:

def mount(_session, socket) do
  # Subscribe to notifications coming from the Broadway app
  Phoenix.PubSub.subscribe(HappyPhoenixApp.PubSub, "notifications")
  
  {:ok, 
   assign(socket,
     messages: ["Ready!"]
   )}
end

Then you just need to implement the handle_info function to receive events from the other app:

@doc """
  Handles events coming from Broadway
"""
def handle_info(message, socket) do
  {:noreply,
   assign(socket,
     messages: socket.assigns.messages ++ [message]
   )}
end

You might notice i am assigning the message i get to a LiveView variable messages (which i initialized previously in mount with an initial value). This will make LiveView re-render the HTML it is currently managing, making sure the client sees the updates in realtime via websockets.

Happy mixing!