F6.09 · part 3 of 3
Many clients, live
A dashboard is most useful when a whole team watches it, and that is where the work from F6.07 pays
off completely. Because a broadcast on "events" fans out to every subscribed
process, every open dashboard receives the same event and folds it independently — ten browsers, ten
mailboxes, ten diffs, all from one publish. No dashboard knows about the others; the runtime delivers to all of
them. To show how many people are looking, the dashboard uses Presence: it
tracks itself on a "dashboard" topic at mount, and a
"presence_diff" message — arriving in handle_info/2 like
any other — lets it recompute the viewer count from Presence.list/1. The whole
thing stays read-only: the dashboard seeds from the contexts, folds broadcasts, and reports
presence, but it never writes — it is a projection, not a participant. And because all of this rides on the
BEAM’s distribution, clustering from F6.08 makes it correct across nodes for free: a viewer
on one server and a write on another still meet, because PubSub and Presence span the cluster. One publish, every
screen, an honest count — and the same code on one node or ten.
Four parts: the fan-out and what is read-only, how one event reaches every dashboard across a cluster, tracking viewers with Presence, and the read-only wiring that ties the whole course together.
Fan-out, Presence, read-only
Three ideas finish the lab. Fan-out means one broadcast reaches every dashboard. Presence reports who is watching. Read-only is the discipline: the dashboard observes, never writes. Select one.
Many clients · select one
piece: fan-out
is: one broadcast, every dashboard
Read-only is not a limitation here — it is what makes the dashboard safe to open anywhere by anyone with access. It cannot corrupt state because it has no path to write; it can only show what the domain already decided.
One event, every dashboard
The fan-out is the F6.07 picture, now with dashboards on the receiving end. One context broadcast goes to PubSub, which delivers it to every subscribed dashboard; each folds the event into its own read model and diffs to its own browser. Clustered across nodes, a write on one server reaches a viewer on another, because PubSub and Presence span the whole cluster.
one publish → every dashboard, across nodes
Scaling the number of viewers or the number of servers does not change the publishing code at all. That invariance — one node or a cluster, one viewer or a thousand, the same broadcast — is the payoff of building on the BEAM from F5 onward.
Counting the viewers
The viewer count uses Presence exactly as in F6.07.3. At the connected mount the dashboard subscribes to the
event topic and tracks itself on a "dashboard" topic. When
anyone joins or leaves, Presence broadcasts a "presence_diff", which the dashboard
handles by recomputing map_size(Presence.list(...)) into the
viewers assign.
def mount(_params, _session, socket) do if connected?(socket) do Portal.subscribe("events") PortalWeb.Presence.track(self(), "dashboard", socket.id, %{}) end {:ok, seed(socket)} end # Presence broadcasts a diff whenever a viewer joins or leaves def handle_info(%{event: "presence_diff"}, socket) do {:noreply, assign(socket, viewers: map_size(PortalWeb.Presence.list("dashboard")))} end
The viewer count and the metrics arrive through the same door —
handle_info/2 — because both are process messages. Presence is not a special case
in the dashboard; it is one more event kind folded into the read model.
The read-only wiring
The dashboard is routed like any LiveView, protected by the F6.08 auth, and everything it does is reading. The recap below is the whole lab in one view: seed from the contexts, fold broadcasts, report presence, and never write. That single read-only LiveView is where the engine, the contexts, the templates, LiveView, PubSub, and the deploy all meet.
# routed like any LiveView, protected by the F6.08 auth live_session :authenticated, on_mount: [{PortalWeb.UserAuth, :ensure_authenticated}] do live "/dashboard", DashboardLive end # the dashboard only reads: # mount seeds counts from the contexts (F6.04) # handle_info folds broadcasts into assigns + a stream (F6.09.2) # Presence reports viewers; clustering (F6.08) spans nodes # it never writes — a projection over the event stream
one publish
Fans out to every dashboard, on every node, and Presence counts them.
the whole course, live
Engine, contexts, LiveView, PubSub, and the deploy, in one read-only screen.
That completes F6.09 — and the course. From the F5 engine and its supervision tree, through contexts, routing, Ecto, templates, LiveView, PubSub, and deployment, to this dashboard that watches it all in real time, you have built one coherent system on the BEAM. Back to the module overview, the F6 chapter, or the course contents.