<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Papers by Vincy - Software Engineering Blog]]></title><description><![CDATA[I'm a software engineer who loves to build things in Elixir/Erlang.]]></description><link>https://papers.vincy.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1701155427100/lxXds2oUu.png</url><title>Papers by Vincy - Software Engineering Blog</title><link>https://papers.vincy.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 12:33:44 GMT</lastBuildDate><atom:link href="https://papers.vincy.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Overengineered #001: Hello World]]></title><description><![CDATA[👋
Welcome to the first episode of Overengineered, a blog series I’m starting where we take simple problems and build ridiculously complex and unnecessary solutions — all for the sake of learning and fun. Most of the solutions will be written in Elix...]]></description><link>https://papers.vincy.dev/overengineered-001-hello-world</link><guid isPermaLink="true">https://papers.vincy.dev/overengineered-001-hello-world</guid><category><![CDATA[Elixir]]></category><category><![CDATA[Erlang]]></category><category><![CDATA[distributed system]]></category><category><![CDATA[OTP]]></category><category><![CDATA[overengineering]]></category><dc:creator><![CDATA[Vince Urag]]></dc:creator><pubDate>Mon, 17 Mar 2025 07:09:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742192841352/c3929f0a-1992-434f-9170-78dd46dd9e03.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">👋</div>
<div data-node-type="callout-text">Welcome to the first episode of <strong>Overengineered</strong>, a blog series I’m starting where we take simple problems and <strong>build ridiculously complex and unnecessary solutions</strong> — all for the sake of learning and fun. Most of the solutions will be written in Elixir but feel free to join the fun using any other language!</div>
</div>

<p>Every journey into learning a programming language starts with a “Hello World.” I remember my first time compiling and executing my first program. It was exhilarating. It gave me a sense of accomplishment… even though it’s just a terminal that shows “Hello World.” Now, let’s revisit the same problem, equipped with the knowledge and experience we've gained along the way.</p>
<h1 id="heading-the-problem">The Problem</h1>
<p><strong>Hello World</strong> in programming is just printing the phrase “Hello World”. Here’s how to do it in some languages:</p>
<pre><code class="lang-plaintext">// Elixir
IO.puts("Hello World")

// Python
print("Hello World")

// Ruby
puts "Hello World"

