23.11.2021

Clojurescript is ZimTiks secret sauce

We frequently get asked about our tech-stack from some of our more technically minded freelancers and in this post we're happy to share some details from the engine room. I'll try to strike a balance between the technical and business oriented aspects.

Why Clojure?

Clojure is a Functional Programming (FP) Lisp dialect which I've been very excited about since 2007. If you've ever heard 2 developers debate which language is the best you'll hear many different topics being discussed, however at the base level we all want just 2 things: Minimum number of bugs and maximum level of productivity.

Clojure excels in both these areas. Its more robust than Java, but even if it wasn't rewriting a Java program to Clojure will reduce the line-count by about 12x. So if they had the same level of bugs/line then it's clear that the Clojure program will be much more reliable and quick to write. This is the same as when compared to C# but even more favorable when compared to C++ and PHP.

Why Clojurescript?

Back in 2007, Clojure compiled to Java Bytecode and thus ran everywhere (Windows, Linux, OSX, and more) via the JVM. In 2015 that changed when David Nolen released the first version of Clojurescript which is a Clojure compiler that emits Javascript. This means that our Backend code, now runs in the browser. This is a game-changer.

This carries some huge benefits for an app like ZimTik. Consider how we calculate the revenue generated by a certain task. The logic is fairly simply:

(defn task-revenue
  [task]
  (if-let [fp (:fixed_price task)]
    fp
    (let [hourly-rate         (get-task-hourly task)
          value-of-time-spent (* (/ (reduce + 0 (map :log_length (:logs task))) 60)
                                 hourly-rate)]
      (if (or (:overtime task) (nil? (:estimate task)) (zero? (:estimate task)))
        value-of-time-spent
        (min value-of-time-spent
             (* (/ (or (:estimate task) 0) 60)
                hourly-rate))))))

Before explaining the code, just consider the options we had for doing this calculation before Clojurescript.

  1. Send the task to the backend, have the calculation done there and returned to the browser (slow)
  2. Write 2 implementations of task-revenue and risk updating 1 and not the other, creating desync between front-/backend (fragile)

Now the exact same code runs in both the browser and on the server. And the code read likes this:

  1. Is it a fixed-price task? Then just use the fixed-price
  2. Are we paid for overtime? Then multiply time-spent by the hourly rate
  3. If its not fixed-price and we're not paid for overtime, then return no more than the estimate multiplied by the hourly rate

The only thing which isn't clear from the above snippet is how we determine the hourly rate of the task. This is how:

(defn get-task-hourly
  [task]
  (let [c-hourly (when-let [ch (:c_hourlyrate task)]
                   (if (= (:c_currency task) (:cur_name (get-base)))
                     ch
                     (>base ch (:c_currency task))))]
    (or (:hourly_rate task)
        (:proj_hourly (get-project (:project_id task)))
        c-hourly
        (:u_hourlyrate @settings)
        0)))

This function has 2 purposes.

  1. Choose an hourly rate in prioritized order: Task, Project, Customer, User
  2. Convert the revenue to the base currency, ie. the currency our user works in

The priority selection is a big time-saver. Some tasks have a special hourly rate - A software developer might for instance, give a discount for support calls. The support task would then have the reduced hourly rate, which overrides the developers default rate. But the task might also be part of a project which is sold on a specific hourly rate. Failing those 2, we look to see if there's a special hourly rate set for this customer and failing to find that, we default to the users own hourly rate. If you set ZimTik up right, you'll save many, many hours fine-tuning invoices.

Clojurescript is great for data processing

So much of software development is data processing. You get an input of some data and with it, you produce some desired output. Could be a report, a graph, a list of clients. Time-management is important for every business person, but it's crucial for freelancers to manage their time effectively. Both because when you're working on an hourly rate time is literally money, but also because as single individuals we put our reputation on the line with each task we accept.

In ZimTik you can bundle any number of tasks into a project. The scope of a project can be set either as a time-value or the sum of task estimates. This scope matters most in relation to the deadline of the project. If you have 200 hours left to climb and 7 days to climb them, you're in trouble. For this reason we show burn-down charts on the main screen of any project:

The Y-axis represent hours left of scope, ie. how close are we to the finish line? The blue dots correspond to your worklog registrations. Everytime you put in an hour the graph moves one step close to zero. At today's date the line turns red which is our prediction of when the work will be completed.

At any point during a project, if your work is not on track to finish before the agreed upon deadline, you get an alert.

In this example the burndown date, ie. the date when all estimated work is completed, is December 1st but the Deadline is November 30th, which means I need to speed things up. But this post isn't about me pulling an all nighter, obviously these types of insights are valuable to anyone working on the clock and here's how the chart is implemented:

(let [dev-period (max 1 (days-between start-date (:log_timestamp last-log)))
      total-burn (reduce + 0 (map :log_length logs))
      daily-burn (int (/ total-burn dev-period))]
  (for [day (range  (count dates))
        :let  [remaining (- scope total-burn  (* day daily-burn))]
        :when (pos? remaining)]                                                      
     {:x (+ day (days-between start-date (js/Date.))) :y remaining}))

This is the data driving the red-line. Graph-data is typically just a series of values, but these are inserted into another graph of the worklog-registrations, so we need to supply both X and Y coordinates for each point (as opposed to just Y).

To do this we work out the length of the period which has work done, ie. first worklog -> last worklog, then divide that by the total amount of work done. This gives us the burn-rate for every average day. From there - and this is the 4th line starting with (for) - We simply need to walk the number of dates and deduct the daily burn from the remaining scope - Once this hits zero, we're done.

So writing just 7 lines of code for a burndown chart might seem like a knock-down argument for Clojurescript, but wait... There's more

ZimTik on Clojurescript on React

The promise of React was automatically updated, stateful/reactive UIs driven purely by data. In days of old we would write directly to each component on the page to change the way they look and behave - and query them to get their values. For pure Javascript developers, React can still seem messy and bloated but Clojurescript's Reagent library makes defining components incredibly simple:

(defn login-form
  []
  [:form#loginForm
   [:input#username]
   [:input#password {:type :password}]
   [:button#submit  {:onClick authorize-user}]])

No matter which programming background you come from, that should be very easily readable. You have 2 inputfields and a submit button which calls the function "authorize-user" if clicked. But this is a fully fledged React-component.

But here's the kicker. When developing Clojurescript apps everything re-compiles and updates in real-time. If I changed the name of one input, that change would be reflected in the browser instantly, same goes for any associated CSS.

For the speed of development, this is a big boost. For testing, prototyping and debugging having live-updates and a live REPL which shares output with the browsers console is a real game-changer.

In summary

So putting all the parts together the conclusion is this: We use Clojure/script and Reagent because we get more done writing fewer lines of code. Having live reloads/updates etc gives us a much faster development cycle. When things break, the minimal number of lines means we spend next to no time fixing bugs.

Reagent gives us the further benefit of hooking into React with normal Clojure functions acting as component. All the bloat and boilerplate is stripped away, giving us yet another massive speedboost.

I hope this has inspired you to take a closer look at Clojurescript, it's quite fun and there's a big and growing market for these skills.

Happy freelancing!

About the author

Lau B. Jensen is a Danish Freelancer / Tech entrepreneur. He's worked mostly with Software Development and management consulting all across Europe. In 2015 he took a 5 year break from freelancing to be the CEO of a VC funded SaaS start-up.

Copyright © 2021 ZimTik. All rights reserved.