TutorialsJul 22, 20217 min read

Clojure microservices for JavaScript developers

Tyler Sullberg

Software Engineer

Developer C sits at a desk working on a beginner-level project

This series was co-written by Tyler Sullberg and Musa Barighzaai.

CircleCI is growing, which is wonderful. However, one of the growth challenges we have is that our backend is primarily written in Clojure, and few developers know Clojure. Many CircleCI engineers, including myself, have learned Clojure on the job.

Before joining CircleCI, I was a JavaScript developer. As the lingua franca of software engineers, JavaScript is a relatively straightforward language to learn. Sure, it has (many) quirks but those are outweighed by the exceptional documentation, countless tutorial Medium posts, and highly-vetted Stack Overflow answers.

In contrast, Clojure is a somewhat less welcoming environment. Once you learn how to navigate the dense forests of parentheses, it is easy to learn the basics of writing Clojure functions. However, building usable microservices has a steep learning curve. There are few resources for devs in this phase of their Clojure journey.

This is the first installment in a series of posts that show how to set up a Clojure microservice:

In these posts, we’ll use JavaScript as a point of comparison for understanding what is new and interesting about Clojure. We will not be covering ClojureScript, a compiler for Clojure that targets JavaScript (that deserves its own blog post). My assumption is that you already know the basics of writing Clojure functions, so we can jump right into setting up a simple Clojure microservice.

By the end of this series, we hope you will find that writing Clojure microservices can be simple and delightful.

Clojure vs JavaScript

Before we jump into creating your first Clojure microservice, we should explore the key differences and similarities between Clojure and JavaScript. This will help us mentally prepare ourselves for what it means to think in Clojure instead of JavaScript.

Functional programming

The basic premise of Clojure’s design is that a developer’s ability to create complexity in an object-oriented program far outstrips their ability to understand that complexity. The whole point of Clojure is to get, transform, and move data as simply as possible.

The primary way this manifests itself in Clojure is through a functional programming style. Luckily, if you’re coming from JavaScript, this functional style will be somewhat familiar. After all, functions are first-class in JavaScript, and modern web development (e.g. React) is often written in a functional style. In fact, Clojure was purportedly an inspiration for React’s design.

There are common patterns used in JavaScript that you will not see Clojure, though:

  • Creating massive objects to manage complex processes like maintaining a database connection
  • Defining functions in objects as a way of namespacing that function
  • Maintaining state in objects

The practical implication of this is that, at least by Peter Norvig’s estimate, 16 of 23 of the classic “Gang of Four” design patterns are obsolete in a functional paradigm. In the next post in this series, we’ll explore how our microservices can manage complex processes like a database or RabbitMQ connection without traditional objects.

Immutability

In object-oriented languages, objects are passed by reference. Mutability of objects can make your JavaScript application less predictable and more difficult to debug.

const cheese = { foodGroup: "dairy" };
const limburger = cheese;
limburger.smellsBad = true;
console.log(cheese);
// {foodGroup: "dairy", smellsBad: true}

In JavaScript, you can get around this with an immutability library; for example, Immutable.js, Lodash, or Ramda. In Clojure, data is immutable by default.

(def cheese {:food-group "dairy"})
(def limburger (assoc cheese :smells-bad true))
(println cheese)
; {food-group "dairy"}

Immutable data structures make it easier for us to write pure, easily testable functions. This fact will shape how we approach composing functionality in our microservice. Compared to JavaScript, Clojure tends to be written with smaller pure functions that are reused and remixed into larger functions.

Dynamic types

Of course. JavaScript is also dynamically typed, so we might expect that there would be a TypeScript equivalent for Clojure. That is not quite the case.

There is a Clojure core library called Clojure.spec that at first glance seems like Clojure’s TypeScript. However, Clojure.spec is not a type system and has important differences from TypeScript:

  • Clojure.spec is designed for flexibility and dynamically combining data. It’s not as strict as TypeScript, but gives you the ability to define what data should look like in practically any way you can imagine. For example, say that the return type of a function should be a prime number.
  • Clojure.spec runs at runtime not compile time. This means you won’t get hints in your IDE that an argument doesn’t match a required type, but it does mean that you can use Clojure.spec to check types in production (e.g. validating data from an API in real-time)
  • Clojure.spec allows you to automatically generate tests rather than writing individual unit tests.

In short, dynamic types in Clojure are an explicit design decision of the language (albeit a controversial one), and Clojure.spec is optimized around dynamic programming. There isn’t a TypeScript analogue for Clojure, and that’s intentional.

Everything is data

Coming from the JavaScript world, another strange property of Clojure (and Lisps generally) is that code is data (homoiconicity for the pretentious).

You can tell Clojure to treat a function call as a list data structure, rather than evaluate it, with a . In this example, we create a definition that is equal to an unevaluated function call. This unevaluated function call is simply a list. Then we take the first member of that list, the addition operator.

clj꞉dev꞉>  (def code-is-data '(+ 1 3))
clj꞉dev꞉> (first code-is-data)
; +

Understanding this aspect of Clojure is important for understanding macros. Macros look very similar to functions; they take arguments and have a body. However, they are different from functions in one critical way. Functions evaluate their arguments and return data while macros return a data structure that then gets evaluated. To demonstrate, we can make a macro that uses infix rather than prefix notation.

clj꞉dev꞉>  (defmacro infix
            [[num-1 operator num-2]]
            (list operator num-1 num-2))
clj꞉dev꞉>  (infix (2 * 3))
; 6

As you build up a Clojure microservice, you will frequently encounter macros in external libraries. These macros can initially be confusing because it may seem like they are violating Clojure syntax rules. In reality, they are _extending _Clojure’s syntax.

JVM

Earlier I said that in Clojure, you write functional code rather than creating stateful objects. Well, that is not completely true.

Clojure runs on the JVM platform (though Clojure can also compile to JavaScript). This means that in Clojure you have access to everything that the world of Java has to offer. You can import Java libraries and create classes.

It is a “have your cake and eat it too situation” that combines the expressiveness of a functional, dynamic language with the power and trust of the JVM.

For example, we can use Clojure’s “new” form to create an instance of Java’s Date class.

(defn now [] (new java.util.Date))
(now)
; #inst "2021-04-22T15:06:19.329-00:00"

REPL

Both Clojure and JavaScript allow you to write code in a REPL (Read-Eval-Print Loop). However, in JavaScript, we tend to lean on test-driven-development and running our web app locally to power our development workflow.

In Clojure (and many other Lisps), the REPL is central to the development workflow. In fact, Clojure was specifically designed for a REPL-driven workflow. Clojure’s REPL allows you an interactive and flexible development experience that ultimately tightens your iteration/feedback loop.

If you are feeling adventurous, you can even use Clojure’s REPL to modify code in production without making a commit. Famously, the Deep Space 1 probe used a Common LISP REPL to fix a bug while the probe was 100 million miles away.

Wrapping up

As a JavaScript developer, thinking in Clojure may come naturally to you. You may already write code in a functional style, use dynamic typing, and write code in a REPL.

Still, Clojure is fundamentally different from JavaScript in several key respects: immutable data, polymorphism rather than defining methods within objects, and homoiconicity (code is data). Any of those aspects could be a challenge for JavaScript developers.

Understanding the similarities and differences between Clojure and JavaScript will serve you well in our next post, where we will build your first Clojure microservice.

Copy to clipboard