Channel.broadcast vs Endpoint.broadcast vs PubSub.broadcast

There seems to be quite a bit of overlap between these 3.

1- What are the main differences between:
Channel, Endpoint, PubSub calling broadcast, broadcast_from

2- They appear to all be built upon PubSub.broadcast, which a mod in the elixir discord pointed out was sending 2 messages: phoenix_pubsub/pubsub.ex at v2.1.1 · phoenixframework/phoenix_pubsub · GitHub

  @spec broadcast_from(t, pid, topic, message, dispatcher) :: :ok | {:error, term}
  def broadcast_from(pubsub, from, topic, message, dispatcher \\ __MODULE__)
      when is_atom(pubsub) and is_pid(from) and is_binary(topic) and is_atom(dispatcher) do
    {:ok, {adapter, name}} = Registry.meta(pubsub, :pubsub)

    with :ok <- adapter.broadcast(name, topic, message, dispatcher) do
      dispatch(pubsub, from, topic, message, dispatcher)
    end
  end

Why are both a broadcast and a dispatch being called here? And which one is the one that pushes to the socket?

PS: I would like to contribute a PR the docs once I have wrapped my head around the differences

2 Likes

You generally shouldn’t need to care about the implementation details within phoenix pubsub. It currently publishes messages between multiple nodes by broadcasting through the configured inter-node communication adapter (pg or redis based) and locally directly to listeners using elixir’s Registry. That’s why there’s a broadcast and dispatch. But that may change at any time.

Publishing to sockets is not handeled by Phoenix PubSub, but only the channels layer on top.

There’s really two layers here.

  1. There’s the low level pubsub support provided by phoenix pubsub. That really (contrary to its name) is completely independant to phoenix itself. You can send any message through it and the receiving process will receive just that message as is.

  2. Channels however is an phoenix specific abstraction, which builds on pubsub, but brings some additional constraints. E.g. it can only send map data (or recently raw binary values) as messages as channels might communicate with external systems like phoenix.js in browsers. These messages are also wrapped in a phoenix specific struct to include additional metadata about the broadcast.

Endpoint.broadcast / Channel.broadcast are APIs to interact with the 2. layer, while PubSub.broadcast is about the 1. There’s Endpoint.broadcast and Channel.broadcast mostly because the latter is a bit more convenient to use, as a few parameters used to broadcast can be infered from the socket a channel has access to, but you need to use Endpoint.broadcast is you want to broadcast from outside the context of an active channel process.

9 Likes

That clears up quite a bit, thank you!

What about broadcast_from? I understand its purpose under the Channel module, but not for Endpoint. Correct me if I’m wrong but since you’re likely calling it from outside a channel process, and it is intended to go straight to the client, it’s not like all topic subscribers won’t receive it (i.e. including the caller), which seems to be the point behind Channel.broadcast_from.

What are the use cases for Endpoint.broadcast_from ?

I can’t think of any at the moment but it does exist for completeness. Both Endpoint.broadcast* and Channel.broadcast* are convenience wrappers around the actual PubSub to be used in your Phoenix apps from outside and from inside channels respectively.

3 Likes

The biggest difference is Endpoint.broadcast broadcasts %Phoenix.Socket.Broadcast{} structs, which means its interface is about broadcasting to web users connected to the endpoint (like channels). In the case of channels, this also allows broadcasts to be “fastened” down to the client which is an optimization that avoids N serializations across users. Of course, regular elixir processes can get it on the messages by subscribing and processing them, but if you have no need to broadcast directly to end-users, lower-level Phoenix.PubSub allows you to broadcast any elixir term and subscribers can do whatever they need from there. Make sense?

6 Likes