// Java
System.out.println("Hello World");
</code></pre>
<p>But, where’s the fun in that? In this article, we’ll be building a <strong>distributed Hello World system</strong>:</p>
<ul>
<li><p>multiple distributed nodes</p>
</li>
<li><p>auto-discovery of nodes</p>
</li>
<li><p>whenever a node joins, all the other nodes will send a “hello world” message to it</p>
</li>
</ul>
<h1 id="heading-project-setup">Project Setup</h1>
<p>To kick off, let’s create an Elixir project with a supervision tree and application callback:</p>
<pre><code class="lang-plaintext">➜ mix new distributed_hello_world --sup
</code></pre>
<p>We want to generate it like that since we will be creating <strong>GenServers</strong> that executes instructions on application boot up.</p>
<h1 id="heading-node-auto-discovery">Node Auto-discovery</h1>
<p>One of our requirements is for nodes to automatically discover and connect to each other. There should be no manual <code>Node.connect/1</code> calls whenever there’s a new node. There are couple of libraries we could use for this like <a target="_blank" href="https://github.com/bitwalker/libcluster"><code>libcluster</code></a> which supports a bunch of strategies. If you’re looking for a robust and well-tested clustering mechanism, you should take a look at those. But since we are overengineering here, we would roll our own using the idea from libcluster’s <a target="_blank" href="https://hexdocs.pm/libcluster/Cluster.Strategy.Gossip.html">Gossip strategy</a>.</p>
<h2 id="heading-how-it-works">How it works</h2>
<p>For our clustering mechanism, we’ll use <strong>UDP broadcast</strong> for node discovery. We’ll use <strong>UDP (User Datagram Protocol)</strong> to send packets over the network without needing a connection. It’s perfect for fire-and-forget type of messages like “Hey, I’m here”.</p>
<p>When we use UDP broadcast, we send a packet to everyone on the local network by targeting the <em>limited broadcast address</em> (255.255.255.255). All nodes that listen to the same UDP port will receive the message.</p>
<h2 id="heading-nodemanager">NodeManager</h2>
<p>Since we have our strategy laid out, let’s start building it. We’ll be dealing with UDP so we need to use Erlang’s <a target="_blank" href="https://www.erlang.org/docs/26/man/gen_udp"><code>gen_udp</code></a> module. To send and receive UDP packets, we need to call <code>:gen_udp.open/2</code> which opens a UDP socket bound to a port. We need to make sure that all the nodes send and listen to the same UDP port. For this example, we’ll use the port <code>45826</code>.</p>
<p>Once we have the socket, we would be able to broadcast messages via <code>:gen_udp.send/4</code>.</p>
<p>Let’s play with it a little bit. Fire up your IEx shell.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">1</span>)&gt; {<span class="hljs-symbol">:ok</span>, socket} = <span class="hljs-symbol">:gen_udp</span>.open(<span class="hljs-number">45826</span>, [<span class="hljs-symbol">:binary</span>, <span class="hljs-symbol">active:</span> <span class="hljs-keyword">true</span>, <span class="hljs-symbol">broadcast:</span> <span class="hljs-keyword">true</span>, <span class="hljs-symbol">reuseport:</span> <span class="hljs-keyword">true</span>])
{<span class="hljs-symbol">:ok</span>, <span class="hljs-comment">#Port&lt;0.3&gt;}</span>
iex(<span class="hljs-number">2</span>)&gt; <span class="hljs-symbol">:gen_udp</span>.send(socket, {<span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">255</span>}, <span class="hljs-number">45826</span>, <span class="hljs-string">"HELLO WORLD"</span>)
<span class="hljs-symbol">:ok</span>
iex(<span class="hljs-number">3</span>)&gt; flush
{<span class="hljs-symbol">:udp</span>, <span class="hljs-comment">#Port&lt;0.3&gt;, {192, 168, 68, 61}, 45826, "HELLO WORLD"}</span>
<span class="hljs-symbol">:ok</span>
</code></pre>
<p>Once you open a UDP socket, it associates the port number to the calling process so all the packets will arrive in the calling process’ mailbox.</p>
<p>We want the node auto-discovery and connection to happen on application startup. To achieve this, we’ll create a <strong>GenServer</strong> that opens a UDP socket upon initialization.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">DistributedHelloWorld.NodeManager</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> GenServer

  <span class="hljs-variable">@port</span> <span class="hljs-number">45826</span>
  <span class="hljs-variable">@heartbeat_interval</span> <span class="hljs-number">2_000</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(_) <span class="hljs-keyword">do</span>
    GenServer.start_link(__MODULE__, %{}, <span class="hljs-symbol">name:</span> __MODULE__)
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(_) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:ok</span>, udp_socket} =
      <span class="hljs-symbol">:gen_udp</span>.open(<span class="hljs-variable">@port</span>, [<span class="hljs-symbol">:binary</span>, <span class="hljs-symbol">active:</span> <span class="hljs-keyword">true</span>, <span class="hljs-symbol">broadcast:</span> <span class="hljs-keyword">true</span>, <span class="hljs-symbol">reuseport:</span> <span class="hljs-keyword">true</span>])

    {<span class="hljs-symbol">:ok</span>, %{<span class="hljs-symbol">socket:</span> udp_socket}, {<span class="hljs-symbol">:continue</span>, <span class="hljs-symbol">:heartbeat</span>}}
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Now that the UDP socket is open, we can start sending and receiving packets. Since UDP messages arrive in the calling process’ mailbox, we can use <code>handle_info/2</code> to receive it. Let’s also create a catch-all <code>handle_info/2</code> and inspect its messages.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">DistributedHelloWorld.NodeManager</span></span> <span class="hljs-keyword">do</span>
  ...

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_continue</span></span>(<span class="hljs-symbol">:heartbeat</span>, state) <span class="hljs-keyword">do</span>
    send(<span class="hljs-keyword">self</span>(), <span class="hljs-symbol">:heartbeat</span>)
    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>(<span class="hljs-symbol">:heartbeat</span>, state) <span class="hljs-keyword">do</span>
    <span class="hljs-symbol">:gen_udp</span>.send(state.socket, {<span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">255</span>}, <span class="hljs-variable">@port</span>, <span class="hljs-symbol">:erlang</span>.term_to_binary(node()))

    Process.send_after(<span class="hljs-keyword">self</span>(), <span class="hljs-symbol">:heartbeat</span>, <span class="hljs-variable">@heartbeat_interval</span>)

    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>(msg, state) <span class="hljs-keyword">do</span>
    IO.inspect(msg)
    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  ...
<span class="hljs-keyword">end</span>
</code></pre>
<p>Run the app while providing a name:</p>
<pre><code class="lang-elixir">➜ iex --sname alice -S mix

Erlang/OTP <span class="hljs-number">27</span> [erts<span class="hljs-number">-15.0</span>] [source] [<span class="hljs-number">64</span>-bit] [<span class="hljs-symbol">smp:</span><span class="hljs-number">14:14</span>] [<span class="hljs-symbol">ds:</span><span class="hljs-number">14:14</span><span class="hljs-symbol">:</span><span class="hljs-number">10</span>] [async-<span class="hljs-symbol">threads:</span><span class="hljs-number">1</span>] [jit]

Compiling <span class="hljs-number">1</span> file (.ex)
Interactive Elixir (<span class="hljs-number">1.17</span>.<span class="hljs-number">2</span>) - press Ctrl+C to exit (type h() ENTER <span class="hljs-keyword">for</span> help)
{<span class="hljs-symbol">:udp</span>, <span class="hljs-comment">#Port&lt;0.5&gt;, {192, 168, 68, 61}, 45826,</span>
 &lt;&lt;<span class="hljs-number">131</span>, <span class="hljs-number">119</span>, <span class="hljs-number">10</span>, <span class="hljs-number">97</span>, <span class="hljs-number">108</span>, <span class="hljs-number">105</span>, <span class="hljs-number">99</span>, <span class="hljs-number">101</span>, <span class="hljs-number">64</span>, <span class="hljs-number">90</span>, <span class="hljs-number">101</span>, <span class="hljs-number">117</span>, <span class="hljs-number">115</span>&gt;&gt;}
</code></pre>
<p>As you may have noticed, the calling node also received the UDP message. This is because we are broadcasting it to everyone on the local network… including us. Let’s handle the message. Since we now have the node name, we should be able to connect to it.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">DistributedHelloWorld.NodeManager</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> GenServer

  ...

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>({<span class="hljs-symbol">:udp</span>, _, _, _, node_bin}, state) <span class="hljs-keyword">do</span>
    node = <span class="hljs-symbol">:erlang</span>.binary_to_term(node_bin)

    if node() != node &amp;&amp; node <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> Node.list() <span class="hljs-keyword">do</span>
      Node.connect(node)
    <span class="hljs-keyword">end</span>

    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  ...
<span class="hljs-keyword">end</span>
</code></pre>
<p>Make sure to add this <strong>GenServer</strong> to the supervision tree.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">DistributedHelloWorld.Application</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> Application

  <span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start</span></span>(_type, _args) <span class="hljs-keyword">do</span>
    children = [
      DistributedHelloWorld.NodeManager
    ]

    opts = [<span class="hljs-symbol">strategy:</span> <span class="hljs-symbol">:one_for_one</span>, <span class="hljs-symbol">name:</span> DistributedHelloWorld.Supervisor]
    Supervisor.start_link(children, opts)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Let’s test this out by spinning up two nodes, <code>alice</code> and <code>bob</code>. They should be connected automatically.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: alice</span>
➜ iex --sname alice -S mix
iex(alice<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt; Node.list()
[<span class="hljs-symbol">:bob</span><span class="hljs-variable">@Zeus</span>]

<span class="hljs-comment"># Node: bob</span>
➜ iex --sname bob -S mix
iex(bob<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt; Node.list()
[<span class="hljs-symbol">:alice</span><span class="hljs-variable">@Zeus</span>]
</code></pre>
<h2 id="heading-a-bit-of-a-problem">A bit of a problem…</h2>
<p>Run <code>alice</code> node again then open up another terminal. On this new terminal, issue this command:</p>
<pre><code class="lang-bash">➜ <span class="hljs-built_in">echo</span> <span class="hljs-string">"Test"</span> | nc -u 127.0.0.1 45826
</code></pre>
<p>You’ll see that there’s an error that happened in <code>alice</code> node.</p>
<pre><code class="lang-elixir">➜ iex --sname alice -S mix
Erlang/OTP <span class="hljs-number">27</span> [erts<span class="hljs-number">-15.0</span>] [source] [<span class="hljs-number">64</span>-bit] [<span class="hljs-symbol">smp:</span><span class="hljs-number">14:14</span>] [<span class="hljs-symbol">ds:</span><span class="hljs-number">14:14</span><span class="hljs-symbol">:</span><span class="hljs-number">10</span>] [async-<span class="hljs-symbol">threads:</span><span class="hljs-number">1</span>] [jit]

Interactive Elixir (<span class="hljs-number">1.17</span>.<span class="hljs-number">2</span>) - press Ctrl+C to exit (type h() ENTER <span class="hljs-keyword">for</span> help)

<span class="hljs-number">13:20</span><span class="hljs-symbol">:</span>04.<span class="hljs-number">779</span> [error] GenServer DistributedHelloWorld.NodeManager terminating
** (ArgumentError) errors were found at the given <span class="hljs-symbol">arguments:</span>

  * <span class="hljs-number">1</span>st <span class="hljs-symbol">argument:</span> invalid external representation of a term

    <span class="hljs-symbol">:erlang</span>.binary_to_term(<span class="hljs-string">"Test\n"</span>)
    (distributed_hello_world 0.<span class="hljs-number">1.0</span>) lib/distributed_hello_world/<span class="hljs-symbol">node_manager.ex:</span><span class="hljs-number">32</span>: DistributedHelloWorld.NodeManager.handle_info/<span class="hljs-number">2</span>
    (stdlib <span class="hljs-number">6.0</span>) <span class="hljs-symbol">gen_server.erl:</span><span class="hljs-number">2173</span>: <span class="hljs-symbol">:gen_server</span>.try_handle_info/<span class="hljs-number">3</span>
    (stdlib <span class="hljs-number">6.0</span>) <span class="hljs-symbol">gen_server.erl:</span><span class="hljs-number">2261</span>: <span class="hljs-symbol">:gen_server</span>.handle_msg/<span class="hljs-number">6</span>
    (stdlib <span class="hljs-number">6.0</span>) <span class="hljs-symbol">proc_lib.erl:</span><span class="hljs-number">329</span>: <span class="hljs-symbol">:proc_lib</span>.init_p_do_apply/<span class="hljs-number">3</span>
Last <span class="hljs-symbol">message:</span> {<span class="hljs-symbol">:udp</span>, <span class="hljs-comment">#Port&lt;0.5&gt;, {127, 0, 0, 1}, 62202, "Test\n"}</span>
<span class="hljs-symbol">State:</span> %{<span class="hljs-symbol">socket:</span> <span class="hljs-comment">#Port&lt;0.5&gt;}</span>
</code></pre>
<p>This happens because our app listens to a specific UDP port. Our app will receive all the packets sent to that port. We can solve this by <strong>scoping</strong>. To scope the message, we’ll just prefix it with <code>node::</code>.</p>
<pre><code class="lang-elixir">...
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>(<span class="hljs-symbol">:heartbeat</span>, state) <span class="hljs-keyword">do</span>
    <span class="hljs-symbol">:gen_udp</span>.send(
      state.socket,
      {<span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">255</span>},
      <span class="hljs-variable">@port</span>,
      <span class="hljs-string">"node::"</span> &lt;&gt; <span class="hljs-symbol">:erlang</span>.term_to_binary(node())
    )

    Process.send_after(<span class="hljs-keyword">self</span>(), <span class="hljs-symbol">:heartbeat</span>, <span class="hljs-variable">@heartbeat_interval</span>)

    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>({<span class="hljs-symbol">:udp</span>, _, _, _, &lt;&lt;<span class="hljs-string">"node::"</span>, node_bin::binary&gt;&gt;}, state) <span class="hljs-keyword">do</span>
    node = <span class="hljs-symbol">:erlang</span>.binary_to_term(node_bin)

    if node() != node &amp;&amp; node <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> Node.list() <span class="hljs-keyword">do</span>
      Node.connect(node)
    <span class="hljs-keyword">end</span>

    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>({<span class="hljs-symbol">:udp</span>, _socket, _ip, _port, _}, state) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>
...
</code></pre>
<p>Now, our app only listens and reacts to packets that are prefixed with <code>node::</code> and ignores the others.</p>
<p>Here’s the full <strong>NodeManager</strong> code:</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">DistributedHelloWorld.NodeManager</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> GenServer

  <span class="hljs-variable">@port</span> <span class="hljs-number">45826</span>
  <span class="hljs-variable">@heartbeat_interval</span> <span class="hljs-number">2_000</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(_) <span class="hljs-keyword">do</span>
    GenServer.start_link(__MODULE__, %{}, <span class="hljs-symbol">name:</span> __MODULE__)
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(_) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:ok</span>, udp_socket} =
      <span class="hljs-symbol">:gen_udp</span>.open(<span class="hljs-variable">@port</span>, [<span class="hljs-symbol">:binary</span>, <span class="hljs-symbol">active:</span> <span class="hljs-keyword">true</span>, <span class="hljs-symbol">broadcast:</span> <span class="hljs-keyword">true</span>, <span class="hljs-symbol">reuseport:</span> <span class="hljs-keyword">true</span>])

    {<span class="hljs-symbol">:ok</span>, %{<span class="hljs-symbol">socket:</span> udp_socket}, {<span class="hljs-symbol">:continue</span>, <span class="hljs-symbol">:heartbeat</span>}}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_continue</span></span>(<span class="hljs-symbol">:heartbeat</span>, state) <span class="hljs-keyword">do</span>
    send(<span class="hljs-keyword">self</span>(), <span class="hljs-symbol">:heartbeat</span>)
    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>(<span class="hljs-symbol">:heartbeat</span>, state) <span class="hljs-keyword">do</span>
    <span class="hljs-symbol">:gen_udp</span>.send(
      state.socket,
      {<span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">255</span>},
      <span class="hljs-variable">@port</span>,
      <span class="hljs-string">"node::"</span> &lt;&gt; <span class="hljs-symbol">:erlang</span>.term_to_binary(node())
    )

    Process.send_after(<span class="hljs-keyword">self</span>(), <span class="hljs-symbol">:heartbeat</span>, <span class="hljs-variable">@heartbeat_interval</span>)

    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>({<span class="hljs-symbol">:udp</span>, _, _, _, &lt;&lt;<span class="hljs-string">"node::"</span>, node_bin::binary&gt;&gt;}, state) <span class="hljs-keyword">do</span>
    node = <span class="hljs-symbol">:erlang</span>.binary_to_term(node_bin)

    if node() != node &amp;&amp; node <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> Node.list() <span class="hljs-keyword">do</span>
      Node.connect(node)
    <span class="hljs-keyword">end</span>

    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>({<span class="hljs-symbol">:udp</span>, _socket, _ip, _port, _}, state) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Nice! All nodes automatically discover and connect to each other. Now let’s move on to the next part, the <strong>Greeter</strong>.</p>
<h1 id="heading-hello-world-folks">Hello World, folks</h1>
<p>Once a new node joins a cluster, it will greet all the nodes and get some greetings too. Just like the previous part, we’ll also be building a <strong>GenServer</strong>.</p>
<h2 id="heading-the-greeter">The Greeter</h2>
<p>For us to know when a node joins the cluster, we need to monitor the nodes. We can do this by using <code>:net_kernel.monitor_nodes(true)</code>.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">DistributedHelloWorld.Greeter</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> GenServer

  <span class="hljs-keyword">require</span> Logger

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(_) <span class="hljs-keyword">do</span>
    GenServer.start_link(__MODULE__, %{}, <span class="hljs-symbol">name:</span> __MODULE__)
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(_arg) <span class="hljs-keyword">do</span>
    <span class="hljs-symbol">:net_kernel</span>.monitor_nodes(<span class="hljs-keyword">true</span>)

    {<span class="hljs-symbol">:ok</span>, %{}}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>(msg, state) <span class="hljs-keyword">do</span>
    IO.inspect(msg)

    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Let’s see what messages we receive when we monitor the nodes.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: alice</span>
iex --sname alice -S mix
{<span class="hljs-symbol">:nodeup</span>, <span class="hljs-symbol">:bob</span><span class="hljs-variable">@Zeus</span>}

<span class="hljs-comment"># Node: bob</span>
➜ iex --sname bob -S mix
{<span class="hljs-symbol">:nodeup</span>, <span class="hljs-symbol">:alice</span><span class="hljs-variable">@Zeus</span>}

<span class="hljs-comment"># alice disconnects...</span>
{<span class="hljs-symbol">:nodedown</span>, <span class="hljs-symbol">:alice</span><span class="hljs-variable">@Zeus</span>}
</code></pre>
<p>To fulfill the requirements, we just need to send a greeting message to the node when we receive the <code>:nodeup</code> message. And whenever we receive the <code>:greet</code> message, we print it via Logger along with the name of the node that sent it.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">DistributedHelloWorld.Greeter</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> GenServer

  ...

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>({<span class="hljs-symbol">:nodeup</span>, node}, state) <span class="hljs-keyword">do</span>
    GenServer.cast({__MODULE__, node}, {<span class="hljs-symbol">:greet</span>, node()})

    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_cast</span></span>({<span class="hljs-symbol">:greet</span>, node}, state) <span class="hljs-keyword">do</span>
    Logger.info(<span class="hljs-string">"Hello world from "</span> &lt;&gt; inspect(node))

    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  ...
<span class="hljs-keyword">end</span>
</code></pre>
<p>Here’s the full code of the <strong>Greeter</strong> module.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">DistributedHelloWorld.Greeter</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> GenServer

  <span class="hljs-keyword">require</span> Logger

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(_) <span class="hljs-keyword">do</span>
    GenServer.start_link(__MODULE__, %{}, <span class="hljs-symbol">name:</span> __MODULE__)
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(_arg) <span class="hljs-keyword">do</span>
    <span class="hljs-symbol">:net_kernel</span>.monitor_nodes(<span class="hljs-keyword">true</span>)

    {<span class="hljs-symbol">:ok</span>, %{}}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>({<span class="hljs-symbol">:nodeup</span>, node}, state) <span class="hljs-keyword">do</span>
    GenServer.cast({__MODULE__, node}, {<span class="hljs-symbol">:greet</span>, node()})

    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>({<span class="hljs-symbol">:nodedown</span>, _}, state) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_cast</span></span>({<span class="hljs-symbol">:greet</span>, node}, state) <span class="hljs-keyword">do</span>
    Logger.info(<span class="hljs-string">"Hello world from "</span> &lt;&gt; inspect(node))

    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Just like the NodeManager, make sure to add it to the supervision tree.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">DistributedHelloWorld.Application</span></span> <span class="hljs-keyword">do</span>
  ...

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start</span></span>(_type, _args) <span class="hljs-keyword">do</span>
    children = [
      DistributedHelloWorld.Greeter,
      DistributedHelloWorld.NodeManager
    ]

    opts = [<span class="hljs-symbol">strategy:</span> <span class="hljs-symbol">:one_for_one</span>, <span class="hljs-symbol">name:</span> DistributedHelloWorld.Supervisor]
    Supervisor.start_link(children, opts)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Let’s test our Distributed Hello World app by spinning up nodes <code>alice</code> and <code>bob</code>.</p>
<pre><code class="lang-elixir">➜ iex --sname alice -S mix
Erlang/OTP <span class="hljs-number">27</span> [erts<span class="hljs-number">-15.0</span>] [source] [<span class="hljs-number">64</span>-bit] [<span class="hljs-symbol">smp:</span><span class="hljs-number">14:14</span>] [<span class="hljs-symbol">ds:</span><span class="hljs-number">14:14</span><span class="hljs-symbol">:</span><span class="hljs-number">10</span>] [async-<span class="hljs-symbol">threads:</span><span class="hljs-number">1</span>] [jit]

Compiling <span class="hljs-number">1</span> file (.ex)
Interactive Elixir (<span class="hljs-number">1.17</span>.<span class="hljs-number">2</span>) - press Ctrl+C to exit (type h() ENTER <span class="hljs-keyword">for</span> help)
iex(alice<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt;
</code></pre>
<p>Nothing happened yet. Now let’s observe what happens when we run <code>bob</code>.</p>
<pre><code class="lang-elixir">➜ iex --sname bob -S mix
Erlang/OTP <span class="hljs-number">27</span> [erts<span class="hljs-number">-15.0</span>] [source] [<span class="hljs-number">64</span>-bit] [<span class="hljs-symbol">smp:</span><span class="hljs-number">14:14</span>] [<span class="hljs-symbol">ds:</span><span class="hljs-number">14:14</span><span class="hljs-symbol">:</span><span class="hljs-number">10</span>] [async-<span class="hljs-symbol">threads:</span><span class="hljs-number">1</span>] [jit]

Interactive Elixir (<span class="hljs-number">1.17</span>.<span class="hljs-number">2</span>) - press Ctrl+C to exit (type h() ENTER <span class="hljs-keyword">for</span> help)

00<span class="hljs-symbol">:</span><span class="hljs-number">59:02</span>.<span class="hljs-number">539</span> [info] Hello world from <span class="hljs-symbol">:alice</span><span class="hljs-variable">@Zeus</span>
iex(bob<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt;
</code></pre>
<p>Go back to <code>alice</code> and you’ll notice that it also received a greeting from <code>bob</code>.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: alice</span>
00<span class="hljs-symbol">:</span><span class="hljs-number">59:02</span>.<span class="hljs-number">538</span> [info] Hello world from <span class="hljs-symbol">:bob</span><span class="hljs-variable">@Zeus</span>
</code></pre>
<p>Now let’s see what happens when a third node joins the cluster. Enter <code>charlie</code>…</p>
<pre><code class="lang-elixir">➜ iex --sname charlie -S mix
Erlang/OTP <span class="hljs-number">27</span> [erts<span class="hljs-number">-15.0</span>] [source] [<span class="hljs-number">64</span>-bit] [<span class="hljs-symbol">smp:</span><span class="hljs-number">14:14</span>] [<span class="hljs-symbol">ds:</span><span class="hljs-number">14:14</span><span class="hljs-symbol">:</span><span class="hljs-number">10</span>] [async-<span class="hljs-symbol">threads:</span><span class="hljs-number">1</span>] [jit]

Interactive Elixir (<span class="hljs-number">1.17</span>.<span class="hljs-number">2</span>) - press Ctrl+C to exit (type h() ENTER <span class="hljs-keyword">for</span> help)

01<span class="hljs-symbol">:</span>02<span class="hljs-symbol">:</span><span class="hljs-number">19.497</span> [info] Hello world from <span class="hljs-symbol">:alice</span><span class="hljs-variable">@Zeus</span>

01<span class="hljs-symbol">:</span>02<span class="hljs-symbol">:</span><span class="hljs-number">19.498</span> [info] Hello world from <span class="hljs-symbol">:bob</span><span class="hljs-variable">@Zeus</span>
iex(charlie<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt;
</code></pre>
<p>It got greetings from both <code>alice</code> and <code>bob</code>. Go back to both <code>alice</code> and <code>bob</code> nodes and see how it reacted.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: alice</span>
01<span class="hljs-symbol">:</span>02<span class="hljs-symbol">:</span><span class="hljs-number">19.494</span> [info] Hello world from <span class="hljs-symbol">:charlie</span><span class="hljs-variable">@Zeus</span>

<span class="hljs-comment"># Node: bob</span>
01<span class="hljs-symbol">:</span>02<span class="hljs-symbol">:</span><span class="hljs-number">19.498</span> [info] Hello world from <span class="hljs-symbol">:charlie</span><span class="hljs-variable">@Zeus</span>
</code></pre>
<h1 id="heading-wrapping-up">Wrapping Up</h1>
<p>So there you have it — a <strong>multi-node Hello World</strong>.</p>
<p>What did we actually do here?</p>
<ul>
<li><p>built our own node-discovery mechanism via UDP broadcast</p>
</li>
<li><p>Node monitoring</p>
</li>
<li><p>an app that says hello to everyone in the cluster</p>
</li>
</ul>
<p>Was it overkill? Absolutely — and that’s the point of this series!</p>
<p>Again, this is <strong>just for fun and learning</strong>. If you need a <strong>robust node-discovery mechanism</strong>, check out <a target="_blank" href="https://github.com/bitwalker/libcluster"><code>libcluster</code></a>.</p>
<p>Link to full source code: <a target="_blank" href="https://github.com/vinceurag/distributed_hello_world">https://github.com/vinceurag/distributed_hello_world</a></p>
<p>Awesome! I hope you had fun reading and following along this first <strong>Overengineered</strong> post. I’ll be making more of this so if you want this kind of content, feel free to subscribe to my newsletter. For suggestions/feedback, please use this <a target="_blank" href="https://vin.cy/overengineered-form">form</a>. If you want to join the fun and take a jab at the problem yourself, please tag me — I'd love to see what you come up with!</p>
<p>Happy overengineering and see you on the next one!</p>
]]></content:encoded></item><item><title><![CDATA[Distributed PubSub in Elixir]]></title><description><![CDATA[In this article, we’ll be exploring on building a distributed PubSub in Elixir. First, we’ll be building a local PubSub which will allow a process to broadcast a message to all local subscribers. Then, we’ll see how we can make it work on a distribut...]]></description><link>https://papers.vincy.dev/distributed-pubsub-in-elixir</link><guid isPermaLink="true">https://papers.vincy.dev/distributed-pubsub-in-elixir</guid><category><![CDATA[Elixir]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[Phoenix framework]]></category><category><![CDATA[Erlang]]></category><category><![CDATA[PubSub]]></category><dc:creator><![CDATA[Vince Urag]]></dc:creator><pubDate>Fri, 27 Sep 2024 12:44:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727439548775/4ac1e861-a1ea-48c6-9404-d9bed22e224f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, we’ll be exploring on building a distributed PubSub in Elixir. First, we’ll be building a local PubSub which will allow a process to broadcast a message to all local subscribers. Then, we’ll see how we can make it work on a distributed environment, taking inspiration from the battle-tested <a target="_blank" href="https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html">Phoenix.PubSub</a> — which you should definitely use if you’re planning to use PubSub in your production app. We’ll be using concepts like Registry, Process Groups, Clusters, etc.</p>
<h1 id="heading-what-is-pubsub">What is PubSub?</h1>
<p>PubSub (Publish/Subscribe) is a design pattern used for asynchronous communication between services. Instead of a process sending a message directly to another process, the <strong>publisher</strong> broadcasts a message with a topic and let <strong>susbcribers</strong> of that topic consume the message. This allows us to build loosely coupled services. To visualize this, let’s take a look at a real-world example, <em>radio stations</em>.</p>
<p>In this example, a radio station is considered a <strong>publisher</strong>. It broadcasts music, podcasts, or whatever through its assigned frequency. When we want to listen to a banger music, we tune in to our favorite radio station’s frequency. The radio station is not directly playing the music to us. It’s just <strong>publishing</strong> (broadcasting) to its assigned frequency, and us, being avid listeners, are <strong>subscribed</strong> (tuned in) to that frequency.</p>
<h1 id="heading-local-pubsub">Local PubSub</h1>
<p>Now, let us build our local PubSub. In this article, we’ll be naming our PubSub system <a target="_blank" href="https://en.wikipedia.org/wiki/Hermes"><strong>Hermes</strong></a>, for obvious reasons.</p>
<pre><code class="lang-elixir">mix new hermes --sup
</code></pre>
<p>In a PubSub pattern, we need a mechanism where we could determine which processes are subscribed to which topics. In other words, we need to have a key-value storage for our processes. Fortunately, Elixir already provides this under the <a target="_blank" href="https://hexdocs.pm/elixir/1.12/Registry.html">Registry</a> module.</p>
<h2 id="heading-registry">Registry</h2>
<p>Registry is a local key-value process storage. It allows you to register the current process under a given key. To understand it better, let’s play with it.</p>
<p>First, let’s start the <code>Registry</code> process.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">1</span>)&gt; Registry.start_link(<span class="hljs-symbol">keys:</span> <span class="hljs-symbol">:duplicate</span>, <span class="hljs-symbol">name:</span> MyPubSub)
{<span class="hljs-symbol">:ok</span>, <span class="hljs-comment">#PID&lt;0.105.0&gt;}</span>
</code></pre>
<p><code>keys: :duplicate</code> just means that we allow multiple processes to be registered under a single key. This is exactly what we need for a PubSub, one key/topic for multiple processes.</p>
<p>Now, let’s create a bunch of <em>subscribers</em> and register them to our <code>MyPubSub</code> process registry. These processes would be registered under the key/topic, <code>:event_topic</code>. You can use any Elixir term as a topic. Depending on your use case, it’s usually an action that happened. For example, <code>:"user.updated"</code>.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">2</span>)&gt; <span class="hljs-keyword">for</span> _i &lt;- <span class="hljs-number">1</span>..<span class="hljs-number">3</span> <span class="hljs-keyword">do</span>
...(<span class="hljs-number">2</span>)&gt;     spawn(<span class="hljs-keyword">fn</span> -&gt;
...(<span class="hljs-number">2</span>)&gt;       Registry.register(MyPubSub, <span class="hljs-symbol">:event_topic</span>, <span class="hljs-keyword">nil</span>)
...(<span class="hljs-number">2</span>)&gt;
...(<span class="hljs-number">2</span>)&gt;       receive <span class="hljs-keyword">do</span>
...(<span class="hljs-number">2</span>)&gt;         message -&gt;
...(<span class="hljs-number">2</span>)&gt;           IO.puts(<span class="hljs-string">"<span class="hljs-subst">#{inspect(<span class="hljs-keyword">self</span>())}</span> received: <span class="hljs-subst">#{inspect(message)}</span>"</span>)
...(<span class="hljs-number">2</span>)&gt;       <span class="hljs-keyword">end</span>
...(<span class="hljs-number">2</span>)&gt;     <span class="hljs-keyword">end</span>)
...(<span class="hljs-number">2</span>)&gt;   <span class="hljs-keyword">end</span>
[<span class="hljs-comment">#PID&lt;0.107.0&gt;, #PID&lt;0.108.0&gt;, #PID&lt;0.109.0&gt;]</span>
</code></pre>
<p>Now that we have subscribers, we can try and broadcast a message to them. The Registry module has a function called <a target="_blank" href="https://hexdocs.pm/elixir/1.12/Registry.html#dispatch/4"><code>dispatch/4</code></a> which we could definitely use in this scenario. <code>dispatch/4</code> will iterate over the entries (in our case, subscribers) and do what we asked it to do.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">3</span>)&gt; Registry.dispatch(MyPubSub, <span class="hljs-symbol">:event_topic</span>, <span class="hljs-keyword">fn</span> entries -&gt;
...(<span class="hljs-number">3</span>)&gt;   <span class="hljs-keyword">for</span> {pid, _} &lt;- entries, <span class="hljs-symbol">do:</span> send(pid, {<span class="hljs-symbol">:hello</span>, <span class="hljs-string">"world"</span>})
...(<span class="hljs-number">3</span>)&gt; <span class="hljs-keyword">end</span>)
<span class="hljs-symbol">:ok</span>
<span class="hljs-comment">#PID&lt;0.109.0&gt; received: {:hello, "world"}</span>
<span class="hljs-comment">#PID&lt;0.108.0&gt; received: {:hello, "world"}</span>
<span class="hljs-comment">#PID&lt;0.107.0&gt; received: {:hello, "world"}</span>
</code></pre>
<p>Voila! We were able to broadcast a message to all registered process under the key/topic. We just demoed PubSub in our <code>iex</code> shell. Now let’s make it pretty and put it in our app, Hermes.</p>
<p>We want the <code>Registry</code> to start as soon as we start our app. So let’s put it in our app’s supervisor.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># lib/hermes/application.ex</span>
...
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start</span></span>(_type, _args) <span class="hljs-keyword">do</span>
  children = [
    {Registry, <span class="hljs-symbol">keys:</span> <span class="hljs-symbol">:duplicate</span>, <span class="hljs-symbol">name:</span> Hermes.Registry}
  ]

  opts = [<span class="hljs-symbol">strategy:</span> <span class="hljs-symbol">:one_for_one</span>, <span class="hljs-symbol">name:</span> Hermes.Supervisor]
  Supervisor.start_link(children, opts)
<span class="hljs-keyword">end</span>
...
</code></pre>
<p>Our Hermes module should have two functions, <code>subscribe/1</code> and <code>publish/2</code>. <code>subscribe/1</code> will subscribe the process to the topic while <code>publish/2</code> will broadcast a message to all subscribers of that topic.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># lib/hermes.ex</span>
<span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">Hermes</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-variable">@registry</span> Hermes.Registry

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">subscribe</span></span>(topic) <span class="hljs-keyword">do</span>
    Registry.register(<span class="hljs-variable">@registry</span>, topic, <span class="hljs-keyword">nil</span>)
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">publish</span></span>(topic, message) <span class="hljs-keyword">do</span>
    Registry.dispatch(<span class="hljs-variable">@registry</span>, topic, <span class="hljs-keyword">fn</span> entries -&gt;
      <span class="hljs-keyword">for</span> {pid, _} &lt;- entries, <span class="hljs-symbol">do:</span> send(pid, message)
    <span class="hljs-keyword">end</span>)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Let’s see if our local PubSub still works as expected.</p>
<pre><code class="lang-elixir">iex -S mix

iex(<span class="hljs-number">1</span>)&gt; spawn(<span class="hljs-keyword">fn</span> -&gt;
...(<span class="hljs-number">1</span>)&gt;   Hermes.subscribe(<span class="hljs-symbol">:<span class="hljs-string">"user.updated"</span></span>)
...(<span class="hljs-number">1</span>)&gt;
...(<span class="hljs-number">1</span>)&gt;   receive <span class="hljs-keyword">do</span>
...(<span class="hljs-number">1</span>)&gt;     message -&gt;
...(<span class="hljs-number">1</span>)&gt;       IO.puts(<span class="hljs-string">"<span class="hljs-subst">#{inspect(<span class="hljs-keyword">self</span>())}</span> received: <span class="hljs-subst">#{inspect(message)}</span>"</span>)
...(<span class="hljs-number">1</span>)&gt;   <span class="hljs-keyword">end</span>
...(<span class="hljs-number">1</span>)&gt; <span class="hljs-keyword">end</span>)
<span class="hljs-comment">#PID&lt;0.167.0&gt;</span>
iex(<span class="hljs-number">2</span>)&gt; Hermes.publish(<span class="hljs-symbol">:<span class="hljs-string">"user.updated"</span></span>, {<span class="hljs-symbol">:user</span>, %{<span class="hljs-symbol">id:</span> <span class="hljs-number">1</span>, <span class="hljs-symbol">name:</span> <span class="hljs-string">"John"</span>}})
<span class="hljs-symbol">:ok</span>
<span class="hljs-comment">#PID&lt;0.167.0&gt; received: {:user, %{id: 1, name: "John"}}</span>
</code></pre>
<h1 id="heading-distributed-pubsub">Distributed PubSub</h1>
<p>Our current version of Hermes is already good enough… but only for single-node apps. This won’t suffice for a clustered app.</p>
<p>To fact-check this, spin up two instances of the app. For easy identification, we’ll just name our nodes Alice and Bob.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Node: Alice</span>
iex --sname alice -S mix
iex(alice@Zeus)1&gt;

<span class="hljs-comment"># Node: Bob</span>
➜ iex --sname bob -S mix
iex(bob@Zeus)1&gt;
</code></pre>
<p>Connect the two nodes.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Alice</span>
iex(alice<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt; Node.connect(<span class="hljs-symbol">:<span class="hljs-string">"bob@Zeus"</span></span>)
<span class="hljs-symbol">:ok</span>
</code></pre>
<p>Verify that they are connected and then subscribe to the <code>:"user.updated"</code> topic.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Bob</span>
iex(bob<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt; Node.list()
[<span class="hljs-symbol">:alice</span><span class="hljs-variable">@Zeus</span>]
iex(bob<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">2</span>&gt; Hermes.subscribe(<span class="hljs-symbol">:<span class="hljs-string">"user.updated"</span></span>)
{<span class="hljs-symbol">:ok</span>, <span class="hljs-comment">#PID&lt;0.136.0&gt;}</span>
</code></pre>
<p>From Alice’s shell, broadcast a PubSub message.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Alice</span>
iex(alice<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">2</span>&gt; Hermes.publish(<span class="hljs-symbol">:<span class="hljs-string">"user.updated"</span></span>, {<span class="hljs-symbol">:user</span>, %{<span class="hljs-symbol">id:</span> <span class="hljs-number">1</span>, <span class="hljs-symbol">name:</span> <span class="hljs-string">"John"</span>}})
<span class="hljs-symbol">:ok</span>
</code></pre>
<p>Observe Bob’s shell. You’ll notice that nothing happened. Even though they are connected, they don’t share the registry. Yes, they are connected, but it just means they can send and receive messages from other nodes in the cluster.</p>
<h2 id="heading-process-groups">Process Groups</h2>
<p>This is where Erlang’s <a target="_blank" href="https://www.erlang.org/doc/apps/kernel/pg.html">pg</a> module would come in. In its simplest definition, it enables processes in the cluster to be a member of a named group, thus the name <code>pg</code> (process group).</p>
<p>Just like how we started using <code>Registry</code>, let’s play with <code>pg</code> on our <code>iex</code> shell. Spin up two nodes and connect them.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Alice</span>
iex --sname alice
iex(alice<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt;

<span class="hljs-comment"># Node: Bob</span>
➜ iex --sname bob
iex(bob<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt; Node.connect <span class="hljs-symbol">:<span class="hljs-string">"alice@Zeus"</span></span>
<span class="hljs-keyword">true</span>
iex(bob<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">2</span>&gt; Node.list()
[<span class="hljs-symbol">:alice</span><span class="hljs-variable">@Zeus</span>]
</code></pre>
<p>Just like the <code>Registry</code>, we need to to start a <code>pg</code> process on both nodes before we can group processes.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Alice</span>
iex(alice<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt; <span class="hljs-symbol">:pg</span>.start_link()
{<span class="hljs-symbol">:ok</span>, <span class="hljs-comment">#PID&lt;0.114.0&gt;}</span>

<span class="hljs-comment"># Node: Bob</span>
iex(bob<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">3</span>&gt; <span class="hljs-symbol">:pg</span>.start_link()
{<span class="hljs-symbol">:ok</span>, <span class="hljs-comment">#PID&lt;0.116.0&gt;}</span>
</code></pre>
<p>Then on Bob’s shell, join a process group called <code>:my_group</code>.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Bob</span>
iex(bob<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">4</span>&gt; <span class="hljs-symbol">:pg</span>.join(<span class="hljs-symbol">:my_group</span>, <span class="hljs-keyword">self</span>())
<span class="hljs-symbol">:ok</span>
</code></pre>
<p>Bob’s iex shell is now part of the <code>:my_group</code> process group. Go back to Alice’s shell and see all the members of the group.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Alice</span>
iex(alice<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">2</span>&gt; [pid] = <span class="hljs-symbol">:pg</span>.get_members(<span class="hljs-symbol">:my_group</span>)
[<span class="hljs-comment">#PID&lt;13033.110.0&gt;]</span>
iex(alice<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">3</span>&gt; send(pid, {<span class="hljs-symbol">:hello</span>, <span class="hljs-string">"world"</span>})
{<span class="hljs-symbol">:hello</span>, <span class="hljs-string">"world"</span>}

<span class="hljs-comment"># Node: Bob</span>
iex(bob<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">6</span>&gt; flush
{<span class="hljs-symbol">:hello</span>, <span class="hljs-string">"world"</span>}
<span class="hljs-symbol">:ok</span>
</code></pre>
<p>You will see the group’s member process from the remote node. Using the <code>send/1</code> function, you’ll be able to send a message to a process on a remote node.</p>
<p>We can use this for our distributed PubSub, right? Every subscriber process on each node can just join a <code>pg</code> group under the same key. Well, yes… but maybe not so efficient? If there are thousands of subscribers on a remote node, then we will be sending thousands of messages to a remote node. <code>Phoenix.PubSub</code> tackled this by having a designated local server process per node that is responsible for broadcasting to its local subscribers, kind of like a message coordinator. Only the local server processes are added in the <code>pg</code> group. So, in our case, if there are 3 nodes running, there will just be 3 members of the process group.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727438463439/a4fc720f-b73c-49f3-99e4-1b7cd9e49ea7.png" alt class="image--center mx-auto" /></p>
<p>To ease up our life, let’s just use <a target="_blank" href="https://hexdocs.pm/libcluster/readme.html"><code>libcluster</code></a> for clustering.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># mix.exs</span>
...
{<span class="hljs-symbol">:libcluster</span>, <span class="hljs-string">"~&gt; 3.4"</span>}
...
</code></pre>
<p>Since we are just running our nodes on localhost, we can use the <a target="_blank" href="https://hexdocs.pm/libcluster/Cluster.Strategy.LocalEpmd.html"><code>LocalEpmd</code></a> strategy.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># lib/hermes/application.ex</span>
...
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start</span></span>(_type, _args) <span class="hljs-keyword">do</span>
  ...
  topologies = [
    <span class="hljs-symbol">hermes_local:</span> [
      <span class="hljs-symbol">strategy:</span> Cluster.Strategy.LocalEpmd
    ]
  ]

  children = [
    {Cluster.Supervisor, [topologies, [<span class="hljs-symbol">name:</span> Hermes.ClusterSupervisor]]},
    {Registry, <span class="hljs-symbol">keys:</span> <span class="hljs-symbol">:duplicate</span>, <span class="hljs-symbol">name:</span> Hermes.Registry}
  ]
  ...
<span class="hljs-keyword">end</span>
...
</code></pre>
<p>Once you start the app for Alice and Bob, you should see that they are connected automatically.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Bob</span>

➜ iex --sname bob -S mix

<span class="hljs-number">18:30</span><span class="hljs-symbol">:</span><span class="hljs-number">17.705</span> [info] [<span class="hljs-symbol">libcluster:</span>hermes_local] connected to <span class="hljs-symbol">:alice</span><span class="hljs-variable">@Zeus</span>
Interactive Elixir (<span class="hljs-number">1.17</span>.<span class="hljs-number">2</span>) - press Ctrl+C to exit (type h() ENTER <span class="hljs-keyword">for</span> help)
iex(bob<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt; Node.list()
[<span class="hljs-symbol">:alice</span><span class="hljs-variable">@Zeus</span>]
</code></pre>
<p>Now that they are connected automatically, we can focus on building our distributed PubSub.</p>
<p>Let’s implement our local server process as a GenServer and add it to our app’s supervision tree.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># lib/hermes/pg_server.ex</span>
<span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">Hermes.PGServer</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> GenServer

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(_) <span class="hljs-keyword">do</span>
    GenServer.start_link(Hermes.PGServer, [], <span class="hljs-symbol">name:</span> Hermes.PGServer)
  <span class="hljs-keyword">end</span>

  <span class="hljs-variable">@impl</span> GenServer
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(_args) <span class="hljs-keyword">do</span>
    <span class="hljs-symbol">:pg</span>.start_link()
    <span class="hljs-symbol">:pg</span>.join({<span class="hljs-symbol">:hermes</span>, Hermes.PubSub}, <span class="hljs-keyword">self</span>())

    {<span class="hljs-symbol">:ok</span>, []}
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>

<span class="hljs-comment"># lib/hermes/application.ex</span>
...
children = [
  {Cluster.Supervisor, [topologies, [<span class="hljs-symbol">name:</span> Hermes.ClusterSupervisor]]},
  {Registry, <span class="hljs-symbol">keys:</span> <span class="hljs-symbol">:duplicate</span>, <span class="hljs-symbol">name:</span> Hermes.Registry},
  Hermes.PGServer
]
...
</code></pre>
<p>Upon the GenServer’s initialization, it will also start the <code>pg</code> process on its default scope. Then, it will join the process group called <code>{:hermes, Hermes.PubSub}</code>. A process group’s name can be any Erlang term. Recompile both and verify that both Alice’s and Bob’s <code>pg</code> server are members of the <code>{:hermes, Hermes.PubSub}</code> process group.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Alice</span>
iex(alice<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt; <span class="hljs-symbol">:pg</span>.get_members({<span class="hljs-symbol">:hermes</span>, Hermes.PubSub})
[<span class="hljs-comment">#PID&lt;22764.190.0&gt;, #PID&lt;0.208.0&gt;]</span>
</code></pre>
<p>Currently, <code>Hermes.publish/2</code> broadcasts only to local subscribers. Instead of broadcasting to registry entries, we need it to send messages to all the other nodes’ <code>Hermes.PGServer</code> process, which are members of the <code>{:hermes, Hermes.PubSub}</code> process group. Here’s the updated <code>Hermes.PGServer</code> module:</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># lib/hermes.ex</span>
<span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">Hermes</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-variable">@registry</span> Hermes.Registry
  <span class="hljs-variable">@pg_group</span> {<span class="hljs-symbol">:hermes</span>, Hermes.PubSub}

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">subscribe</span></span>(topic) <span class="hljs-keyword">do</span>
    Registry.register(<span class="hljs-variable">@registry</span>, topic, <span class="hljs-keyword">nil</span>)
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">publish</span></span>(topic, message) <span class="hljs-keyword">do</span>
    <span class="hljs-keyword">for</span> pid &lt;- <span class="hljs-symbol">:pg</span>.get_members(<span class="hljs-variable">@pg_group</span>), node(pid) != node() <span class="hljs-keyword">do</span>
      send(pid, {<span class="hljs-symbol">:broadcast_to_local</span>, topic, message})
    <span class="hljs-keyword">end</span>

    broadcast_local(topic, message)
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">broadcast_local</span></span>(topic, message) <span class="hljs-keyword">do</span>
    Registry.dispatch(<span class="hljs-variable">@registry</span>, topic, <span class="hljs-keyword">fn</span> entries -&gt;
      <span class="hljs-keyword">for</span> {pid, _} &lt;- entries, <span class="hljs-symbol">do:</span> send(pid, message)
    <span class="hljs-keyword">end</span>)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p><code>Hermes.publish/2</code> will now send a message to all the clustered nodes’ <code>Hermes.PGServer</code>. That module should be broadcasting it to its local subscribers.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># lib/hermes/pg_server.ex</span>

...
<span class="hljs-variable">@impl</span> GenServer
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>({<span class="hljs-symbol">:broadcast_to_local</span>, topic, message}, state) <span class="hljs-keyword">do</span>
  Hermes.broadcast_local(topic, message)
  {<span class="hljs-symbol">:noreply</span>, state}
<span class="hljs-keyword">end</span>
...
</code></pre>
<p>Once the node’s <code>Hermes.PGServer</code> process receives a message, it should broadcast it locally.</p>
<p>That should be it! You now have a PubSub that could broadcast to remote nodes… a distributed PubSub! 🎉</p>
<p>Before taking it out for a spin, let’s create a very basic client module so we can easily test that our PubSub works. This <code>Client</code> module just starts a GenServer process that subscribes to a specified topic. It will also just print out any messages it receive.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># lib/hermes/support/client.ex</span>
<span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">Client</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> GenServer

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(topic) <span class="hljs-keyword">do</span>
    GenServer.start_link(__MODULE__, topic)
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(topic) <span class="hljs-keyword">do</span>
    Hermes.subscribe(topic)
    {<span class="hljs-symbol">:ok</span>, topic}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_info</span></span>(msg, state) <span class="hljs-keyword">do</span>
    IO.puts(<span class="hljs-string">"Received: <span class="hljs-subst">#{inspect(msg)}</span>"</span>)
    {<span class="hljs-symbol">:noreply</span>, state}
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Now that we have it in place, we can start testing it. I’m feeling a bit adventurous today so we’ll be connecting 3 nodes namely, <code>Alice</code>, <code>Bob</code>, and <code>Carol</code>.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Alice</span>
iex --sname alice -S mix

<span class="hljs-comment"># Node: Bob</span>
iex --sname bob -S mix

<span class="hljs-comment"># Node: Carol</span>
iex --sname carol -S mix
</code></pre>
<p>Since we have configured <code>libcluster</code>, they should automatically discover and connect to each other.</p>
<p>For this test, we’ll have the following:</p>
<ul>
<li><p>Alice:</p>
<ul>
<li><p>1 client subscribed to <code>:"user.created"</code></p>
</li>
<li><p>1 client subscribed to <code>:"user.updated"</code></p>
</li>
</ul>
</li>
<li><p>Bob:</p>
<ul>
<li><p>5 clients subscribed to <code>:"user.created"</code></p>
</li>
<li><p>0 clients subscribed to <code>:"user.updated"</code></p>
</li>
</ul>
</li>
<li><p>Carol:</p>
<ul>
<li><p>0 clients subscribed to <code>:"user.created"</code></p>
</li>
<li><p>2 clients subscribed to <code>:"user.updated"</code></p>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Alice</span>
iex(alice<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt; Client.start_link(<span class="hljs-symbol">:<span class="hljs-string">"user.created"</span></span>)
{<span class="hljs-symbol">:ok</span>, <span class="hljs-comment">#PID&lt;0.214.0&gt;}</span>
iex(alice<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">2</span>&gt; Client.start_link(<span class="hljs-symbol">:<span class="hljs-string">"user.updated"</span></span>)
{<span class="hljs-symbol">:ok</span>, <span class="hljs-comment">#PID&lt;0.215.0&gt;}</span>

<span class="hljs-comment"># Node: Bob</span>
iex(bob<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt; <span class="hljs-keyword">for</span> _i &lt;- <span class="hljs-number">1</span>..<span class="hljs-number">5</span>, <span class="hljs-symbol">do:</span> Client.start_link(<span class="hljs-symbol">:<span class="hljs-string">"user.created"</span></span>)
[
  <span class="hljs-symbol">ok:</span> <span class="hljs-comment">#PID&lt;0.201.0&gt;,</span>
  <span class="hljs-symbol">ok:</span> <span class="hljs-comment">#PID&lt;0.202.0&gt;,</span>
  <span class="hljs-symbol">ok:</span> <span class="hljs-comment">#PID&lt;0.203.0&gt;,</span>
  <span class="hljs-symbol">ok:</span> <span class="hljs-comment">#PID&lt;0.204.0&gt;,</span>
  <span class="hljs-symbol">ok:</span> <span class="hljs-comment">#PID&lt;0.205.0&gt;</span>
]

<span class="hljs-comment"># Node: Carol</span>
iex(carol<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">1</span>&gt; <span class="hljs-keyword">for</span> _i &lt;- <span class="hljs-number">1</span>..<span class="hljs-number">2</span>, <span class="hljs-symbol">do:</span> Client.start_link(<span class="hljs-symbol">:<span class="hljs-string">"user.updated"</span></span>)
[<span class="hljs-symbol">ok:</span> <span class="hljs-comment">#PID&lt;0.197.0&gt;, ok: #PID&lt;0.198.0&gt;]</span>
</code></pre>
<p>Let’s try publishing a <code>:"user.created"</code> message from <code>Alice</code>.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Alice</span>
iex(alice<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">3</span>&gt; Hermes.publish(<span class="hljs-symbol">:<span class="hljs-string">"user.created"</span></span>, %{<span class="hljs-symbol">name:</span> <span class="hljs-string">"Jade"</span>})
<span class="hljs-symbol">:ok</span>
<span class="hljs-symbol">Received:</span> %{<span class="hljs-symbol">name:</span> <span class="hljs-string">"Jade"</span>}

<span class="hljs-comment"># Node: Bob</span>
<span class="hljs-symbol">Received:</span> %{<span class="hljs-symbol">name:</span> <span class="hljs-string">"Jade"</span>}
<span class="hljs-symbol">Received:</span> %{<span class="hljs-symbol">name:</span> <span class="hljs-string">"Jade"</span>}
<span class="hljs-symbol">Received:</span> %{<span class="hljs-symbol">name:</span> <span class="hljs-string">"Jade"</span>}
<span class="hljs-symbol">Received:</span> %{<span class="hljs-symbol">name:</span> <span class="hljs-string">"Jade"</span>}
<span class="hljs-symbol">Received:</span> %{<span class="hljs-symbol">name:</span> <span class="hljs-string">"Jade"</span>}

<span class="hljs-comment"># Node: Carol</span>
<span class="hljs-comment"># ** nothing new **</span>
</code></pre>
<p>Since no clients in <code>Carol</code> are interested when users are created, nothing was broadcasted to its local subscribers.</p>
<p>Cool. Now, let’s try publishing a <code>:"user.updated"</code> message from Bob.</p>
<pre><code class="lang-elixir"><span class="hljs-comment"># Node: Alice</span>
<span class="hljs-symbol">Received:</span> %{<span class="hljs-symbol">id:</span> <span class="hljs-number">1</span>, <span class="hljs-symbol">name:</span> <span class="hljs-string">"Jadyline"</span>}

<span class="hljs-comment"># Node: Bob</span>
iex(bob<span class="hljs-variable">@Zeus</span>)<span class="hljs-number">2</span>&gt; Hermes.publish(<span class="hljs-symbol">:<span class="hljs-string">"user.updated"</span></span>, %{<span class="hljs-symbol">id:</span> <span class="hljs-number">1</span>, <span class="hljs-symbol">name:</span> <span class="hljs-string">"Jadyline"</span>})
<span class="hljs-symbol">:ok</span>

<span class="hljs-comment"># Node: Carol</span>
<span class="hljs-symbol">Received:</span> %{<span class="hljs-symbol">id:</span> <span class="hljs-number">1</span>, <span class="hljs-symbol">name:</span> <span class="hljs-string">"Jadyline"</span>}
<span class="hljs-symbol">Received:</span> %{<span class="hljs-symbol">id:</span> <span class="hljs-number">1</span>, <span class="hljs-symbol">name:</span> <span class="hljs-string">"Jadyline"</span>}
</code></pre>
<p>Awesome! Our distributed PubSub seems to be working fine!</p>
<p>I would like to reiterate that what we built here is a simple toy example <strong>solely for learning purposes and not meant to be used in production</strong>. If you need a distributed PubSub for production, <strong>just use the already battle-tested</strong> <a target="_blank" href="https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html"><code>Phoenix.PubSub</code></a>.</p>
<p>Link to full source code: <a target="_blank" href="https://github.com/vinceurag/hermes">https://github.com/vinceurag/hermes</a></p>
<p>See you on the next one!</p>
]]></content:encoded></item><item><title><![CDATA[Understanding GenServers]]></title><description><![CDATA[In this post, we'll be demystifying GenServers. We'll discuss its anatomy and then later roll out our own version of GenServer from the ground up.
To fully grasp this article, you must at least know the basics of Elixir/Erlang processes.
GenServer in...]]></description><link>https://papers.vincy.dev/understanding-genservers</link><guid isPermaLink="true">https://papers.vincy.dev/understanding-genservers</guid><category><![CDATA[Elixir]]></category><category><![CDATA[Erlang]]></category><category><![CDATA[OTP]]></category><category><![CDATA[backend]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[genserver]]></category><dc:creator><![CDATA[Vince Urag]]></dc:creator><pubDate>Sun, 03 Dec 2023 08:10:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1701591011976/3ecdafd4-046b-4581-b58a-4c86f0b07bfd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this post, we'll be demystifying GenServers. We'll discuss its anatomy and then later roll out our own version of GenServer from the ground up.</p>
<p><em>To fully grasp this article, you must at least know the basics of Elixir/Erlang processes.</em></p>
<h1 id="heading-genserver-in-action">GenServer in Action</h1>
<p>If you already know how to use GenServers, feel free to skip this section and jump straight to <strong>Anatomy of a GenServer</strong>.</p>
<p>GenServer is a behavior that facilitates client-server interaction. It abstracts away all the nitty-gritty details when dealing with interprocess communications.</p>
<p>In this example, we'll build an item accumulator. It would be an Elixir process that accepts an item and stores it in a list. This is how it would look like if we were to use the process primitive:</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">1</span>)&gt; pid = spawn(<span class="hljs-keyword">fn</span> -&gt;
...(<span class="hljs-number">1</span>)&gt;     receive <span class="hljs-keyword">do</span>
...(<span class="hljs-number">1</span>)&gt;       {sender, acc, item} -&gt; send(sender, [item | acc])
...(<span class="hljs-number">1</span>)&gt;     <span class="hljs-keyword">end</span>
...(<span class="hljs-number">1</span>)&gt;   <span class="hljs-keyword">end</span>)
<span class="hljs-comment">#PID&lt;0.229.0&gt;</span>
iex(<span class="hljs-number">2</span>)&gt; send pid, {<span class="hljs-keyword">self</span>, [], <span class="hljs-string">"apple"</span>}
{<span class="hljs-comment">#PID&lt;0.169.0&gt;, [], "apple"}</span>
iex(<span class="hljs-number">3</span>)&gt; flush
[<span class="hljs-string">"apple"</span>]
</code></pre>
<p>As you may have noticed, we would need to pass a couple of things around. We would also need to make sure that it's a long-living process (we'll discuss more about this later). It's easy to get lost within this code. This is why GenServer behavior exists.</p>
<p>Let's rewrite this using GenServer. To start, we need to define a module that implements the GenServer <em>behaviour</em>.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">ItemAccumulator</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> GenServer
<span class="hljs-keyword">end</span>
</code></pre>
<p>First, it should be able to set an initial state, in our case, an initial accumulator. To do this, we need to implement the <code>init/1</code> callback.</p>
<pre><code class="lang-elixir"><span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(acc) <span class="hljs-keyword">do</span>
  {<span class="hljs-symbol">:ok</span>, acc}
<span class="hljs-keyword">end</span>
</code></pre>
<p>Our <code>ItemAccumulator</code> will have two functionalities. We need to have a function that would store the item and another function to return the items. When storing an item, we don't need a response; hence, we can use <code>GenServer.cast/2</code>. On our GenServer module, we then need to handle the <code>cast</code> request using the <code>handle_cast/2</code> callback.</p>
<pre><code class="lang-elixir"><span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_cast</span></span>({<span class="hljs-symbol">:add</span>, item}, acc) <span class="hljs-keyword">do</span>
  {<span class="hljs-symbol">:noreply</span>, [item | acc]}
<span class="hljs-keyword">end</span>
</code></pre>
<p>On the other hand, when we ask for the list of items in the accumulator, we need to wait for a response from the server. Because of this, we need to use <code>GenServer.call/3</code>. On our GenServer module, we would need to implement the <code>handle_call/3</code> callback.</p>
<p>Lastly, let's also show the accumulated items before the process exits.</p>
<pre><code class="lang-elixir"><span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">terminate</span></span>(_reason, acc) <span class="hljs-keyword">do</span>
  IO.puts <span class="hljs-string">"Last state: <span class="hljs-subst">#{inspect(acc)}</span>"</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>This is how our GenServer module should look like:</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">ItemAccumulator</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> GenServer

  <span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(acc) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:ok</span>, acc}
  <span class="hljs-keyword">end</span>

  <span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_call</span></span>(<span class="hljs-symbol">:list_items</span>, from, acc) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:reply</span>, acc, acc}
  <span class="hljs-keyword">end</span>

  <span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_cast</span></span>({<span class="hljs-symbol">:add</span>, item}, acc) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:noreply</span>, [item | acc]}
  <span class="hljs-keyword">end</span>

  <span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">terminate</span></span>(_reason, acc) <span class="hljs-keyword">do</span>
    IO.puts <span class="hljs-string">"Last state: <span class="hljs-subst">#{inspect(acc)}</span>"</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Let us test this out.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">1</span>)&gt; GenServer.start_link(ItemAccumulator, [], <span class="hljs-symbol">name:</span> ItemAccumulator)
{<span class="hljs-symbol">:ok</span>, <span class="hljs-comment">#PID&lt;0.165.0&gt;}</span>
</code></pre>
<p>The <code>start_link/3</code> function starts the GenServer process. Giving it a <code>name</code> would register the process and would make it easier for us to locate and use it.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">2</span>)&gt; GenServer.cast(ItemAccumulator, {<span class="hljs-symbol">:add</span>, <span class="hljs-string">"Apples"</span>})
<span class="hljs-symbol">:ok</span>
iex(<span class="hljs-number">3</span>)&gt; GenServer.cast(ItemAccumulator, {<span class="hljs-symbol">:add</span>, <span class="hljs-string">"Oranges"</span>})
<span class="hljs-symbol">:ok</span>
iex(<span class="hljs-number">4</span>)&gt; GenServer.call(ItemAccumulator, <span class="hljs-symbol">:list_items</span>)
[<span class="hljs-string">"Oranges"</span>, <span class="hljs-string">"Apples"</span>]
iex(<span class="hljs-number">5</span>)&gt; GenServer.stop(ItemAccumulator)
Last <span class="hljs-symbol">state:</span> [<span class="hljs-string">"Oranges"</span>, <span class="hljs-string">"Apples"</span>]
<span class="hljs-symbol">:ok</span>
</code></pre>
<p>Since we have a working GenServer, we can now stop here or make the interface more developer-friendly. We can do that by abstracting the implementation details away from the client. They don't always need to know the implementation details, in our case, that we're using GenServers.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">ItemAccumulator</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> GenServer

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(state) <span class="hljs-keyword">do</span>
    GenServer.start_link(__MODULE__, state, <span class="hljs-symbol">name:</span> __MODULE__)
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add</span></span>(item) <span class="hljs-keyword">do</span>
    GenServer.cast(__MODULE__, {<span class="hljs-symbol">:add</span>, item})
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">list_items</span></span>(), <span class="hljs-symbol">do:</span> GenServer.call(__MODULE__, <span class="hljs-symbol">:list_items</span>)

  ...
  <span class="hljs-comment"># callbacks</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Let's try it out in the shell.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">1</span>)&gt; ItemAccumulator.start_link([])
{<span class="hljs-symbol">:ok</span>, <span class="hljs-comment">#PID&lt;0.155.0&gt;}</span>
iex(<span class="hljs-number">2</span>)&gt; ItemAccumulator.add(<span class="hljs-string">"Apples"</span>)
<span class="hljs-symbol">:ok</span>
iex(<span class="hljs-number">3</span>)&gt; ItemAccumulator.add(<span class="hljs-string">"Oranges"</span>)
<span class="hljs-symbol">:ok</span>
iex(<span class="hljs-number">4</span>)&gt; ItemAccumulator.list()
[<span class="hljs-string">"Oranges"</span>, <span class="hljs-string">"Apples"</span>]
</code></pre>
<p>The interface now looks cleaner and just focuses on the functionalities instead of implementation details.</p>
<h1 id="heading-anatomy-of-a-genserver">Anatomy of a GenServer</h1>
<p>A GenServer is just a regular Elixir/Erlang process that is <em>stuck</em> in a loop. Don't believe me? Here's what the observer shows when you're running a GenServer process.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701265823838/9a884161-6b24-422f-8e98-0ecf314f635f.jpeg" alt class="image--center mx-auto" /></p>
<p>It's an Erlang process that waits for a message, acts on the message, then loops and waits for a message again.</p>
<p>Take a look at the snippet below.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">HealthCheck</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">loop</span></span>() <span class="hljs-keyword">do</span>
    receive <span class="hljs-keyword">do</span>
      {pid, <span class="hljs-symbol">:ping</span>} -&gt;
        send(pid,<span class="hljs-symbol">:pong</span>)
        loop()
      <span class="hljs-symbol">:exit</span> -&gt; 
        Process.exit(<span class="hljs-keyword">self</span>(), <span class="hljs-symbol">:normal</span>)
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>We define a module called <code>HealthCheck</code> that has a function that just responds to a ping message and then loops. Let's use this module to build our first dumb version of our generic server.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">1</span>)&gt; pid = spawn(&amp;HealthCheck.loop/0)
<span class="hljs-comment">#PID&lt;0.155.0&gt;</span>
</code></pre>
<p>We now have a running generic server. To confirm that it's indeed alive, verify it using <code>Process.alive?/1</code>.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">2</span>)&gt; Process.alive?(pid)
<span class="hljs-keyword">true</span>
</code></pre>
<p>Let's do a ping check and see if our server responds.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">3</span>)&gt; send(pid, {<span class="hljs-keyword">self</span>(), <span class="hljs-symbol">:ping</span>})
{<span class="hljs-comment">#PID&lt;0.151.0&gt;, :ping}</span>
iex(<span class="hljs-number">4</span>)&gt; Process.info(<span class="hljs-keyword">self</span>(), <span class="hljs-symbol">:messages</span>)
{<span class="hljs-symbol">:messages</span>, [<span class="hljs-symbol">:pong</span>]}
</code></pre>
<p>Now confirm if our generic server is still alive.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">5</span>)&gt; Process.alive?(pid)
<span class="hljs-keyword">true</span>
</code></pre>
<p>We just built a long-running process. This is GenServer under the hood, with some quirks sprinkled on top of it.</p>
<p>Now let's build our own version of a GenServer!</p>
<h1 id="heading-basicserver">BasicServer</h1>
<h2 id="heading-requirements">Requirements</h2>
<p>In this mini-project, we aim to replicate the following functionalities of a GenServer:</p>
<ul>
<li><p><code>init</code> - We should be able to set the initial state.</p>
</li>
<li><p><code>call</code> - We should be able to make synchronous calls to the server and wait for a reply.</p>
</li>
<li><p><code>cast</code> - We should be able to send fire-and-forget calls to the server.</p>
</li>
<li><p><code>stop</code> - We should be able to stop the server.</p>
</li>
</ul>
<p>GenServers have other functionalities like <code>multicast</code>, <code>multicall</code>, etc but we'll only focus on the functionalities enumerated above.</p>
<h2 id="heading-using-macro"><code>__using__</code> Macro</h2>
<p>The <code>__using__</code> macro allows us to inject code into another module. We need this so that we can set the behavior for the server module and implement default callbacks.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">BasicServer</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-variable">@callback</span> init(state :: term()) :: {<span class="hljs-symbol">:ok</span>, term()} | {<span class="hljs-symbol">:stop</span>, term()}

  <span class="hljs-function"><span class="hljs-keyword">defmacro</span> <span class="hljs-title">__using__</span></span>(_opts) <span class="hljs-keyword">do</span>
    <span class="hljs-keyword">quote</span> <span class="hljs-keyword">do</span>
      <span class="hljs-variable">@behaviour</span> BasicServer
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<h2 id="heading-setting-initial-state">Setting Initial State</h2>
<p>Setting the initial state of a GenServer is done via the <code>init/1</code> callback. A module that is using our BasicServer must have an <code>init/1</code> callback defined; otherwise, we raise an exception.</p>
<pre><code class="lang-elixir">...
<span class="hljs-function"><span class="hljs-keyword">defmacro</span> <span class="hljs-title">__using__</span></span>(_opts) <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">quote</span> <span class="hljs-keyword">do</span>
    <span class="hljs-variable">@behaviour</span> BasicServer

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(_state) <span class="hljs-keyword">do</span>
      raise <span class="hljs-string">"you forgot to implement the init callback!"</span>
    <span class="hljs-keyword">end</span>

    defoverridable <span class="hljs-symbol">init:</span> <span class="hljs-number">1</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
...
</code></pre>
<h2 id="heading-starting-the-server">Starting the Server</h2>
<p>Now that we're enforcing the presence of an <code>init/1</code> callback, we can proceed with building our first function which is starting the BasicServer.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">BasicServer</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-variable">@callback</span> init(state :: term()) :: {<span class="hljs-symbol">:ok</span>, term()} | {<span class="hljs-symbol">:stop</span>, term()}

  ...

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(callback_mod, init_state, opts \\ []) <span class="hljs-keyword">do</span>
    pid =
      <span class="hljs-comment"># links to the parent process</span>
      spawn_link(<span class="hljs-keyword">fn</span> -&gt;
        {<span class="hljs-symbol">:ok</span>, state} = apply(callback_mod, <span class="hljs-symbol">:init</span>, [init_state])

        loop(callback_mod, state)
      <span class="hljs-keyword">end</span>)

    <span class="hljs-comment"># registers the process</span>
    if opts[<span class="hljs-symbol">:name</span>], <span class="hljs-symbol">do:</span> Process.register(pid, opts[<span class="hljs-symbol">:name</span>])

    {<span class="hljs-symbol">:ok</span>, pid}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">defp</span> <span class="hljs-title">loop</span></span>(callback, state) <span class="hljs-keyword">do</span>
    <span class="hljs-comment"># this receive-block will do the heavy-lifting later</span>
    receive <span class="hljs-keyword">do</span>
      _ -&gt; loop(callback, state)
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>

  ...
<span class="hljs-keyword">end</span>
</code></pre>
<h2 id="heading-synchronous-calls">Synchronous Calls</h2>
<p>In this section, we will be building the <code>call</code> part of GenServers. <code>call</code> is an asynchronous operation. It will not proceed with other actions until it receives a reply or it times out. How are we going to build this? <code>call</code> will send a message to our server process which is in a suspended state and just waiting for a message to arrive. Once our server process receives the message, it will call the callback module's <code>handle_call/3</code> function, passing in the message and current state. The calling process would then enter a suspended state waiting for a reply.</p>
<p>Let's create the <code>call/2</code> function.</p>
<pre><code class="lang-elixir">...
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">call</span></span>(pid, message) <span class="hljs-keyword">do</span>
  <span class="hljs-comment"># we need to mark the message as a `:call` to pattern-match later</span>
  <span class="hljs-comment"># in the receive-loop</span>
  send(pid, {<span class="hljs-symbol">:call</span>, <span class="hljs-keyword">self</span>(), message})

  <span class="hljs-comment"># since `call` is a synchronous operation, we need to wait for</span>
  <span class="hljs-comment"># a response from the server</span>
  receive <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:reply</span>, reply} -&gt;
      reply
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
...
</code></pre>
<p>Now, we modify the receive-block inside the looping function to handle the <code>call</code> message and call the appropriate callback function.</p>
<pre><code class="lang-elixir">...
<span class="hljs-function"><span class="hljs-keyword">defp</span> <span class="hljs-title">loop</span></span>(callback, state) <span class="hljs-keyword">do</span>
  receive <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:call</span>, caller_pid, message} -&gt;
      <span class="hljs-comment"># for this example, we'll only handle the</span>
      <span class="hljs-comment"># `{:reply, reply, state}` response.</span>
      {<span class="hljs-symbol">:reply</span>, reply, new_state} = apply(callback, <span class="hljs-symbol">:handle_call</span>, [message, caller_pid, state])
      send(caller_pid, {<span class="hljs-symbol">:reply</span>, reply})
      loop(callback, new_state)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
...
</code></pre>
<h2 id="heading-asynchronous-casts">Asynchronous Casts</h2>
<p>Let's deal with the fire-and-forget requests. This is very similar to the <code>call</code> function, except that we don't need to send a reply back to the calling process.</p>
<pre><code class="lang-elixir">...
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">cast</span></span>(pid, message) <span class="hljs-keyword">do</span>
  <span class="hljs-comment"># we don't need the caller_pid anymore</span>
  <span class="hljs-comment"># since we don't need to send a message back</span>
  send(pid, {<span class="hljs-symbol">:cast</span>, message})
<span class="hljs-keyword">end</span>
...
</code></pre>
<p>Handling the <code>cast</code> message is also simpler. We just need to call the callback function.</p>
<pre><code class="lang-elixir">...
<span class="hljs-function"><span class="hljs-keyword">defp</span> <span class="hljs-title">loop</span></span>(callback, state) <span class="hljs-keyword">do</span>
  receive <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:call</span>, caller_pid, message} -&gt;
      {<span class="hljs-symbol">:reply</span>, reply, new_state} = apply(callback, <span class="hljs-symbol">:handle_call</span>, [message, caller_pid, state])
      send(caller_pid, {<span class="hljs-symbol">:reply</span>, reply})
      loop(callback, new_state)

    {<span class="hljs-symbol">:cast</span>, message} -&gt;
      {<span class="hljs-symbol">:noreply</span>, new_state} = apply(callback, <span class="hljs-symbol">:handle_cast</span>, [message, state])
      loop(callback, new_state)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
...
</code></pre>
<h2 id="heading-hey-server-stop">Hey Server, stop!</h2>
<p>Finally, the last remaining function, <code>stop</code>. This function would just tell our server to exit. Well, it needs to do some cleanup first before exiting. Let's start by implementing the stop function.</p>
<pre><code class="lang-elixir">...
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">stop</span></span>(server, reason) <span class="hljs-keyword">do</span>
  send(server, {<span class="hljs-symbol">:stop</span>, reason})

  <span class="hljs-symbol">:ok</span>
<span class="hljs-keyword">end</span>
...
</code></pre>
<p>Hmm... why do we need to send a message to the server? Remember, the server is in a suspended state waiting for a message. Sure, we can just <code>kill</code> the server process, but that's not a graceful exit. Once the server receives the exit message, it executes the <code>terminate/2</code> callback and then escapes the loop; thus, terminating the server process.</p>
<pre><code class="lang-elixir">...
<span class="hljs-function"><span class="hljs-keyword">defp</span> <span class="hljs-title">loop</span></span>(callback, state) <span class="hljs-keyword">do</span>
  receive <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:call</span>, caller_pid, message} -&gt;
      {<span class="hljs-symbol">:reply</span>, reply, new_state} = apply(callback, <span class="hljs-symbol">:handle_call</span>, [message, caller_pid, state])
      send(caller_pid, {<span class="hljs-symbol">:reply</span>, reply})
      loop(callback, new_state)

    {<span class="hljs-symbol">:cast</span>, message} -&gt;
      {<span class="hljs-symbol">:noreply</span>, new_state} = apply(callback, <span class="hljs-symbol">:handle_cast</span>, [message, state])
      loop(callback, new_state)

    {<span class="hljs-symbol">:stop</span>, reason} -&gt;
      apply(callback, <span class="hljs-symbol">:terminate</span>, [reason, state])
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
...
</code></pre>
<h2 id="heading-testing-our-basic-server">Testing Our Basic Server</h2>
<p>Here's the full code of our GenServer clone, <strong>BasicServer</strong>.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">BasicServer</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-variable">@callback</span> init(state :: term()) :: {<span class="hljs-symbol">:ok</span>, term()} | {<span class="hljs-symbol">:stop</span>, term()}
  <span class="hljs-variable">@callback</span> handle_call(msg :: term(), from :: pid(), state :: term()) ::
              {<span class="hljs-symbol">:reply</span>, reply :: term(), state :: term()}
  <span class="hljs-variable">@callback</span> handle_cast(msg :: term(), state :: term()) :: {<span class="hljs-symbol">:noreply</span>, state :: term()}
  <span class="hljs-variable">@callback</span> terminate(reason :: atom(), state :: term()) :: <span class="hljs-symbol">:ok</span>

  <span class="hljs-function"><span class="hljs-keyword">defmacro</span> <span class="hljs-title">__using__</span></span>(_opts) <span class="hljs-keyword">do</span>
    <span class="hljs-keyword">quote</span> <span class="hljs-keyword">do</span>
      <span class="hljs-variable">@behaviour</span> BasicServer

      <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(_state) <span class="hljs-keyword">do</span>
        raise <span class="hljs-string">"you forgot to implement the init callback!"</span>
      <span class="hljs-keyword">end</span>

      defoverridable <span class="hljs-symbol">init:</span> <span class="hljs-number">1</span>
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(callback_mod, init_state, opts \\ []) <span class="hljs-keyword">do</span>
    pid =
      spawn_link(<span class="hljs-keyword">fn</span> -&gt;
        {<span class="hljs-symbol">:ok</span>, state} = apply(callback_mod, <span class="hljs-symbol">:init</span>, [init_state])

        loop(callback_mod, state)
      <span class="hljs-keyword">end</span>)

    if opts[<span class="hljs-symbol">:name</span>], <span class="hljs-symbol">do:</span> Process.register(pid, opts[<span class="hljs-symbol">:name</span>])

    {<span class="hljs-symbol">:ok</span>, pid}
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">call</span></span>(pid, message) <span class="hljs-keyword">do</span>
    send(pid, {<span class="hljs-symbol">:call</span>, <span class="hljs-keyword">self</span>(), message})

    receive <span class="hljs-keyword">do</span>
      {<span class="hljs-symbol">:reply</span>, reply} -&gt;
        reply
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">cast</span></span>(server, message) <span class="hljs-keyword">do</span>
    send(server, {<span class="hljs-symbol">:cast</span>, message})

    <span class="hljs-symbol">:ok</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">stop</span></span>(server, reason) <span class="hljs-keyword">do</span>
    send(server, {<span class="hljs-symbol">:stop</span>, reason})

    <span class="hljs-symbol">:ok</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">defp</span> <span class="hljs-title">loop</span></span>(callback, state) <span class="hljs-keyword">do</span>
    receive <span class="hljs-keyword">do</span>
      {<span class="hljs-symbol">:call</span>, caller_pid, message} -&gt;
        {<span class="hljs-symbol">:reply</span>, reply, new_state} = apply(callback, <span class="hljs-symbol">:handle_call</span>, [message, caller_pid, state])
        send(caller_pid, {<span class="hljs-symbol">:reply</span>, reply})
        loop(callback, new_state)

      {<span class="hljs-symbol">:cast</span>, message} -&gt;
        {<span class="hljs-symbol">:noreply</span>, new_state} = apply(callback, <span class="hljs-symbol">:handle_cast</span>, [message, state])
        loop(callback, new_state)

      {<span class="hljs-symbol">:stop</span>, reason} -&gt;
        apply(callback, <span class="hljs-symbol">:terminate</span>, [reason, state])
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>We'll reuse the <code>ItemAcc</code> we wrote before but instead of using GenServer, we use BasicServer.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">ItemAcc</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">use</span> BasicServer

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_link</span></span>(state) <span class="hljs-keyword">do</span>
    BasicServer.start_link(__MODULE__, state, <span class="hljs-symbol">name:</span> __MODULE__)
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add</span></span>(item) <span class="hljs-keyword">do</span>
    BasicServer.cast(__MODULE__, {<span class="hljs-symbol">:add</span>, item})
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">list_items</span></span>(), <span class="hljs-symbol">do:</span> BasicServer.call(__MODULE__, <span class="hljs-symbol">:list_items</span>)

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">stop</span></span>(), <span class="hljs-symbol">do:</span> BasicServer.stop(__MODULE__, <span class="hljs-symbol">:normal</span>)

  <span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init</span></span>(state), <span class="hljs-symbol">do:</span> {<span class="hljs-symbol">:ok</span>, state}

  <span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_call</span></span>(<span class="hljs-symbol">:list_items</span>, _from, acc) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:reply</span>, acc, acc}
  <span class="hljs-keyword">end</span>

  <span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_cast</span></span>({<span class="hljs-symbol">:add</span>, item}, acc) <span class="hljs-keyword">do</span>
    {<span class="hljs-symbol">:noreply</span>, [item | acc]}
  <span class="hljs-keyword">end</span>

  <span class="hljs-variable">@impl</span> <span class="hljs-keyword">true</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">terminate</span></span>(_reason, state) <span class="hljs-keyword">do</span>
    IO.puts <span class="hljs-string">"Terminating... Last state: <span class="hljs-subst">#{inspect(state)}</span>"</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<pre><code class="lang-elixir">iex(<span class="hljs-number">1</span>)&gt; ItemAcc.start_link([])
{<span class="hljs-symbol">:ok</span>, <span class="hljs-comment">#PID&lt;0.155.0&gt;}</span>
iex(<span class="hljs-number">2</span>)&gt; ItemAcc.add(<span class="hljs-string">"Apples"</span>)
<span class="hljs-symbol">:ok</span>
iex(<span class="hljs-number">3</span>)&gt; ItemAcc.add(<span class="hljs-string">"Oranges"</span>)
<span class="hljs-symbol">:ok</span>
iex(<span class="hljs-number">4</span>)&gt; ItemAcc.list_items()
[<span class="hljs-string">"Oranges"</span>, <span class="hljs-string">"Apples"</span>]
iex(<span class="hljs-number">5</span>)&gt; ItemAcc.stop()
Terminating... Last <span class="hljs-symbol">state:</span> [<span class="hljs-string">"Oranges"</span>, <span class="hljs-string">"Apples"</span>]
<span class="hljs-symbol">:ok</span>
</code></pre>
<p>We are now done implementing the core functionalities of a GenServer. I think it's important to highlight that <strong>you should not build your own GenServer</strong>. Elixir's GenServers are battle-tested already. You most likely don't need to reinvent the wheel. We only did it for educational purposes. If you need client-server communication, then just use the already-built GenServer.</p>
<p>If you enjoyed this insightful post and would like to receive more content like this, I warmly invite you to subscribe to my newsletter. Your support is truly appreciated! See you on the next one!</p>
]]></content:encoded></item><item><title><![CDATA[Tracing Function Calls in Elixir]]></title><description><![CDATA[Foreword
In this post, we'll talk about tracing a function to know its input arguments and return value. For demonstrations of the principles, we'll be using Erlang's dbg module (but of course, using Elixir syntax) then later on we will use a library...]]></description><link>https://papers.vincy.dev/tracing-function-calls-in-elixir</link><guid isPermaLink="true">https://papers.vincy.dev/tracing-function-calls-in-elixir</guid><category><![CDATA[Elixir]]></category><category><![CDATA[Erlang]]></category><category><![CDATA[Phoenix framework]]></category><category><![CDATA[backend]]></category><dc:creator><![CDATA[Vince Urag]]></dc:creator><pubDate>Tue, 28 Nov 2023 07:58:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1701158207197/26893750-f9a8-4115-ac77-491601b3904e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-foreword">Foreword</h1>
<p>In this post, we'll talk about tracing a function to know its input arguments and return value. For demonstrations of the principles, we'll be using Erlang's <code>dbg</code> module (but of course, using Elixir syntax) then later on we will use a library called <a target="_blank" href="https://github.com/vinceurag/smart_tracer">SmartTracer</a>.</p>
<p>There are a lot of opinions on whether you should or should not trace on a live production node. This article is not about it. I think it's fine as long as you know what you're doing and you added ample safeguards to your traces.</p>
<p>Anyway, let's jump in.</p>
<h1 id="heading-using-erlangs-dbg">Using Erlang's dbg</h1>
<p>The <code>dbg</code> module is included in the <code>runtime_tools</code> app which is part of OTP. It contains tools that could help you in debugging/tracing and is suitable to be included in production systems.</p>
<p>For demonstration purposes, we'll be using the <code>Foo</code> module defined below as an example.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">Foo</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-variable">@moduledoc</span> <span class="hljs-keyword">false</span>

  <span class="hljs-variable">@doc</span> <span class="hljs-keyword">false</span>
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">bar</span></span>(name) <span class="hljs-keyword">do</span>
    <span class="hljs-string">"Hello, my name is <span class="hljs-subst">#{get_name(name)}</span>"</span>
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">defp</span> <span class="hljs-title">get_name</span></span>(name) <span class="hljs-keyword">do</span>
    <span class="hljs-string">"Fizz"</span> &lt;&gt; name  
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Fire up your <code>IEx</code> and load the <code>Foo</code> module.</p>
<p>To start a tracer process we just need to invoke <code>:dbg.tracer/0</code>. This process will be the recipient of all trace messages.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">2</span>)&gt; <span class="hljs-symbol">:dbg</span>.tracer()
{<span class="hljs-symbol">:ok</span>, <span class="hljs-comment">#PID&lt;0.121.0&gt;}</span>
</code></pre>
<p>Now we need to configure which processes or ports to trace and what. For this example, let's trace all the function calls in existing and future processes.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">3</span>)&gt; <span class="hljs-symbol">:dbg</span>.p(<span class="hljs-symbol">:all</span>, <span class="hljs-symbol">:c</span>)
{<span class="hljs-symbol">:ok</span>, [{<span class="hljs-symbol">:matched</span>, <span class="hljs-symbol">:nonode</span><span class="hljs-variable">@nohost</span>, <span class="hljs-number">56</span>}]}
</code></pre>
<p><code>:all</code> means we want to trace existing processes before this was set and all processes that would be created. The second argument specifies what we want to trace, in this case <code>:c</code>, which means, function <strong>c</strong>alls.</p>
<p>Erlang documentation has a long list of flags to configure your tracer. You can take a look at those <a target="_blank" href="https://erlang.org/doc/man/dbg.html#p-2">here</a>.</p>
<p>You might be wondering about the somehow cryptic return value of <code>:dbg.p/2</code>. It's just saying that it's matching 56 processes based on your configuration. To verify this, You can run this:</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">4</span>)&gt; length(Process.list())
<span class="hljs-number">56</span>
</code></pre>
<p>Now we need to set up a <em>trace pattern</em>. We'll tell the tracer which function to trace.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">5</span>)&gt; <span class="hljs-symbol">:dbg</span>.tp(Foo, <span class="hljs-symbol">:bar</span>, <span class="hljs-number">1</span>, [{<span class="hljs-symbol">:_</span>, [], [{<span class="hljs-symbol">:return_trace</span>}]}])
{<span class="hljs-symbol">:ok</span>, [{<span class="hljs-symbol">:matched</span>, <span class="hljs-symbol">:nonode</span><span class="hljs-variable">@nohost</span>, <span class="hljs-number">1</span>}, {<span class="hljs-symbol">:saved</span>, <span class="hljs-number">1</span>}]}
</code></pre>
<p>The first to the third argument is the MFA. The last argument is called <code>MatchSpec</code>. We won't dive into it but Erlang has a comprehensive document that you can read <a target="_blank" href="https://erlang.org/doc/apps/erts/match_spec.html">here</a>. Basically, we told the tracer that we want to trace calls to <a target="_blank" href="https://erlang.org/doc/apps/erts/match_spec.html"><code>Foo.bar/1</code></a> and we also want the return values.</p>
<p>We're quite done with setting up the tracer. Let's try it out.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">6</span>)&gt; Foo.bar(<span class="hljs-string">"Buzz"</span>)
<span class="hljs-string">"Hello, my name is FizzBuzz"</span>

iex(<span class="hljs-number">7</span>)&gt; (&lt;0.<span class="hljs-number">104.0</span>&gt;) call <span class="hljs-string">'Elixir.Foo'</span><span class="hljs-symbol">:bar</span>(&lt;&lt;<span class="hljs-string">"Buzz"</span>&gt;&gt;)
(&lt;0.<span class="hljs-number">104.0</span>&gt;) returned from <span class="hljs-string">'Elixir.Foo'</span><span class="hljs-symbol">:bar/</span><span class="hljs-number">1</span> -&gt; &lt;&lt;<span class="hljs-string">"Hello, my name is FizzBuzz"</span>&gt;&gt;
</code></pre>
<p>Woohoo! 🎉</p>
<p>Remember to stop the tracer since it also consumes memory.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">8</span>)&gt; <span class="hljs-symbol">:dbg</span>.stop_clear()
<span class="hljs-symbol">:ok</span>
</code></pre>
<p>Let's try tracing a local function call. In this case, a call to <code>get_name/1</code> which is a private function.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">9</span>)&gt; <span class="hljs-symbol">:dbg</span>.tracer()
{<span class="hljs-symbol">:ok</span>, <span class="hljs-comment">#PID&lt;0.130.0&gt;}</span>

iex(<span class="hljs-number">10</span>)&gt; <span class="hljs-symbol">:dbg</span>.p(<span class="hljs-symbol">:all</span>, <span class="hljs-symbol">:c</span>)
{<span class="hljs-symbol">:ok</span>, [{<span class="hljs-symbol">:matched</span>, <span class="hljs-symbol">:nonode</span><span class="hljs-variable">@nohost</span>, <span class="hljs-number">56</span>}]}

iex(<span class="hljs-number">11</span>)&gt; <span class="hljs-symbol">:dbg</span>.tp(Foo, <span class="hljs-symbol">:get_name</span>, <span class="hljs-number">1</span>, [{<span class="hljs-symbol">:_</span>,  [], [{<span class="hljs-symbol">:return_trace</span>}]}])
{<span class="hljs-symbol">:ok</span>, [{<span class="hljs-symbol">:matched</span>, <span class="hljs-symbol">:nonode</span><span class="hljs-variable">@nohost</span>, 0}, {<span class="hljs-symbol">:saved</span>, <span class="hljs-number">1</span>}]}

iex(<span class="hljs-number">12</span>)&gt; Foo.bar(<span class="hljs-string">"Buzz"</span>)
<span class="hljs-string">"Hello, my name is FizzBuzz"</span>
</code></pre>
<p>Nothing happened. It's because <code>:dbg.tp/4</code> does not trace local function calls. To trace local function calls, we need to use <code>:dbg.tpl/4</code>. I assume the <code>l</code> there stands for <code>local</code>. 🤷‍♂️</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">13</span>)&gt; <span class="hljs-symbol">:dbg</span>.tpl(Foo, <span class="hljs-symbol">:get_name</span>, <span class="hljs-number">1</span>, [{<span class="hljs-symbol">:_</span>,  [], [{<span class="hljs-symbol">:return_trace</span>}]}])
{<span class="hljs-symbol">:ok</span>, [{<span class="hljs-symbol">:matched</span>, <span class="hljs-symbol">:nonode</span><span class="hljs-variable">@nohost</span>, <span class="hljs-number">1</span>}, {<span class="hljs-symbol">:saved</span>, <span class="hljs-number">1</span>}]}

iex(<span class="hljs-number">14</span>)&gt; Foo.bar(<span class="hljs-string">"Buzz"</span>)
(&lt;0.<span class="hljs-number">104.0</span>&gt;) call <span class="hljs-string">'Elixir.Foo'</span><span class="hljs-symbol">:get_name</span>(&lt;&lt;<span class="hljs-string">"Buzz"</span>&gt;&gt;)
(&lt;0.<span class="hljs-number">104.0</span>&gt;) returned from <span class="hljs-string">'Elixir.Foo'</span><span class="hljs-symbol">:get_name/</span><span class="hljs-number">1</span> -&gt; &lt;&lt;<span class="hljs-string">"FizzBuzz"</span>&gt;&gt;
<span class="hljs-string">"Hello, my name is FizzBuzz"</span>
</code></pre>
<p>That's about everything you need to know to start tracing functions in Elixir. In the following section we'll talk about using the library called <a target="_blank" href="https://github.com/vinceurag/smart_tracer">SmartTracer</a> by yours truly.</p>
<h1 id="heading-using-smarttracer">Using SmartTracer</h1>
<p><strong>SmartTracer</strong> is an Elixir library I built to aid developers with tracing function calls. It has some added safeguards and rich features like trace recording and defining your own custom trace handlers.</p>
<h2 id="heading-design">Design</h2>
<p><img src="https://journal.vinceurag.com/content/images/2021/02/smartTracer.png" alt /></p>
<p>As show in the diagram above, every process spawned by the <strong>SmartTracer</strong> has the IEx shell as its parent process. We don't want to have dangling/rogue processes in our system. This way, if the shell exits, all <strong>SmartTracer</strong>-related processes will be brought down as well. The shell also trap exits so it won't die when the <em>tracer</em> and <em>trace handler</em> processes exit.</p>
<p>The <em>recorder</em> process is not in the <em>tracer</em> process chain since we want it to be independent from the <em>tracer</em> processes. If ever the <em>tracer</em> process terminates, we'd still be able to playback the recorded function calls.</p>
<h2 id="heading-safeguards">Safeguards</h2>
<h3 id="heading-rate-limiting">Rate Limiting</h3>
<p>As I have mentioned before, tracing function calls consumes resources so it's not ideal to trace unlimited function calls as it may degrade your app's performance. Most of the time, you just need a small sample data. When using the <strong>SmartTracer</strong> library, a limit is necessary. When the rate limit is tripped, the <em>tracer</em> processes are killed. You would still be able to playback the recorded calls.</p>
<h3 id="heading-shell-as-the-parent-process">Shell as the Parent Process</h3>
<p>Having the IEx shell as the parent process of all <strong>SmartTracer</strong>-related processes, we could be sure that once the IEx shell exits, there will be no rogue processes left behind.</p>
<h2 id="heading-features">Features</h2>
<h3 id="heading-easy-to-use-interface">Easy-to-use interface</h3>
<p>Instead of manually starting the <em>tracers</em> and setting the <em>pattern</em>, starting a trace can be done via a one-liner:</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">2</span>)&gt; SmartTracer.trace(&amp;Foo.bar/<span class="hljs-number">1</span>, <span class="hljs-number">5</span>)
<span class="hljs-symbol">:ok</span>

iex(<span class="hljs-number">3</span>)&gt; Foo.bar(<span class="hljs-string">"Buzz"</span>)
<span class="hljs-string">"Hello, my name is FizzBuzz"</span> <span class="hljs-comment"># &lt;- return value from shell, ignore</span>

03<span class="hljs-symbol">:</span><span class="hljs-number">31:34</span>.<span class="hljs-number">429</span> [info] Elixir.Foo.bar/<span class="hljs-number">1</span> is being called <span class="hljs-symbol">with:</span> [<span class="hljs-string">"Buzz"</span>]
</code></pre>
<p>You just need to provide the function reference and the number of calls you want to trace.</p>
<p>If you need the return value, it's just an option away.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">7</span>)&gt; SmartTracer.trace(&amp;Foo.bar/<span class="hljs-number">1</span>, <span class="hljs-number">5</span>, <span class="hljs-symbol">return:</span> <span class="hljs-keyword">true</span>)
<span class="hljs-symbol">:ok</span>

iex(<span class="hljs-number">8</span>)&gt; Foo.bar(<span class="hljs-string">"Buzz"</span>)
03<span class="hljs-symbol">:</span><span class="hljs-number">33:00</span>.<span class="hljs-number">636</span> [info] Elixir.Foo.bar/<span class="hljs-number">1</span> is being called <span class="hljs-symbol">with:</span> [<span class="hljs-string">"Buzz"</span>]
<span class="hljs-string">"Hello, my name is FizzBuzz"</span> <span class="hljs-comment"># &lt;- return value from shell, ignore</span>
03<span class="hljs-symbol">:</span><span class="hljs-number">33:00</span>.<span class="hljs-number">636</span> [info] Elixir.Foo.bar/<span class="hljs-number">1</span> <span class="hljs-symbol">returns:</span> <span class="hljs-string">"Hello, my name is FizzBuzz"</span>
</code></pre>
<p>To trace local functions, you just need to set the <code>scope</code> to <code>:local</code>.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">13</span>)&gt; SmartTracer.trace(&amp;Foo.get_name/<span class="hljs-number">1</span>, <span class="hljs-number">5</span>, <span class="hljs-symbol">scope:</span> <span class="hljs-symbol">:local</span>)
<span class="hljs-symbol">:ok</span>

iex(<span class="hljs-number">14</span>)&gt; Foo.bar(<span class="hljs-string">"Buzz"</span>)
<span class="hljs-string">"Hello, my name is FizzBuzz"</span> <span class="hljs-comment"># &lt;- return value from shell, ignore</span>

03<span class="hljs-symbol">:</span><span class="hljs-number">39:42</span>.<span class="hljs-number">136</span> [info] Elixir.Foo.get_name/<span class="hljs-number">1</span> is being called <span class="hljs-symbol">with:</span> [<span class="hljs-string">"Buzz"</span>]
</code></pre>
<h3 id="heading-call-recording">Call Recording</h3>
<p>Let's say you would be tracing a function that is being called very frequently, there's a chance that the tracer's output will be buried deep in the shell. To avoid this, you can record the calls and play them back when you need to (as long as the IEx and recorder agent are alive).</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">15</span>)&gt; SmartTracer.trace(&amp;Foo.bar/<span class="hljs-number">1</span>, <span class="hljs-number">5</span>, <span class="hljs-symbol">record:</span> <span class="hljs-keyword">true</span>)
<span class="hljs-symbol">:ok</span>

iex(<span class="hljs-number">16</span>)&gt; Foo.bar(<span class="hljs-string">"Buzz"</span>)                                     
03<span class="hljs-symbol">:</span><span class="hljs-number">47:35</span>.<span class="hljs-number">238</span> [info]  Elixir.Foo.bar/<span class="hljs-number">1</span> is being called <span class="hljs-symbol">with:</span> [<span class="hljs-string">"Buzz"</span>]
<span class="hljs-string">"Hello, my name is FizzBuzz"</span> <span class="hljs-comment"># &lt;- return value from shell, ignore</span>

iex(<span class="hljs-number">17</span>)&gt; SmartTracer.playback()
[
  %SmartTracer.Utils.Recorder.Call{
    <span class="hljs-symbol">args:</span> [<span class="hljs-string">"Buzz"</span>],
    <span class="hljs-symbol">arity:</span> <span class="hljs-number">1</span>,
    <span class="hljs-symbol">datetime:</span> <span class="hljs-string">~U[2021-02-05 19:47:35Z]</span>,
    <span class="hljs-symbol">function:</span> <span class="hljs-symbol">:bar</span>,
    <span class="hljs-symbol">module:</span> Foo,
    <span class="hljs-symbol">type:</span> <span class="hljs-symbol">:call</span>
  }
]
</code></pre>
<h3 id="heading-custom-tracer">Custom Tracer</h3>
<p>Another big feature of <strong>SmartTracer</strong> is the ability to define your own tracer. By default, <strong>SmartTracer</strong> would just use Logger to output the function calls. When you define your own tracer, you would have full control on what to do with the traces. You can write it to a file, send it to an API, etc. Just be mindful of any performance impact.</p>
<p>Here's a very simple definition of a CustomTracer.</p>
<pre><code class="lang-elixir"><span class="hljs-class"><span class="hljs-keyword">defmodule</span> <span class="hljs-title">MyApp.CustomTracer</span></span> <span class="hljs-keyword">do</span>
  <span class="hljs-variable">@moduledoc</span> <span class="hljs-keyword">false</span>

  <span class="hljs-keyword">use</span> SmartTracer.Custom

  <span class="hljs-keyword">require</span> Logger

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle</span></span>(<span class="hljs-symbol">:call</span>, {_module, _fun, args}) <span class="hljs-keyword">do</span>
    <span class="hljs-comment"># maybe write to a file?</span>
    Logger.info(<span class="hljs-string">"The function was called with <span class="hljs-subst">#{inspect(args)}</span>"</span>)
  <span class="hljs-keyword">end</span>

  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle</span></span>(<span class="hljs-symbol">:return</span>, {_module, _fun, _arity, return_value}) <span class="hljs-keyword">do</span>
    Logger.info(<span class="hljs-string">"The function returned: <span class="hljs-subst">#{inspect(return_value)}</span>"</span>)
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>Now, instead of calling SmartTracer directly, you would now invoke the <code>trace/3</code> function from your CustomTracer.</p>
<pre><code class="lang-elixir">iex(<span class="hljs-number">4</span>)&gt; MyApp.CustomTracer.trace(&amp;Foo.bar/<span class="hljs-number">1</span>, <span class="hljs-number">5</span>)
<span class="hljs-symbol">:ok</span>

iex(<span class="hljs-number">5</span>)&gt; Foo.bar(<span class="hljs-string">"Buzz"</span>)
<span class="hljs-string">"Hello, my name is FizzBuzz"</span> <span class="hljs-comment"># &lt;- return value from shell, ignore</span>
03<span class="hljs-symbol">:</span><span class="hljs-number">59:26</span>.<span class="hljs-number">435</span> [info]  The function was called <span class="hljs-keyword">with</span> [<span class="hljs-string">"Buzz"</span>]
</code></pre>
<p>That's all! To learn more, refer to <a target="_blank" href="https://hexdocs.pm/smart_tracer/SmartTracer.html">SmartTracer's documentation</a>. Also, feel free to contribute to this project. [<a target="_blank" href="https://github.com/vinceurag/smart_tracer">Github</a>]</p>
]]></content:encoded></item><item><title><![CDATA[Glimpse of Docker in 2 Minutes]]></title><description><![CDATA[Docker is a containerization platform that has been around since 2013. It tries to eliminate the it-works-on-my-machine problem. In this mini-article, we'll discuss what are containers and images in a very simple way. We'll also try to run a PHP file...]]></description><link>https://papers.vincy.dev/glimpse-of-docker-in-2-minutes</link><guid isPermaLink="true">https://papers.vincy.dev/glimpse-of-docker-in-2-minutes</guid><category><![CDATA[technology]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Devops]]></category><category><![CDATA[containers]]></category><dc:creator><![CDATA[Vince Urag]]></dc:creator><pubDate>Wed, 08 Aug 2018 04:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1701153014117/da69e9c1-e13f-41e7-bcae-2973469150f8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://journal.vinceurag.com/content/images/2018/08/r_566074_HLoe9.jpg" alt class="image--center mx-auto" /></p>
<p>Docker is a containerization platform that has been around since 2013. It tries to eliminate the <code>it-works-on-my-machine</code> problem. In this mini-article, we'll discuss what are containers and images in a very simple way. We'll also try to run a PHP file via Docker. Let's get started!</p>
<h1 id="heading-basic-terminologies">Basic Terminologies</h1>
<h2 id="heading-images">Images</h2>
<p>A Docker image is basically a file that's a snapshot of a container. Images are used to run containers. Think of it as a blueprint and the house is the container.</p>
<h2 id="heading-containers">Containers</h2>
<p>Docker Container is an <em>instance</em> of a Docker image. Think of it as a 'running image'.</p>
<h1 id="heading-demo">Demo</h1>
<p>In this demo, we'll be running a simple PHP script that prints a text on the browser without installing any other shits (👀Apache).</p>
<h2 id="heading-create-a-simple-php-script">Create a simple PHP script</h2>
<p>Create a new directory called <code>scripts</code>, then inside it, create a new file <code>luffy.php</code>.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
  <span class="hljs-comment">// scripts/luffy.php</span>

  <span class="hljs-keyword">echo</span> <span class="hljs-string">"Gomu Gomu no bazooooka!"</span>;
</code></pre>
<h2 id="heading-dockerfile">Dockerfile</h2>
<p>So we've already talked about images and containers but we haven't actually tried making one. A Dockerfile is a text file that holds commands and keywords that Docker uses to build the image.</p>
<p>Create a new file directly above the <code>scripts</code> directory and name it <code>Dockerfile</code>. Copy and paste the code below.</p>
<pre><code class="lang-docker"><span class="hljs-keyword">FROM</span> php:<span class="hljs-number">7.2</span>-apache

<span class="hljs-keyword">COPY</span><span class="bash"> scripts/ /var/www/html</span>

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>
</code></pre>
<p>Dockerfile Dissection</p>
<ul>
<li><p><code>FROM</code> - This instruction pulls an image from a public Docker repository like <a target="_blank" href="https://hub.docker.com/">Docker Hub</a>. In our case, we need to have <code>php</code> and <code>apache</code> installed. With luck on our side, there's already an <a target="_blank" href="https://hub.docker.com/_/php/">image that has PHP and Apache</a>. The format for setting the base image is <code>FROM image:tag</code>.</p>
</li>
<li><p><code>COPY</code> - This instruction copies the files in <code>&lt;source&gt;</code> to <code>&lt;destination&gt;</code>.</p>
</li>
<li><p><code>EXPOSE</code> - This instructions tells Docker which ports the container is listening during runtime.</p>
</li>
</ul>
<h2 id="heading-building-the-docker-image">Building the Docker Image</h2>
<p>Since we have already created the Dockerfile, it's time for Docker to build the image. While in the directory where Dockerfile is located, issue this command to build the image:</p>
<pre><code class="lang-bash">docker build -t bazooka .
</code></pre>
<p>During the first time, it would download all the layers needed. So you will see something like this:</p>
<pre><code class="lang-bash">Sending build context to Docker daemon  3.584kB
Step 1/3 : FROM php:7.2-apache
7.2-apache: Pulling from library/php
be8881be8156: Pull complete
69a25f7e4930: Pull complete
65632e89c5f4: Pull complete
cd75fa32da8f: Pull complete
15bc7736db11: Pull complete
b2c40cef4807: Pull complete
f3507e55e5eb: Pull complete
e6006cdfa16b: Pull complete
a3ed406e3c88: Pull complete
ae70e16ef035: Pull complete
b9501b07bdf1: Pull complete
f9ecb48a3c12: Pull complete
23d9db872f7e: Pull complete
c41139f0d4f5: Pull complete
f180ba12d718: Pull complete
Digest: sha256:34d20a90946b2a2e05ca6d2fe71f1394168af6dc1d18ab8211cc43f1679410cb
Status: Downloaded newer image <span class="hljs-keyword">for</span> php:7.2-apache
 ---&gt; 6a99292078a8
Step 2/3 : COPY scripts/ /var/www/html
 ---&gt; c00fd3a92d60
Step 3/3 : EXPOSE 80
 ---&gt; Running <span class="hljs-keyword">in</span> bf9e253efab6
Removing intermediate container bf9e253efab6
 ---&gt; 81b417c196b3
Successfully built 81b417c196b3
Successfully tagged bazooka:latest
</code></pre>
<h2 id="heading-running-the-image">Running the image</h2>
<p>To run the created image:</p>
<pre><code class="lang-bash">docker run -p 80:80 bazooka
</code></pre>
<p>Formula: <code>docker run &lt;flags|options&gt; &lt;image-name&gt;</code></p>
<ul>
<li><code>-p</code> - This flag is used to forward a port. <code>&lt;host|your-machine&gt;:&lt;container&gt;</code></li>
</ul>
<h2 id="heading-appreciate-the-magic">Appreciate the magic!</h2>
<p>Open your browser then access <a target="_blank" href="http://localhost/luffy.php"><code>localhost/luffy.php</code></a>. You should now see the text "Gomu Gomu no bazooooka!" printed.</p>
<p><img src="https://journal.vinceurag.com/content/images/2018/08/Screen-Shot-2018-08-28-at-8.51.37-PM-2.png" alt /></p>
]]></content:encoded></item></channel></rss>