Real-Time Phoenix: use PubSub with implementation of Phoenix.Socket.Transport rather than channels (page 37)

Hi, I’ve got a question about the implementation of PubSub when using a Phoenix.Socket.Transport behaviour rather than channels.
Before reading the book, I wrote a websocket server in Elixir using the Phoenix.Socket.Transport behaviour.

Since I’d like to use the PubSub to broadcast over multiple servers (using pg2 and libcluster to deliver messages also to remote nodes that are not in the same LAN), do I have to switch to channels to do that?

I ask it since in the book is not mentioned if you can build something like that also without channels.

Thanks in advance.

Andrea

You will probably be successful with calling PubSub.subscribe inside of your Transport process. This will receive a handle_info callback whenever an event is received over PubSub. You must subscribe to specific topics, which the book goes into detail about.

Think of PubSub as a layer that Phoenix provides. It sits below Channels, and Channels are built on top of it. If you don’t use Channels you can still benefit from PubSub because it’s a completely separate layer. You just need to call the right functions to use it.

Hi Steve,
firstly, many thanks for your quick reply and, over all, for this great book about the Phoenix real-time implementation. It’s really giving me many ideas about how to optimize my real-time application made in Elixir.

Secondly, I’ve got a question about your reply: you said that I have to subscribe to specific topics in order to receive callbacks using the PubSub layer, as I also saw in the book examples.
The question is: how can I subscribe to topics if I’m not using channels at all?
Inside my Phoenix.Socket.Transport implementation, I capture websocket messages (from the clients) using the handle_in/2 function and pattern matching.

Thank you very much again for all your help.

PubSub is not reliant on Channels at all. I realize now that the word topic may not have been the best choice because Channels and PubSub both use “topics” as a noun. In this case, a topic is just a string. Here’s an example of how to use PubSub:

Example 1: PubSub broadcast but no one is listening

iex(1)> Phoenix.PubSub.broadcast!(Clove.PubSub, "test", %{})
:ok
iex(2)> flush()                                             
:ok

In this case, we broadcast over PubSub and did not receive a message. This makes sense because how would the system know to send us the message?

Let’s try again but this time receive the message.

Example 2: PubSub with a listener

iex(1)> Phoenix.PubSub.subscribe(Clove.PubSub, "test")
:ok
iex(2)> Phoenix.PubSub.broadcast!(Clove.PubSub, "test", %{nice: true})
:ok
iex(3)> flush()
%{nice: true}
:ok

Hey, nice! The message was delivered to the current process (because we subscribed from the process).

Bit Deeper

PubSub subscribe/2 function subscribes the current process to the given topic. When there is a broadcast on that topic, then the process gets a message letting you know that it received something.

You can subscribe/broadcast from anywhere in your code. Typically subscription will be done in some long-lived process like a GenServer, Channel, or LiveView. Broadcast typically happens from many places in the code, such as after a record is persisted to the database, an API call is received, etc.

Choosing Topic Names

The book has a section on how to best choose your topics. I typically recommend splitting by some tenant concept, such as user, organization, etc. For example:

  • team-4:widget.saved could be the topic for when a widget gets persisted for team 4
  • user-1337:notification could be the topic for when user 1337 gets a notification

Hope this helps!