Clone
03-real-world.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Part 3: Real-World Code — Elixir in the Real World</title>

  <link rel="stylesheet" href="https://unpkg.com/reveal.js@5.1.0/dist/reset.css" />
  <link rel="stylesheet" href="https://unpkg.com/reveal.js@5.1.0/dist/reveal.css" />
  <link rel="stylesheet" href="https://unpkg.com/reveal.js@5.1.0/dist/theme/white.css" id="theme" />
  <link rel="stylesheet" href="https://unpkg.com/reveal.js@5.1.0/plugin/highlight/monokai.css" />
  <link rel="stylesheet" href="shared.css" />
</head>
<body>
  <div class="reveal">
    <div class="slides">

      <!-- =============================================
           SECTION: Intro
           ============================================= -->

      <section class="section-divider" data-transition="zoom">
        <h2>Part 3: Real-World Code</h2>
        <p>From concepts to production</p>

        <aside class="notes">
          Everything we just covered — pattern matching, processes, supervision, LiveView — these aren't academic concepts. They're the foundation of production systems running real businesses right now. Let's walk through three real systems built at Vianet Management.
        </aside>
      </section>

      <section>
        <h2>Three Production Systems</h2>
        <p class="muted">Going beyond basic Web apps</p>
        <ol>
          <li class="fragment"><strong>Vianet Marketing</strong> &mdash; Email marketing platform</li>
          <li class="fragment"><strong>OrbitFour</strong> &mdash; full featured domain registration platform</li>
          <li class="fragment"><strong>Vianet Admin</strong> &mdash; Multi-site LiveView dashboard</li>
        </ol>

        <aside class="notes">
          We'll look at three systems. Each one showcases different aspects of what we learned — GenServers, supervision, pattern matching, and LiveView — all in production.
        </aside>
      </section>

      <!-- =============================================
           SECTION 3.1: Vianet Marketing
           ============================================= -->

      <section class="section-divider" data-transition="zoom">
        <h2>Vianet Marketing</h2>
        <p>Sending emails through AWS Simple Email Service (SES)</p>

        <aside class="notes">
          First system: our Vianet marketing platform. This sends half a million personalized emails through AWS SES. The challenge isn't just volume — it's doing it reliably without exceeding rate limits.
        </aside>
      </section>

      <section data-transition="fade">
        <img src="images/vianet-marketing.jpg" alt="Vianet Marketing email campaign dashboard" style="max-height: 85vh; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
        <aside class="notes">
          This is our Vianet marketing platform. On the left you can see the campaign details — audience, member count, schedule. On the right, the actual email preview. Over half a million members, and each one gets a personalized email.
        </aside>
      </section>

      <section>
        <h2>The Challenge</h2>
        <div class="callout fragment">
          <p>Send 500,000+ personalized marketing emails</p>
        </div>
        <ul>
          <li class="fragment">Emails need to be sent individually so each one is trackable.</li>
          <li class="fragment">Ensure we send <strong>one and only one</strong> email per recipient!</li>
          <li class="fragment">Handle bounces, complaints, and delivery notifications</li>
          <li class="fragment">Each email generates 3&ndash;4 API requests &mdash; needs to run <strong>concurrently</strong> without blocking the app</li>
          <li class="fragment">With AWS SES, if you exceed your rate limit, they...<br> <strong class="fragment">silently drop your emails</strong>.</li>
        </ul>

        <aside class="notes">
          Here's what makes this hard. Each email has to be sent individually so we can track opens, clicks, and bounces per recipient. We have to guarantee one and only one email per person — duplicates destroy trust. We need to handle bounces, complaints, and delivery notifications from AWS. Each email generates 3 to 4 API requests, so this has to run concurrently without blocking the web app. And the kicker: if you exceed your AWS SES rate limit, they silently drop your emails. No error, no warning, nothing. So we need a rate limiter that's rock-solid.
        </aside>
      </section>

      <section>
        <h2>Our Solution</h2>

        <ul>
          <li class="fragment"><strong>Scheduler Worker</strong> queries recipients, creates Oban jobs</li>
          <li class="fragment"><strong>Oban</strong> picks up each job and triggers a <strong>Mailer Worker</strong></li>
          <li class="fragment"><strong>Mailer Worker</strong> validate &rarr; personalize &rarr; rate-check &rarr; send</li>
          <li class="fragment"><strong>Rate Limiter</strong> ensures we never exceed the AWS SES limit</li>
        </ul>

        <aside class="notes">
          Here's our solution. A scheduler worker queries recipients and creates individual Oban jobs — one per email. Oban is an Elixir job queue backed by Postgres, it picks up each job and triggers a mailer worker. Each mailer worker handles one email through a pipeline: validate, personalize, check the rate limiter, send. And the rate limiter is a GenServer that ensures we never exceed the AWS SES sending rate. Everything is supervised.
        </aside>
      </section>

      <section>
        <h2>Scheduling 500k Jobs</h2>
        <p class="muted small">Stream records in chunks &mdash; constant memory usage</p>

        <pre><code class="language-elixir" data-trim>
