Yipgo is a medium-sized project - it has about 9,500 lines of Clojure and Clojurescript including the tests. It’s not the first Clojure project I’ve started and yet I continue to learn things every day that I work on the codebase. I’ve made plenty of mistakes and there’s some early decisions I made that I think I nailed - be it through luck or judgement. Below I’ll outline some of those decisions and how they’ve affected the build, hopefully it’ll be useful to developers, especially Clojure ones.

Just in case you don’t know, Clojure is a functional lisp-a-like language whose compiler outputs JVM bytecode. Clojurescript is the same language but compiles it to Javascript.

What’s Yipgo

For a quick bit of context Yipgo is a ticketing system, a little like a light-weight, fast Jira. It’s built with responsiveness and ease of use in mind. Ok, context and a bit of marketing. :)

The mistakes

Not using clojure.spec earlier

Saftey net provided by typing

Clojure is a dynamically typed language. This means that type checking happens at runtime and removes the safety net that strongly typed languages benefit from.

There’s a relatively young library which is developed by the core Clojure team called Clojure.Spec. It’s job is to provide specifications for data and functions which can be checked when you run your tests and/or in development builds. If you spec your low-level, fundamental operations and datastructures then you will catch a plethora of issues that may have passed by until production runtime. It’s a great compromise when it comes to dynamic vs typed trade-offs. I’m sure other languages have similar systems but this one is ours.

Despite the sales pitch above, I avoided Spec. I would like to think it was out of concern for the evolving API and the - at the time - shoddy error messages, but in reality I expect it was lazyness.

Not surprisingly it was a huge mistake as I had some bugs that would have been caught early with the system in place. It was a particularly silly bug that ultimately persuaded me to start using Spec and over a couple of days I retrofitted the backend code with the checks and found an embarrassing amount of issues that I could fix without them biting me in production. Spec allowed me to solve a hefty chunk of technical debt in one fell swoop, if you’re a Clojure person, then please make it part of your toolkit.

Not making use of front/back shared structures

Clojure and Clojurescript

Clojure has a method for sharing code between the backend and the frontend through the use of .cljc files that can, in theory, be compiled by both the Clojure and Clojurescript compilers. Though the use of reader macros (think of them as language syntax for now), you can run snippets of code conditionally for either platform. For example, here’s a simple log function that can be imported into both front and back-end code which will behave similarly:

(ns yipgo.c.log)

(defn dbg [obj]
  ;; this line will be compiled by the Clojure compiler
  (#?(:clj clojure.pprint/pprint
      ;; this line will be compiled by the Clojurescript compiler
      :cljs cljs.pprint/pprint)
   obj)
  obj)

To a non-Clojure developer, apologies, it’s going to be a little hard to parse the first time you see it. Basically, there’s a conditional defined there using the macro #? that tells either the Clojure (:clj) or the Clojurescript (cljs) compilers which bits they should compile and what to ignore. This means that I can use the dbg function all over the codebase. It can be a bit ugly - think #ifdef in C - but it’s so handy to maintain identical datastructures between the back and front.

Early in the project I didn’t bother with the shared code and found myself writing very similar back and frontend code, negating the benefits of having one language for everything. More recently I’ve been porting my models to cljc files and therefore allowing them to be spec’d during development as well as saving time and debugging effort changing them in two places. The data I send to the REST API from the front-end isn’t identical to the structure I eventually give to the database, so there’s some munging/inheritance involved, but it’s a small price to pay.

Front-end testing

This one is a much more generic issue and I’m a bit ashamed to admit it but the project has maintained about 80% back-end test coverage and at best 10% front-end coverage. Clojurescript comes with cljs.test which is just like its clj.test counterpart that I use extensively for the backend so it’s not a particularly hard thing to get going. Writing and especially refactoring code without tests is a bit like riding a bike without a helmet.

However, code that is shared between the front and back only needs testing once, so that allows a little gap in the coverage.

The not-so-mistakes

Picking Clojure itself

This already feels like a bit of a fanboy homage to Clojure, so I’ll let others tell you why Clojure is a good pick:

EDN everywhere

EDN (extensible data notation [eed-n]) is another clojure-specific technology. A quick way to describe it is as the language’s equivalent of JSON. However, EDN has different goals over JSON which I think makes it a much better fit for what I’m doing here at Yipgo.

First and foremost, it’s extensible, this means you can build readers to handle custom elements within the EDN stream.

Secondly, it comes with a rich set of default elements including datetimes and uuids, both of which are used extensively in Yip.

The Yipgo rest API speaks in EDN to the backend so there are minimal conversions happening - I’ve not had to implement any custom readers yet. If I’d have done the data transfer in JSON, there would have to be logic to support uuids, dates etc.

The disadvantage with this is that browsing JSON datastructures in a browsers console (after, say a console.log) is a reasonably pleasant experience as it’s formatted to an interactive, browsable tree. EDN gets dumped out as a plain string. Small enough price to pay.