michael_teter

michael_teter

Feedback request - my simple GenServer demo

I recently took a code challenge for a job I applied to. My Elixir experience is limited to one production Phoenix project; but as most of you know, with a framework and a fairly simple project, there’s not a lot of skill required beyond basic language use and understanding how to use the framework correctly. Thus, I had not done anything interesting with BEAM before.

Here was the problem spec:

URLShortener

Create a GenServer that acts as a URLShortener which holds the URLs and IDs in its state (a simple map structure would be OK).

Create the following client functions and corresponding callbacks:

  1. shorten/2 , this takes a name/pid and a URL and returns an id
  2. get/2 , this takes a name/pid and an id and returns the url
  3. flush/1 , this takes a name/pid and flushes the state to an empty state
  4. count/1 , this takes a name/pid and returns a total count of the URLs in the state
  5. get_stats/1 , this takes a name/pid and returns the count of URLs per unique hosts from the urls
  6. get_click_stats/1 , this takes a name/pid and returns the total count of clicks on all urls combined (click count is generated from get/2 )
  7. get_click_stats/2 , this takes a name/pid and id and returns the count of clicks on url with given id (click count is generated from get/2 )

And here was my answer:
https://github.com/michaelteter/shorty

I followed a few examples I found online, and I used Elixir/GenServer documentation. However, the feedback I got was limited to “it’s not how we would have done it”. But as it is with job interviews, it’s quite rare to get details. So in my interest to learn, I would be happy to hear any comments from ElixirForum folks. TIA!

Most Liked

kokolegorille

kokolegorille

Sorry for your job interview…

I have seen some part I would change, but given I don’t know the rule it’s kind of difficult to answer fully.

  • Why start_server? I would use start_link
  • Why use tuple when there is no argument? atom is enough I think
  • Maintaining an id count is painful, if allowed, I would use UUID
  • Why create your regex to parse host when there is URI module
  • I would use cast for flush

I find also difficult to reason about the state you are passing.

As a simple example, I would store a simple tuple by given id, something like {host, url, count}

I don’t mean to have the perfect answer, but what about something like this?

  @impl GenServer
  def handle_call({:shorten, url}, _from, state) do
    host = URI.parse(url).host
    id = UUID.uuid4()
    new_state = Map.put(state, id, {host, url, 0})
    {:reply, id, new_state}
  end

  @impl GenServer
  def handle_call({:get, id}, _from, state) do
    case Map.get(state, id) do
      {host, url, count} ->
        {:reply, url, %{state | id => {host, url, count + 1}}}
      nil ->
        {:reply, nil, state}
    end
  end

  @impl GenServer
  def handle_call(:count, _from, state) do
    {:reply, Enum.count(state), state}
  end

  @impl GenServer
  def handle_call(:get_stats, _from, state) do
    reply = state
    |> Enum.group_by(fn {_k, {url, _, _}} -> url end)
    |> Enum.reduce(%{}, fn {key, list}, acc ->
      Map.put(acc, key, Enum.reduce(list, 0, fn {_k, {_, _, count}}, a -> a + count end))
    end)

    {:reply, reply, state}
  end

  @impl GenServer
  def handle_call(:get_click_stats, _from, state) do
    reply = state
    |> Enum.reduce(0, fn {_key, {_host, _url, count}}, acc -> acc + count end)
    {:reply, reply, state}
  end

  @impl GenServer
  def handle_call({:get_click_stats, id}, _from, state) do
    reply = state
    |> Enum.filter(fn {key, _value} -> key == id end)
    |> Enum.reduce(0, fn {_key, {_host, _url, count}}, acc -> acc + count end)
    {:reply, reply, state}
  end

  @impl GenServer
  def handle_cast(:flush, _state) do
    {:noreply, %{}}
  end
kokolegorille

kokolegorille

start_link is not only for historical reason, it means there is a link created between the parent and child processes, which is not the case with start.

And yes, our code makes about the same things :slight_smile:

For the cast… it’s true I start with call everywhere, but when I really don’t expect any response, I switch for cast.

Where Next?

Popular in Challenges Top

sb8244
Note: This topic is to talk about Day 10 of the Advent of Code 2019 . There is a private leaderboard for elixirforum members. You can jo...
New
Aetherus
This topic is about Day 3 of the Advent of Code 2020 . Thanks to @egze, we have a private leaderboard: https://adventofcode.com/2020/le...
New
bjorng
This topic is about Day 18 of the Advent of Code 2021. We have a private leaderboard (shared with users of Erlang Forums): https://adve...
New
New
bjorng
Note: This topic is to talk about Day 3 of the Advent of Code 2019 . There is a private leaderboard for elixirforum members. You can jo...
New
bjorng
Note: This topic is to talk about Day 23 of the Advent of Code. For general discussion about the Advent of Code 2018 and links to topics...
New
bjorng
Note: This topic is to talk about Day 1 of the Advent of Code 2019.
New
code-shoily
Just did part 1. Part 2 seems to be demanding too much of my reading time so will get to that after I am done with some chores. Oh here ...
New
stevensonmt
Reasonably pleased with my solution. The bitstring packet problems are so well suited to Erlang/Elixir it’s almost not fair. defmodule D...
New
seeplusplus
Hello all, hopefully I post this before someone else does and I don’t dupe. IMO Day 4 was much easier than Day 3 (yay, I can sleep befor...
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New

We're in Beta

About us Mission Statement