def perform(%Oban.Job{args: args}) do
  member_ids = Campaigns.member_ids!(args["campaign_id"])

  # Stream from DB → chunk → bulk insert jobs
  Repo.transaction(fn ->
    Repo.stream(member_ids(campaign), max_rows: 2_000)
    |> Stream.chunk_every(2_000)
    |> Enum.each(fn member_ids ->
      member_ids
      |> Enum.map(&amp;build_mailer_job(&amp;1, campaign))
      |> Oban.insert_all()
    end)
  end, timeout: :infinity)
end
        </code></pre>

        <aside class="notes">
          You can't load 500k records into memory at once. Repo.stream gives us a lazy stream — a database cursor that fetches rows lazily. Stream.chunk_every breaks it into chunks of 2,000. Each chunk becomes 2,000 individual email jobs inserted into Oban. Constant memory usage regardless of how many records we have. The Repo.transaction wrapper is required because the database cursor needs the connection to stay open for the full iteration.
        </aside>
      </section>

      <section>
        <h2>The Mailer Worker</h2>
        <p class="muted small">Pattern matching handles every possible failure</p>

        <pre class="small-code"><code class="language-elixir" data-trim>
def perform(%Oban.Job{args: args} = job) do
  campaign_id = Map.get(args, "campaign_id")
  member_id = Map.get(args, "member_id")

  with(
    {:ok, member}         <- get_member_with_site(member_id),
    {:ok, _}              <- validate_email(member.email),
    {:ok, campaign}       <- get_campaign_with_site(campaign_id),
    {:ok, _}              <- EmailRateLimiter.ready?()
  ) do
    send_campaign_email(campaign, member)
  else
    {:not_ready, :exceeded_rate}  -> {:snooze, 1}
    {:already_sent}               -> {:discard, :already_sent}
    {:invalid_email}              -> {:discard, :invalid_email}
    {:error, result}              -> {:error, result}
  end
end
        </code></pre>

        <aside class="notes">
          Each email goes through a pipeline using the `with` chain we saw in LiveBook. Get the member, validate the email, get the campaign, reserve a campaign email record to guarantee exactly-once delivery, then check the rate limiter. If everything passes, send. The else block reads like a specification: rate exceeded? Snooze 1 second. Daily limit hit? Back off up to an hour. Already sent? Discard. Invalid email? Discard. Every outcome is handled explicitly. No silent failures.
        </aside>
      </section>

      <section>
        <h2>The Rate Limiter</h2>
        <p class="muted small">A GenServer using a token bucket algorithm</p>

        <pre class="small-code"><code class="language-elixir" data-trim>
defmodule Marketing.EmailRateLimiter do
  use GenServer

  defstruct tokens: 0.0, rate: 100.0

  # Public API — other code just calls this
  def ready?, do: GenServer.call(__MODULE__, :acquire)

  def handle_call(:acquire, _from, state) do
    if state.tokens >= 1.0 do
      {:reply, {:ok, :token}, %{state | tokens: state.tokens - 1.0}}
    else
      {:reply, {:not_ready, :exceeded_rate}, state}
    end
  end
