text: OTP in Elixir: Learn GenServer by Building Your Own URL Shortener
  Ilija Eftimov
  Originally published at
  ieftimov.com
  on
  ・14 min read
  Looking TelecomPlatform,
  defmodule URLShortener do def start do spawn( __MODULE__ , :loop, [%{}]) end def loop(state) do receive do {:stop, caller} -> send caller, "Shutting down." {:shorten, url, caller} -> url_md5 = md5(url) new_state = Map.put(state, url_md5, url) send caller, url_md5 loop(new_state) {:get, md5, caller} -> send caller, Map.fetch(state, md5) loop(state) :flush -> loop(%{}) _ -> loop(state) end end defp md5(url) do :crypto.hash(:md5, url) |> Base.encode16(case: :lower) end end:
  %{ "99999ebcfdb78df077ad2727fd00969f" => "", "76100d6f27db53fddb6c8fce320f5d21" => "", "3097fca9b1ec8942c4305e550ef1b50a" => "", ... }
  Then, we send the MD5 value back to the caller. Obviously, this is not howbit.ly or the likes work, as their links are much shorter. (For those interested,here’san:
  iex(22)> shortener = URLShortener.start #PID<0.141.0> iex(23)> send shortener, {:shorten, "", self()} {:shorten, "", #PID<0.102.0>} iex(24)> send shortener, {:shorten, "", self()} {:shorten, "", #PID<0.102.0>} iex(25)> send shortener, {:shorten, "", self()} {:shorten, "", #PID<0.102.0>} iex(26)> flush "8c4c7fbc57b08d379da5b1312690be04" "99999ebcfdb78df077ad2727fd00969f" "3097fca9b1ec8942c4305e550ef1b50a" :ok iex(27)> send shortener, {:get, "99999ebcfdb78df077ad2727fd00969f", self()} {:get, "99999ebcfdb78df077ad2727fd00969f", #PID<0.102.0>} iex(28)> flush "" :ok iex(29)> send shortener, {:get, "8c4c7fbc57b08d379da5b1312690be04", self()} {:get, "8c4c7fbc57b08d379da5b1312690be04", #PID<0.102.0>} iex(30)> flush "" :ok iex(31)> send shortener, {:get, "3097fca9b1ec8942c4305e550ef1b50a", self()} {:get, "3097fca9b1ec8942c4305e550ef1b50a", #PID<0.102.0>} iex(32)> flush "" :ok gen erichere for more details).
  To start the server we can run:
  GenServer.start_link( __MODULE__ , :ok, []):
  def init(:ok) do {:ok, %{}} end:
  defmodule URLShortener do use GenServer def start_link(opts \\ []) do GenServer.start_link( __MODULE__ , :ok, opts) end def init(:ok) do {:ok, %{}} end end:
  iex(1)> {:ok, pid} = URLShortener.start_link {:ok, #PID<0.108.0>}
  That’s all we can do at this moment. The difference here is that the
  GenServer.start_link/3 function will return a tuple with an atom (
  :ok) and the PID of the server.
  Stopping the server ✋
  Let’s add the
  stop command:
  defmodule URLShortener do use GenServer # Client API def start_link(opts \\ []), do: GenServer.start_link( __MODULE__ , :ok, opts) def stop(pid) do GenServer.cast(pid, :stop) end # GenServer callbacks def init(:ok), do: {:ok, %{}} def handle_cast(:stop, state) do {:stop, :normal, state} end end:
  iex(1)> {:ok, pid} = URLShortener.start_link {:ok, #PID<0.109.0>} iex(2)> Process.alive?(pid) true iex(3)> URLShortener.stop(pid) :ok iex(4)> Process.alive?(pid) false:
  defmodule URLShortener do use GenServer # Client API def start_link(opts \\ []), do: GenServer.start_link( __MODULE__ , :ok, opts) def stop(pid), do: GenServer.cast(pid, :stop) def shorten(pid, url) do GenServer.call(pid, {:shorten, url}) end # GenServer callbacks def init(:ok), do: {:ok, %{}} def handle_cast(:stop, state), do: {:stop, :normal, state} def handle_call({:shorten, url}, _from, state) do short = md5(url) {:reply, short, Map.put(state, short, url)} end defp md5(url) do :crypto.hash(:md5, url) |> Base.encode16(case: :lower) end enddocumentation to learn more.
  Fetching a shortened URL 🔗
  Let’s implement the
  get command, which when provided with a
  short version of the link it will return the full URL:
  defmodule URLShortener do use GenServer # Client API # ... def get(pid, short_link) do GenServer.call(pid, {:get, short_link}) end # GenServer callbacks # ... def handle_call({:get, short_link}, _from, state) do {:reply, Map.get(state, short), state} end end attprocess:
  defmodule URLShortener do use GenServer # Client API def start_link(name, opts \\ []) do GenServer.start_link( __MODULE__ , :ok, opts ++ [name: name]) end def stop(name), do: GenServer.cast(name, :stop) def shorten(name, url), do: GenServer.call(name, {:shorten, url}) def get(name, short_link) do GenServer.call(name, {:get, short_link}) end # GenServer callbacks def init(:ok), do: {:ok, %{}} def handle_cast(:stop, state), do: {:stop, :normal, state} def handle_call({:shorten, url}, _from, state), do: {:reply, md5(url), Map.put(state, md5(url), url)} def handle_call({:get, short_link}, _from, state) do {:reply, Map.get(state, short_link), state} end defp md5(url), do: :crypto.hash(:md5, url) |> Base.encode16(case: :lower) end
  That’s all. We added a new argument to
  URLShortener.start_link/2 and we dropped all usage of
  PID and replaced it with
  name.
  Let’s take it for a spin in
  IEx:
  iex(1)> {:ok, pid} = URLShortener.start_link(:foo) {:ok, #PID<0.109.0>} iex(2)> URLShortener.shorten(:foo, "") "99999ebcfdb78df077ad2727fd00969f" iex(3)> URLShortener.get(:foo, "99999ebcfdb78df077ad2727fd00969f") "" iex(4)> URLShortener.stop(:foo) :ok iex(5)> Process.alive?(pid) false
  You can see that this is pretty cool - instead of using
  PID we added a name
  :foo to the process which allowed us to refer to it using the name instead ofthe:
  defmodule URLShortener do use GenServer @name :url_shortener_server # Client API def start_link(opts \\ []) do GenServer.start_link( __MODULE__ , :ok, opts ++ [name: @name]) end def stop, do: GenServer.cast(@name, :stop) def shorten(url), do: GenServer.call(@name, {:shorten, url}) def get(short_link) do GenServer.call(@name, {:get, short_link}) end # GenServer callbacks # ... end:
  iex(1)> {:ok, pid} = URLShortener.start_link {:ok, #PID<0.108.0>} iex(2)> URLShortener.shorten("") "99999ebcfdb78df077ad2727fd00969f" iex(3)> URLShortener.shorten("") "c88f320dec138ba5ab0a5f990ff082ba" iex(4)> URLShortener.get("99999ebcfdb78df077ad2727fd00969f") "" iex(5)> URLShortener.stop :ok iex(6)> Process.alive?(pid) false:
  defmodule URLShortener do use GenServer # Client API def start_link(name, opts \\ []) do GenServer.start_link( __MODULE__ , :ok, opts ++ [name: name]) end def shorten(name, url) do GenServer.call(name, {:shorten, url}) end def get(name, short) do GenServer.call(name, {:get, short}) end def flush(name) do GenServer.cast(name, :flush) end def stop(name) do GenServer.cast(name, :stop) end def count(name) do GenServer.call(name, :count) end # Callbacks def init(:ok) do {:ok, %{}} end def handle_cast(:flush, _state) do {:noreply, %{}} end def handle_cast(:stop, state) do {:stop, :normal, state} end def handle_call({:shorten, url}, _from, state) do shortened = md5(url) new_state = Map.put(state, shortened, url) {:reply, shortened, new_state} end def handle_call({:get, short}, _from, state) do {:reply, Map.get(state, short), state} end def handle_call(:count, _from, state) do count = Map.keys(state) |> Enum.count {:reply, count, state} end defp md5(url) do :crypto.hash(:md5, url) |> Base.encode16(case: :lower) end end
  The two callbacks are fairly?
  Do you have your own Gatsby site? Let's brainstorm a dev.to cross-poster
  I'd love it if my blog posts were automatically sent to dev.to - wouldn't you?
  