Skip to content

incolotech/polly-tutorial

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Workshop assignment

Requirements

  1. All code should be shared via private Github repository.
  2. The solution should be built with Phoenix LiveView.
  3. Users should be able to create account by inserting their username.
  4. Users should be able to create new polls.
  5. Users should be able to vote in existing polls.
  6. Users should be able to see real-time updates of the poll results.
  7. You are free to use any Elixir/Erlang library and any open-source CSS framework for the design.
  8. Performance: users actions should not be blocking each other. User 1 actions should not be blocked by user 2 actions.
  9. The application should not use any external dependencies, such as a database or disk storage. All needed data should be stored in application memory.
  10. The application should start with mix phx.server so it can be started locally.
  11. The application should be well-structured, and the code should be readable.

Deliverables

Please provide a link to a GitHub repository containing your solution to the above task. Your solution should include:

  1. A Phoenix LiveView application that meets the above requirements.
  2. A README file that explains how to set up and run the application.
  3. Clear and concise documentation on how the application works, including any design decisions you made and any trade-offs you considered.
  4. Well-structured code that adheres to best practices and is easy to understand and modify.
  5. The core business functionality of the application must be covered by unit tests.

If you want to do it from scratch then be my guest.


Polly

Polly is a liveview based polling application. It allows the users to

  • Create polls with multiple options
  • Voting of Polls
  • Checking for results of the polls
  • Updates the results and vote count in real time

Demo

A small demo of showing the vote counts increasing in real-time

screenrecording.mov

Things to know

  • Once you sign in, just click at the username in the upper right corner to log out.

Architecture

This application from state management standpoint has 2 main components

  • Polly.PollsManager - This is a module which uses 3 ets tables to manage the state of all the polls and their votes. More in-depth explaination is provided below

  • Polly.VoteManager - Holds the votes casted by a single user. This genserver is instantiated when a user signs in and is uniquely identified using the user's username. Currently this module is used to check if the user has voted for a poll or not but can be extended for audit purposes, listing all votes on a user's profile page and more. More in-depth explaination is provided below

Polly.PollsManager

PollsManager takes care of all the state related to Polls. Essentially PollsManager stores data using 3 read and write concurrency enabled ets table. They are as

  • :polls - Stores all the Poll structs with their options, :id of the poll is used as a key for fast lookup

  • :polls_votes - Stores the total votes submitted towards a Poll. This table contains the vote counts in the format of {:id, count}. update_counter method provided by :ets is used to atomically increment these votes

  • :polls_options_votes - Stores the votes per Option of a Poll. Each Poll can have many options and for each option vote count is stored in form of {:option_id, count}. Here as :id of an option is a unique uuid and hence an option can be uniquely identified without knowing the :id of the poll.

Polly.VoteManager

VoteManager

VoteManager is a gen-server responsible for holding and managing votes for a user. A VoteManager process holds votes casted by a single user. Each instance is registered to the VoteRegistry for fast and convinient discovery and is created under a DynamicSupervisor.

This process design works well here because the act of "storing a vote" and checking if a user has "already cast a vote" are independent operation on a user level.

The state of the gen server is of the format map(poll_id => %Vote{}). This has been done to provide O(1) lookups.

The VoteRegistry used here is also using partitioned storage to allow for more performance.

Decisions

  • I could have definitely considered the PartitionSupervisor -> DynamicSupervisor -> GenServer for the Polls storage as well but I felt that when multiple users would try to vote on the same poll at the same time I would have had to synchronise those calls using handle_call and that could potentially slow down the act of increasing vote counters

  • Similarly a simpler VoteManager backed by an ets table similar to PollyManager could have definitely worked but I was stuck on one of the criterias of the test i.e. "Action of one user shouldn't block the other".

  • Parts of this application may seem a bit over-engineered but I did so keeping in mind that there can be thousands of users using this application at a time.

TradeOff

  • I didn't spend alot of time on the UI and hence its really simple, obviously I would have done a better job if this was production.
  • I could add alot more tests but felt what I have added cover a good chunk of functionality.
  • Could have added few more validations.
  • Could have added functionalities like edit poll, delete poll, delete option etc but I focused on the core for the test.
  • Could have considered using Horde based Registry and Supervisor to make this application distributed but that seemed a bit too overkill for this test. But I have production experience with Horde and distributed elixir application just to be aware.

Tests and Credo

I have tried to add as many test as I can, definitely could have added more but felt the ones added were good. Also added credo with a .credo.exs (added a few more checks that I personally like).

  • mix credo passes
  • mix test --cover outputs more then 80% coverage

Output of mix test --cover


27 tests, 0 failures

Randomized with seed 992261

Generating cover results ...

Percentage | Module
-----------|--------------------------
     0.00% | Polly.Constants
     0.00% | Polly.Schema.Vote
     0.00% | PollyWeb.Layouts
     0.00% | PollyWeb.PageHTML
    50.00% | PollyWeb.ErrorHTML
    63.33% | PollyWeb.UserAuth
    70.73% | PollyWeb.PollLive.Show
    71.43% | PollyWeb.PollLive.FormComponent
    80.00% | Polly.Application
    80.00% | Polly.Schema.Option
    80.00% | Polly.VoteSupervisor
    80.00% | PollyWeb.Telemetry
    81.41% | PollyWeb.CoreComponents
    83.33% | Polly.Factory
    87.50% | Polly.Polls
    90.00% | PollyWeb.Router
    92.86% | Polly.VoteManager
   100.00% | Polly
   100.00% | Polly.Mailer
   100.00% | Polly.PollsManager
   100.00% | Polly.Schema.Poll
   100.00% | PollyWeb
   100.00% | PollyWeb.ConnCase
   100.00% | PollyWeb.Endpoint
   100.00% | PollyWeb.ErrorJSON
   100.00% | PollyWeb.Gettext
   100.00% | PollyWeb.LoginLive.Index
   100.00% | PollyWeb.PollLive.Index
   100.00% | PollyWeb.PollyComponents
   100.00% | PollyWeb.UserSessionController
-----------|--------------------------
    81.52% | Total

How to start

To start your Phoenix server:

  • Run mix setup to install and setup dependencies
  • Start Phoenix endpoint with mix phx.server or inside IEx with iex -S mix phx.server

Now you can visit localhost:4000 from your browser.

Releases

No releases published

Packages

No packages published

Languages

  • Elixir 92.9%
  • HTML 3.5%
  • JavaScript 3.5%
  • CSS 0.1%