end
        </code></pre>

        <aside class="notes">
          This is the real rate limiter, simplified for the slide. It's a GenServer using a token bucket algorithm. The public API is just `ready?` — that's all other code needs to call. Internally, it tracks tokens that refill over time based on our SES sending rate. When a worker asks ready?, it refills tokens based on elapsed time, then checks if there's a token available. If yes, it decrements and says OK. If not, it says not ready. Thread-safe by design — the GenServer processes messages one at a time, so there's no way two workers can grab the same token.
        </aside>
      </section>

      <!-- =============================================
           SECTION 3.2: OrbitFour Domain Registry
           ============================================= -->

      <section class="section-divider" data-transition="zoom">
        <h2>OrbitFour Domain Registry</h2>
        <p>Taking ownership of our core functions</p>

        <aside class="notes">
          Second system: OrbitFour, our domain registrar. This is a story about taking ownership of your core functions — and how Elixir made that practical.
        </aside>
      </section>

      <section data-transition="fade">
        <img src="images/orbitfour-homepage.jpg" alt="OrbitFour homepage" style="max-height: 85vh; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
        <aside class="notes">
          This is OrbitFour — our domain registrar. Clean, simple interface. No upselling, no cross-sells. Let's look at what's under the hood.
        </aside>
      </section>

      <section>
        <h2>What is OrbitFour?</h2>
        <p>A <strong>domain registrar</strong> &mdash; think GoDaddy, but built by a small team in West Michigan.</p>
        <ul>
          <li class="fragment">Talks directly to <strong>Verisign</strong> via persistent TCP &mdash; EPP protocol</li>
          <li class="fragment">Full lifecycle: search &rarr; register &rarr; DNS &rarr; renew &rarr; transfer</li>
          <li class="fragment">Billing, RDAP/WHOIS, customer portal, marketing site &mdash; <strong>all one Phoenix app</strong></li>
          <li class="fragment"><strong>16 background workers</strong> &mdash; syncing, renewals, health checks</li>
        </ul>

        <aside class="notes">
          OrbitFour is Vianet's domain registrar. Customers search for a domain, buy it, manage DNS, renew it — the whole lifecycle. Under the hood, it talks directly to Verisign over a persistent TCP connection. And here's what's impressive: one Phoenix app handles everything. EPP protocol, RDAP and WHOIS public APIs, Stripe billing, DNS zone management, a full LiveView customer portal, the marketing website, and 16 background workers. In most stacks, this would be 4 or 5 separate services. Here it's one app, one deployment, one supervision tree.
        </aside>
      </section>

      <section>
        <h2>The Core Function</h2>
        <p>OrbitFour is a <strong>domain registrar</strong> and needs to communicate with registries like Verisign via EPP as our <strong>core function</strong>.</p>
        <ul>
          <li class="fragment">Originally an <strong>open-source PHP EPP library</strong> was used.</li>
          <li class="fragment">Open-source is great for non-core features.</li>
          <li class="fragment">We needed to <strong>own</strong> the protocol layer: persistent TCP/TLS, connection pooling, automatic recovery</li>
        </ul>
        <p class="fragment muted small" style="margin-top: 0.8em;">
          Elixir made this possible &mdash; the BEAM was literally built for managing network connections.
        </p>

        <aside class="notes">
          OrbitFour is a domain registrar. Talking to Verisign over EPP is the single most important thing the app does. Originally we relied on an open-source PHP EPP library — it worked, but we didn't fully understand it, and when things broke we were at the mercy of upstream. Using open-source for non-core features is smart — but when the feature IS your product, you need to own it. Elixir made this realistic. The BEAM was designed for exactly this kind of work: persistent TCP connections, connection pooling, automatic recovery. We could write our own EPP client and actually understand every line.
        </aside>
      </section>

      <section data-transition="fade">
        <img src="images/orbitfour-epp-health.jpg" alt="OrbitFour EPP Health Monitor" style="max-height: 85vh; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
        <aside class="notes">
          This is our EPP Health Monitor — a LiveView page that shows the connection pool status in real-time. You can see the persistent TCP connections to PublicInterest and Verisign, utilization, response times. Stats refresh every 5 seconds. This is what owning your core function looks like.
        </aside>
      </section>

      <section>
        <h2>The Real Win</h2>
        <p class="muted small">OrbitFour replaced <strong>five separate services</strong> with one Elixir app</p>
        <ul>
          <li class="fragment"><strong>PHP EPP service</strong> &mdash; domain registration</li>
          <li class="fragment"><strong>Java RDAP service</strong> &mdash; domain lookups</li>
          <li class="fragment"><strong>PHP WHOIS service</strong> &mdash; WHOIS queries</li>
          <li class="fragment"><strong>Craft CMS</strong> &mdash; marketing site</li>
          <li class="fragment"><strong>Nuxt/Vue.js</strong> &mdash; customer portal</li>
        </ul>
        <p class="fragment" style="font-size: 0.8em; margin-top: 0.8em; color: #555;">
          These services took <strong>years</strong> to build originally.<br/>
          <span class="fragment">With <strong>Elixir</strong> + Claude Code,<br> we replaced them all in <strong>under six months</strong>.</span>
        </p>

        <aside class="notes">
          Here's the real story with OrbitFour. The original system was five separate services across three languages and two frameworks. A PHP EPP service for domain registration, a Java RDAP service for lookups, a PHP WHOIS service, a Craft CMS marketing site, and a Nuxt/Vue.js customer portal. These took years to build and maintain. With Elixir and Claude Code, we consolidated everything into a single Phoenix app in under six months. One codebase, one deployment, one supervision tree. That's the power of the platform.
        </aside>
      </section>


      <!-- =============================================
           SECTION 3.3: Internal Admin
           ============================================= -->

      <section class="section-divider" data-transition="zoom">
        <h2>Internal Admin</h2>
        <p>Multi-site LiveView dashboard</p>

        <aside class="notes">
          Third system: our internal admin app. This is a LiveView dashboard that connects to all of Vianet's properties from a single place.
        </aside>
      </section>

      <section data-transition="fade">
        <img src="images/vianet-admin.jpg" alt="Vianet Admin photo review dashboard" style="max-height: 85vh; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
        <aside class="notes">
          This is our internal admin app. Here you're looking at the photo review queue for Roommates.com — one dashboard to manage all of Vianet's properties.
        </aside>
      </section>

      <section>
        <h2>The Philosophy</h2>
        <p class="muted small">Instead of building admin features <em>into</em> each app...</p>
        <ul>
          <li class="fragment"><strong>Customer apps stay focused</strong> &mdash; no admin routes, no admin UI, no admin auth</li>
          <li class="fragment"><strong>Admin app iterates fast</strong> &mdash; internal tool, no public-facing risk</li>
          <li class="fragment"><strong>One dashboard</strong> &mdash; manage all sites from a single place</li>
          <li class="fragment"><strong>LiveView</strong> &mdash; real-time updates, interactive tables, presence tracking</li>
        </ul>

        <aside class="notes">
          The philosophy: instead of polluting each customer-facing app with admin features, we built a separate admin app that connects to all of them. Customer apps stay clean. The admin app can iterate fast since it's internal. One dashboard to manage everything. And LiveView gives us real-time updates for free.
        </aside>
      </section>

      <section>
        <h2>Multi-Database Architecture</h2>
        <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
  use Application

  def start(_type, _args) do
    children = [
      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,
      VianetAdmin.Presence,        # ← Track which admins are online
    ]

    opts = [strategy: :one_for_one, name: VianetAdmin.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
        </code></pre>

        <aside class="notes">
          Here's the admin app's supervision tree. It connects to five PostgreSQL databases simultaneously — each Repo is a supervised connection pool. Plus a connection monitor and presence tracking to see which admins are online. If any database connection drops, the supervisor handles it automatically. This is a simplified version — the full tree has more children, but this shows the pattern.
        </aside>
      </section>


      <!-- =============================================
           SECTION: Wrap-up
           ============================================= -->

      <section class="section-divider" data-transition="zoom">
        <h2>Wrap-up</h2>

        <aside class="notes">
          Let's wrap up.
        </aside>
      </section>

      <section>
        <h2>What We Covered</h2>
        <ol>
          <li class="fragment"><strong>Why Elixir?</strong> &mdash; The BEAM, 40 years of reliability</li>
          <li class="fragment"><strong>The Language</strong> &mdash; Pattern matching, processes, supervision, LiveView</li>
          <li class="fragment"><strong>Real-World Code</strong> &mdash; marketing emails, a domain registrar, multi-site admin</li>
        </ol>
        <ul style="margin-top: 1em; list-style: none; padding: 0;">
          <li class="fragment">Concurrency is the <strong>default</strong>, not an afterthought</li>
          <li class="fragment">Errors are handled <strong>by design</strong>, not by hope</li>
          <li class="fragment">Real-time features come <strong>for free</strong></li>
        </ul>
        <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">
          Quick recap. Why Elixir: the BEAM, 40 years of telecom reliability. The language: pattern matching, processes, supervision, and LiveView — all hands-on. Real production code: half a million emails, a domain registrar consolidated from five services, a multi-site admin dashboard. The key takeaway: concurrency, fault tolerance, and real-time features aren't libraries you bolt on — they're built into the language. It's a fundamentally different way to build web applications, and once it clicks, it's really hard to go back.
        </aside>
      </section>

      <section>
        <h2>Getting Started</h2>
        <ul class="resource-list">
          <li>
            <a href="https://elixir-lang.org">elixir-lang.org</a>
            <span class="resource-desc">&mdash; the official guides are excellent</span>
          </li>
          <li>
            <a href="https://livebook.dev">livebook.dev</a>
            <span class="resource-desc">&mdash; install it tonight, start experimenting</span>
          </li>
          <li>
            <a href="https://phoenixframework.org">phoenixframework.org</a>
            <span class="resource-desc">&mdash; the web framework</span>
          </li>
          <li>
            <a href="https://elixirforum.com">elixirforum.com</a>
            <span class="resource-desc">&mdash; one of the friendliest communities in tech</span>
          </li>
          <li>
            <a href="https://elixirschool.com">elixirschool.com</a>
            <span class="resource-desc">&mdash; free lessons, great for self-paced learning</span>
          </li>
        </ul>

        <aside class="notes">
          If you want to get started: elixir-lang.org has excellent official guides. Install Livebook tonight and start experimenting. Phoenix is the web framework. The Elixir Forum is genuinely one of the friendliest communities in tech. And Elixir School has free, self-paced lessons.
        </aside>
      </section>

      <section>
        <h2>Thank You</h2>
        <p><strong>Torey Heinz</strong></p>
        <p class="muted">Vianet Management / Shopflow</p>
        <p style="margin-top: 1em; font-size: 0.75em;">
          <a href="https://github.com/toreyheinz/2026-GR-WebDevElixirTalk">github.com/toreyheinz/2026-GR-WebDevElixirTalk</a>
        </p>
        <p style="margin-top: 1em; font-size: 1.2em; font-style: italic;">Questions?</p>

        <aside class="notes">
          Thank you! I'm happy to take questions. If you want to chat more about Elixir, catch me after — I could talk about this stuff all day.
        </aside>
      </section>

    </div>
  </div>

  <script src="https://unpkg.com/reveal.js@5.1.0/dist/reveal.js"></script>
  <script src="https://unpkg.com/reveal.js@5.1.0/plugin/highlight/highlight.js"></script>
  <script src="https://unpkg.com/reveal.js@5.1.0/plugin/notes/notes.js"></script>
  <script src="https://unpkg.com/reveal.js@5.1.0/plugin/markdown/markdown.js"></script>
  <script>
    Reveal.initialize({
      hash: true,
      transition: 'slide',
      slideNumber: true,
      plugins: [RevealHighlight, RevealNotes, RevealMarkdown],
      highlight: {
        highlightOnLoad: true
      }
    });
  </script>
</body>
</html>