I believe that to really learn a programming language, I have to get involved in a project. Getting an overview of the Elixir language was great. But to be able to learn about the details of Elixir and its ecosystem I needed a side-project.
Like many developers who want to learn a new technology, I searched for a problem to solve.
If all you have is a hammer, everything looks like a nail.
Abraham Maslow
I had to find a nail for this new hammer. Luckily there was a need at my work for a planning poker tool that is integrated with Jira and works well with remote developers.
Planning poker is a estimation technique used in agile software development. Usually this is done with physical paper cards. In a remote-friendly environment, physical cards are not always useful.
At work we used the web-based tool PlanITpoker for a while but we missed an integration with Jira, our issue tracker. We also had some issues with people joining twice, when using the tool at the same time on the phone and on the notebook.
To me this sounded like a nice challenge for learning Elixir. The challange involved presence detection of team members, synchronising estimations and using external APIs. I found my nail!
Everything was geared towards the specific use-case we have. Learning Elixir was the focus, the product was secondary. I wanted to implement the following functionality.
I wanted to spend as little time as possible on the user interface. This meant for me using a template and hacking the needed Javascript. This is how step 1 "Import stories from Jira" looks like:
Step 2 "Estimate" and step 3 "Saving estimations in Jira" is best shown in a short video. Here you can see an estimation of one story from the perspective of a moderator:
The video shows: The moderator has selected a story to estimate. All team members see the same story description. Team members are giving their estimation by choosing a card for the estimated effort: XS, S, M, L or XL. When all team members have given their estimation, the results are shown and the moderator can save an estimation.
Learning a new language through a side-project works well for me. I get energy from learning and it is motivating to create something useful.
The complete source code can be found on Github.
Jira API
HTTPoison makes creating API clients in Elixir easier. I like the concept of using adapters to external APIs. Using HTTPoison.Base
allows this in a concise manner. An example:
defmodule Jira.API do use HTTPoison.Base def process_url(url) do Application.get_env(:jira, :host) <> url end def process_response_body(body) do Poison.decode!(body) end def backlog(board_id) when is_integer(board_id) do get!("/rest/agile/1.0/board/#{board_id}/backlog").body endend
Using Jira.API.backlog/1
returns the response of the /rest/agile/1.0/board/#{board_id}/backlog
endpoint. The backlog
function is then used in the rest of the code. This allows easy mocking in automated tests.
Caching
To not overload the Jira API with unnecessary requests I wanted to cache the backlog and allow users to invalidate this cache if needed. I made this work by using the library con_cache
.
The implementation was surprisingly concise:
def backlog(board_id) do ConCache.get_or_store(:jira_backlog, board_id, fn() -> API.backlog(board_id) end)enddef invalidate_backlog(board_id) do ConCache.delete(:jira_backlog, board_id)end
When working with NodeJS or PHP I would have eventually used Redis or Memcached for this. Thanks to using ETS (Erlang Term Storage) under the hood, con_cache
is already a good enough implementation.
Phoenix Presence
Team members should be able to join and leave an estimation session. The state of team members should be synchronised across devices. Phoenix Presence was a perfect tool for this.
Implementing Presence was straight forward. There are many examples available. Doing presence detection with NodeJS would be totally possible. What makes Phoenix Presence so special is that it synchronises presence information between multiple Erlang nodes automatically, without a central data store and with strong eventual consistency (CRDT).
Phoenix Channels
During estimation sessions I wanted to have all information synchronised between team members. When a moderator selects a story to estimate, the description of the story should be shown for every team member. When a team member votes, it should be visible immediately.
I used socket.io for soft-realtime communication in other projects before. Programming with Phoenix Channels felt very similar to that. The Phoenix documentation has a great walkthrough how to implement Phoenix Channels.
In following example of a new vote coming in, I store the vote and then broadcast the new vote to all other team members.
def handle_in("vote:new", message, socket) do {:ok, vote} = Votes.insert_vote(%{ topic: socket.topic, user_id: socket.assigns.user["id"], issue_key: message["issue_key"], vote: message["vote"], }) broadcast! socket, "vote:new", VoteView.render("vote.json", vote) {:noreply, socket}end
GitHub login
For logging in team members I used Github. This had the added benefit of having an avatar and a name. After a bit of research I found ueberauth
and ueberauth_github
.
I had some troubles understanding how to use the library. When I found out how to implement the auth callbacks it went well.
Deployment
For me it was the first time using Heroku. I like that Heroku provides a free plan for side-projects. Following the instructions in the Phoenix documentation got my Elixir app deployed in no time and without hassle. I have also setup the Github integration to auto-deploy when pushing to the master
branch.
Using Elixir has been fun. I'm far from being done learning about it. My ideas what to look into: