F6 · the architecture · module 7
PubSub, channels & real-time
F6.06 made one LiveView live: its own socket, its own state, its own diffs. But a catalog has many
viewers, and when one of them creates a course the others should see it without refreshing. That fan-out is
Phoenix.PubSub — publish/subscribe between processes over a named topic.
The shape is the OTP message passing the F5 engine already ran on: a process broadcasts a message, and every
process subscribed to that topic receives it in its mailbox. The discipline that matters is where the
broadcast comes from: the domain emits the event, in the context, right after a successful
write — not the web layer. Each LiveView subscribes on its connected mount (the
connected?/1 guard from F6.06.1) and handles the message in
handle_info/2, the callback for process messages as opposed to
handle_event/3 for browser clicks. Underneath LiveView sit channels,
the raw real-time primitive for custom client protocols, and alongside them Presence, which
tracks who is on a topic and stays correct across a whole cluster. One write becomes everyone’s live
update, and the domain never learns the web exists.
Three dives: broadcasting domain events from the context, subscribing a LiveView and handling the message, and the channels and presence beneath and beside it — real-time on the same message passing as F5.
One write, many subscribers
idle — 3 LiveViews subscribed to "catalog:lobby", awaiting a broadcast
Press broadcast: the context publishes one event on the topic and every subscriber updates at once.
Three parts of real-time
Live updates are three moves, and this module takes each in turn. broadcast publishes a domain event to a topic. subscribe joins a topic so a LiveView receives those events. presence tracks who is on a topic, across nodes. Select a part.
Real-time · select a part
part: broadcast
does: publish an event to a topic
PubSub is not a web feature bolted on — it is the BEAM’s native message passing exposed over a topic. The same broadcast reaches a LiveView, a background worker, or a process on another node, with no change to the caller.
One write, many clients
Trace a single create. A user submits the form; the LiveView calls the context; the context writes through
the Repo and, on success, broadcasts {:course_created, course} on the
"courses" topic. PubSub delivers that one message to every subscribed LiveView —
including the author’s and everyone else’s — and each updates its assigns and pushes a diff
to its own browser. One write, one broadcast, many live screens.
write → broadcast → every subscriber
The author’s own screen updates the same way as everyone else’s — through the broadcast, not a special case in the create handler. That uniformity is what keeps real-time code small: there is one path, and every client is on it.
Three deep dives
In order: broadcasting domain events from the context; subscribing a LiveView and handling the message; and the channels and presence that sit beneath and beside PubSub.
Phoenix.PubSub, topics, and broadcasting from the context after a write — with a facade wrapper for the server name.handle_info/2 — process messages, not browser events — updating a stream.Presence for who is on a topic, synced across a cluster.F5 emitted events
the engine recorded domain events to its EventStore as the source of truth.
F6.07 broadcasts them
the same kind of event, published on a topic, drives every live screen.
Start with broadcasting engine events, then subscribing a LiveView, then channels and presence. This module follows F6.06 — LiveView — and the next, F6.08, takes the whole app to production.
References
PubSub, channels, the handle_info/2 callback, and Presence.
Sources
- Phoenix.PubSub — distributed publish/subscribe over topics.
- Phoenix — Channels — the real-time primitive over WebSockets.
- Phoenix.LiveView — handle_info/2 — handling process messages in a LiveView.
- Phoenix.Presence — cluster-wide presence tracking.