Trim Internal Admin section and rework wrap-up slides

Torey Heinz committed Feb 23, 2026
commit 2c6a28fced543f72d987120adf770284c3f66b01
Showing 2 changed files with 82 additions and 158 deletions
scratch.md +35 -0
@@ @@ -156,3 +156,38 @@ During my intro I want make sure to give a shout out to my employer Vianet Manag
Scheduling 500k Jobs
The Mailer Worker
The Rate Limiter
+ Why GenServer?
+
+
+
+ That's a lot of slides. What's your thinking — trim it down, reorder, or rework the focus? What do you want to keep vs cut?
+
+ Yes, we need to trim it down.
+
+ Keep:
+ 1. Section divider — Internal Admin
+ 2. The Philosophy — why a separate admin app
+ 3. Multi-Database Architecture — application.ex with 5 repos (code)
+ 6. LiveView: Async Charts — start_async + push_event (code)
+
+ Drop:
+ 4. LiveView: Real-Time Admin Forms — assign_async from 3 DBs (code)
+ 5. LiveView: Real-Time Presence — presence tracking (code)
+ 7. The Flow — step-by-step of async chart loading
+
+
+ Let's rethink the "wrap-up" it should
+
+ - Rewrite "What we covered" based on the current slides.
+ - slides/01-intro.html
+ - 02-the-language.livemd
+ - 03-real-world.livemd
+
+ - Highlight all the awesome things Elixir provides: This may take multiple slides
+ - Supervision, GenServer, pattern matching, with chains, concurrency, LiveView.
+ State lives in processes, not shared memory
+ Errors are handled by design, not by hope
+ Concurrency is the default, not an afterthought
+ Real-time features come for free
+
+ - These aren't separate tools bolted together. They're all built into the language and framework.
slides/03-real-world.html +47 -158
@@ @@ -335,7 +335,9 @@ end
<section>
<h2>Multi-Database Architecture</h2>
- <p class="muted small">Connects to <strong>five databases</strong> simultaneously</p>
+ <p class="muted small">
+ Connects to <strong>five databases</strong> simultaneously. Each Repo is a supervised connection pool to a different PostgreSQL database.
+ </p>
<pre class="small-code"><code class="language-elixir" data-trim>
defmodule VianetAdmin.Application do
@@ @@ -343,20 +345,13 @@ defmodule VianetAdmin.Application do
def start(_type, _args) do
children = [
- VianetAdminWeb.Telemetry,
VianetAdmin.Repo, # Admin's own database
VianetAdmin.PuppiesRepo, # Puppies.com database
VianetAdmin.RRRepo, # ReputableRooms database
VianetAdmin.RoommatesRepo, # Roommates database
VianetAdmin.GeocodeRepo, # Geocoding database
VianetAdmin.ConnectionMonitor,
- {Phoenix.PubSub, name: VianetAdmin.PubSub},
- {Finch, name: VianetAdmin.Finch},
VianetAdmin.Presence, # ← Track which admins are online
- VianetAdminWeb.Endpoint,
- VianetAdmin.Scheduler,
- {Oban, ...},
- {Cachex, [:cache]} # In-memory cache
]
opts = [strategy: :one_for_one, name: VianetAdmin.Supervisor]
@@ @@ -365,29 +360,17 @@ defmodule VianetAdmin.Application do
end
</code></pre>
- <p class="fragment" style="font-size: 0.7em; color: #555;">
- Each <code>Repo</code> is a supervised connection pool to a different PostgreSQL database.
- </p>
-
<aside class="notes">
The admin app's supervision tree connects to five databases simultaneously. Each Repo is a supervised connection pool to a different PostgreSQL database. Plus presence tracking, a scheduler, job queue, and in-memory cache. All in one tree.
</aside>
</section>
<section>
- <h2>LiveView: Real-Time Admin Forms</h2>
+ <h2>LiveView: Assign Async</h2>
<p class="muted small">Loading data from three databases asynchronously</p>
<pre class="small-code"><code class="language-elixir" data-trim>
def mount(params, _session, socket) do
- {form, action} =
- if Map.has_key?(params, "id") do
- admin = Accounts.get_admin!(params["id"])
- {Admin.update_changeset(admin, %{}) |> to_form(), :edit}
- else
- {Admin.create_changeset(%Admin{}, %{}) |> to_form(), :new}
- end
-
socket =
socket
|> assign(form: form, action: action)
@@ @@ -406,9 +389,9 @@ def mount(params, _session, socket) do
end
</code></pre>
- <p class="fragment" style="font-size: 0.7em; color: #555;">
+ <!-- <p class="fragment" style="font-size: 0.7em; color: #555;">
<code>assign_async</code> loads admin lists from 3 databases <strong>concurrently</strong> &mdash; loading states come for free.
- </p>
+ </p> -->
<aside class="notes">
Here's a real LiveView form. It loads data from three different databases asynchronously using assign_async. The loading states are automatic. Pattern matching determines if we're creating or editing. The form submission is just an Elixir function, not a REST endpoint.
@@ @@ -416,40 +399,30 @@ end
</section>
<section>
- <h2>LiveView: Real-Time Presence</h2>
- <p class="muted small">Multiple admins viewing the same record can see each other</p>
-
- <pre><code class="language-elixir" data-trim>
- # Track this admin's presence
- Presence.track(self(), "admin:presence", admin.id, %{
- initials: Utilities.initials(admin),
- current_user: params["id"]
- })
- Phoenix.PubSub.subscribe(PubSub, "admin:presence")
-
- # Handle presence changes — who joined, who left
- def handle_info(
- %Phoenix.Socket.Broadcast{event: "presence_diff", payload: diff},
- socket) do
- {:noreply,
- socket
- |> handle_leaves(diff.leaves)
- |> handle_joins(diff.joins)}
- end
+ <h2>Using Async Results</h2>
+ <p class="muted small">The template handles loading and success states automatically</p>
- defp handle_joins(socket, joins) do
- Enum.reduce(joins, socket, fn {admin, %{metas: [meta | _]}}, socket ->
- assign(socket, :admins, Map.put(socket.assigns.admins, admin, meta))
- end)
- end
+ <pre class="small-code"><code class="language-elixir" data-trim>
+ &lt;.async_result :let={admins} assign={@puppies_admins}&gt;
+ &lt;:loading&gt;
+ &lt;.spinner /&gt;
+ &lt;/:loading&gt;
+
+ &lt;.input
+ type="select"
+ label="Puppies admin account"
+ options={Enum.map(admins, &{&1.name, &1.id})}
+ /&gt;
+ &lt;/.async_result&gt;
</code></pre>
- <p class="fragment" style="font-size: 0.8em;">
- <strong>No WebSocket code. No JavaScript. No polling.</strong>
+ <p class="fragment" style="font-size: 0.7em; color: #555;">
+ Loading spinner &rarr; data arrives &rarr; dropdown renders.<br/>
+ <strong>No loading state management. No useEffect. No fetch.</strong>
</p>
<aside class="notes">
- Real-time presence. When multiple admins look at the same user record, they can see each other. Phoenix Presence handles distributed presence tracking across all connected clients. No WebSocket code, no JavaScript, no polling. It just works.
+ Here's how you consume the async result in the template. The async_result component gives you a loading slot for a spinner, and when the data arrives, it renders the content with the resolved value. No useState, no useEffect, no loading boolean you have to manage. LiveView handles it all. Compare this to React where you'd need useState for the data, useEffect for the fetch, a loading boolean, error handling — all wired up manually.
</aside>
</section>
@@ @@ -494,101 +467,6 @@ end
</aside>
</section>
- <section>
- <h2>The Flow</h2>
- <ol>
- <li class="fragment">Page loads &rarr; shows loading spinner (<code>AsyncResult.loading()</code>)</li>
- <li class="fragment">Async task completes &rarr; <code>handle_async</code> fires, data flows to chart via <code>push_event</code></li>
- <li class="fragment">User changes filter &rarr; new async task starts, spinner reappears</li>
- <li class="fragment">New data arrives &rarr; chart updates in real-time</li>
- </ol>
- <p class="fragment" style="font-size: 0.8em; margin-top: 1em; color: #555;">
- <strong>No REST API. No <code>fetch()</code>. No loading state management library.</strong><br/>
- All handled by LiveView's built-in async primitives.
- </p>
-
- <aside class="notes">
- Let me walk through the flow explicitly. Page loads, shows a spinner. Async task completes, data flows to the chart. User changes a filter, new task starts, spinner appears. New data arrives, chart updates. No REST API, no fetch, no state management library. It's all built into LiveView.
- </aside>
- </section>
-
- <!-- =============================================
- SECTION: Summary
- ============================================= -->
-
- <section>
- <h2>What We've Seen</h2>
- <p class="muted small">Three production systems, same patterns</p>
-
- <div class="pattern-grid">
- <div class="pattern-card fragment">
- <h4>Supervision</h4>
- <p><strong>Email:</strong> Rate limiter + job queue<br/>
- <strong>Domain:</strong> Connection pool + health cache<br/>
- <strong>Admin:</strong> Multi-DB + presence + cache</p>
- </div>
- <div class="pattern-card fragment">
- <h4>GenServer</h4>
- <p><strong>Email:</strong> Token bucket rate limiter<br/>
- <strong>Domain:</strong> TCP connection pool<br/>
- <strong>Admin:</strong> Connection monitor</p>
- </div>
- <div class="pattern-card fragment">
- <h4>Pattern Matching</h4>
- <p><strong>Email:</strong> Error handling in <code>with</code><br/>
- <strong>Domain:</strong> Socket type dispatch<br/>
- <strong>Admin:</strong> Form actions, presence diffs</p>
- </div>
- <div class="pattern-card fragment">
- <h4><code>with</code> Chains</h4>
- <p><strong>Email:</strong> 6-step pipeline<br/>
- <strong>Domain:</strong> Connect &rarr; greet &rarr; login<br/>
- <strong>Admin:</strong> Form submit &rarr; redirect</p>
- </div>
- <div class="pattern-card fragment">
- <h4>Concurrency</h4>
- <p><strong>Email:</strong> 500k jobs in parallel<br/>
- <strong>Domain:</strong> Pooled TCP connections<br/>
- <strong>Admin:</strong> Async from 5 databases</p>
- </div>
- <div class="pattern-card fragment">
- <h4>LiveView</h4>
- <p><strong>Email:</strong> Campaign management<br/>
- <strong>Domain:</strong> Domain search &amp; registration<br/>
- <strong>Admin:</strong> Real-time dashboards + presence</p>
- </div>
- </div>
-
- <aside class="notes">
- Three production systems, same patterns everywhere. Supervision, GenServer, pattern matching, with chains, concurrency, LiveView. These aren't separate tools bolted together. They're all built into the language and framework. One supervision tree. One deployment. One codebase.
- </aside>
- </section>
-
- <section>
- <h2>One Unified Platform</h2>
- <p style="font-size: 0.85em; color: #555; max-width: 700px; margin: 0.5em auto;">
- These aren't separate tools bolted together. They're all
- <strong>built into the language and framework</strong>.
- </p>
- <div class="stats-row fragment">
- <div class="stats-item">
- <div class="stat" style="font-size: 1.8em !important;">1</div>
- <div class="stat-label">Supervision tree</div>
- </div>
- <div class="stats-item">
- <div class="stat" style="font-size: 1.8em !important;">1</div>
- <div class="stat-label">Deployment</div>
- </div>
- <div class="stats-item">
- <div class="stat" style="font-size: 1.8em !important;">1</div>
- <div class="stat-label">Codebase</div>
- </div>
- </div>
-
- <aside class="notes">
- One supervision tree. One deployment. One codebase. That's the power of building on the BEAM.
- </aside>
- </section>
<!-- =============================================
SECTION: Wrap-up
@@ @@ -603,25 +481,21 @@ end
</section>
<section>
- <h2>What We Covered Today</h2>
+ <h2>What We Covered</h2>
+ <p class="muted">Three parts:</p>
<ol>
- <li class="fragment"><strong>Why Elixir exists</strong> &mdash; Erlang's 40-year legacy of reliability, made accessible</li>
- <li class="fragment"><strong>Pattern matching</strong> &mdash; clearer code where every case is explicit</li>
- <li class="fragment"><strong>Processes &amp; supervision</strong> &mdash; concurrency without locks, fault tolerance without fear</li>
- <li class="fragment"><strong>LiveView</strong> &mdash; real-time web apps without JavaScript complexity</li>
- <li class="fragment"><strong>Real production systems</strong> &mdash; 500k emails, TCP protocols, multi-site admin tools</li>
+ <li class="fragment"><strong>Why Elixir?</strong> &mdash; The BEAM, 40 years of reliability, and why it matters for web devs</li>
+ <li class="fragment"><strong>The Language</strong> &mdash; Pattern matching, processes, supervision, and LiveView</li>
+ <li class="fragment"><strong>Real-World Code</strong> &mdash; 500k emails, a domain registrar, and a multi-site admin</li>
</ol>
<aside class="notes">
- Quick recap: why Elixir exists, pattern matching for clarity, processes and supervision for concurrency and fault tolerance, LiveView for real-time web apps, and three production systems showing it all in action.
+ Quick recap — three parts. First, why Elixir exists: the BEAM, 40 years of telecom reliability, and why that matters for web developers. Then the language: pattern matching, processes, supervision, and LiveView — all hands-on in LiveBook. And finally, real production code from Vianet: half a million emails, a domain registrar consolidated from five services, and a multi-site admin dashboard.
</aside>
</section>
<section>
- <h2>The Pitch</h2>
- <p style="font-size: 0.85em; color: #555; margin-bottom: 0.8em;">
- Elixir isn't just "another language." It changes <strong>how you think</strong> about building web apps:
- </p>
+ <h2>What Elixir Gives You</h2>
<ul>
<li class="fragment">State lives in <strong>processes</strong>, not shared memory</li>
<li class="fragment">Errors are handled <strong>by design</strong>, not by hope</li>
@@ @@ -630,7 +504,22 @@ end
</ul>
<aside class="notes">
- Elixir isn't just another language to learn. It fundamentally changes how you think about building software. State in processes, not shared memory. Errors handled by design. Concurrency as a default. Real-time for free. It's a different paradigm, and once it clicks, it's hard to go back.
+ So what does Elixir actually give you? State lives in processes, not shared memory — no locks, no race conditions. Errors are handled by design — supervisors restart crashed processes, pattern matching makes every case explicit. Concurrency is the default — the BEAM was built for millions of simultaneous connections. And real-time features come for free with LiveView and Phoenix Channels.
+ </aside>
+ </section>
+
+ <section>
+ <h2>One Unified Platform</h2>
+ <p class="fragment" style="font-size: 0.9em; color: #555; margin-bottom: 0.8em;">
+ These aren't separate tools bolted together.<br/>
+ They're all <strong>built into the language and framework</strong>.
+ </p>
+ <p class="fragment muted small" style="margin-top: 0.8em; font-style: italic;">
+ It's a different paradigm &mdash; and once it clicks, it's hard to go back.
+ </p>
+
+ <aside class="notes">
+ And here's the thing that makes Elixir special. All of these features — supervision, GenServer, pattern matching, concurrency, LiveView — they're not libraries you install or tools you bolt on. They're built into the language and framework from day one. State lives in processes, not shared memory. Errors are handled by design, not by hope. Concurrency is the default, not an afterthought. Real-time features come for free. It's a fundamentally different way to build web applications, and once it clicks, it's really hard to go back.
</aside>
</section>