Clojure is a powerful language, but as with any language, its true power comes through extensibility. The ability to plug in additional modules and open up functionality is key to the proper development of a Clojure application, and as such, understanding both how Clojure functions and how the various frameworks interlock is key to proper development.
Today, we’re going to take a look at Clojure, and observe some common frameworks on offer. We’ll briefly summarize these frameworks, and hopefully create a resource for new Clojure developers to find their given solution. Keep in mind that this list is provided in no specific order, and serves only to inform as to the general offerings available.
What is Clojure (Briefly)?
Clojure is a unique language designed from the ground up to be the best of many qualities. The author, Rich Hickey, laid out a few requirements that he was looking for before he decided to create Clojure:
Why did I write yet another programming language? Basically because I wanted:
- A Lisp
- for Functional Programming
- symbiotic with an established Platform
- designed for Concurrency
and couldn’t find one.
With this in mind, Hickey began developing Clojure to be a “wishlist language” of sorts. He started by focusing on making a modern Lisp-like variant that was symbiotic with Java, considering JVM to be an established, proven, and trusted platform. He went so far as to say in Clojure’s documentation the he considers virtual machines, not OSes, as the platforms of the future, and hedged his bets upon this argument. From here, he incorporated the idea of identities, tying them directly to states expressed over time. The entire concept of states hinges on immutable qualities to data, with concurrency then handling these changes of state using reference types.
All of this was built upon the principle that the language would be symbiotic with Java — Clojure itself runs on the JVM (Java Virtual Machine), and as such, Java code can be used to call Clojure functions and vice versa. Clojure is based upon S-expressions , meaning that, like most Lisp implementations, the syntax is first parsed into data structures by a defined reader before it is compiled.
A huge focus for Clojure was the idea that Clojure should be couched in functional programming. The idea of immutable data structures and avoiding static typing led to the adoption of heavily dynamic types with a strictly immutable, persistent data structure at its core. This hybrid approach ultimately flies in the face of the object orientation that has become so common in programming, even when it is inappropriate to use.
It should be noted that, while Clojure is in itself a language, in practice, with its heavy core community functioning to continually develop and improve it, it almost forms a platform of templates and modular libraries more so than just a simple language. Because of this, Clojure has much to offer — chances are that, if you want to do something with Clojure, the solution is out there.
Of course, this focus on community iteration means there’s a great many possibilities for specific implementations. The aforementioned breadth of templates and modular libraries is at the core of what makes Clojure so attractive – it’s universal enough to work on almost any JVM, while being dynamic to the point of offering a wide range of functionality. Let’s take a look at a few of these possible frameworks.
Compojure and Alternative Routing Libraries
Right off the bat, we should address the issue of routing libraries through its largest representative — Compojure. Though not technically a framework, Compojure is often described as such. Compojure is largely a library that handles routing, and very little else beyond tertiary functions for that purpose. One of the reasons it is often considered a framework is because it is so widely adopted.
That being said, there are a great many alternatives to Compojure, including gudu, bidi, Reitit, and playnice. These are useful in their own use cases, but are outside the scope of this piece as they are not really complete frameworks.
Catacumba is a relatively young framework built upon the idea of building an asynchronous alternative to Ring without the limitations inherent in that implementation. The focus on asynchronous web applications sets Catacumba aside from others in this list, especially in its support for abstractions like promises and reactive-streams. Its focus on reducing constraints is mirrored in its development codebase, and because of this, the implementation is highly modular, powerful, and lightweight.
Unfortunately, Catacumba’s specific focus means that it’s not really fit for full-stack integration – it’s limited, powerful in what it does, but ultimately specifically meant for asynchronous, un-opinionated applications. Due to this, it has rather limited applications, and should be considered only when the application itself meets the requirements that drove the creation of Catacumba in the first place.
ClojureHomePage is a unique framework in that it focuses almost entirely on creating a webpage utilizing Clojure for both the front and backend. It supports a wide range of web headers, environmental variables, and routes, and is capable of generating quite complex HTML and CSS. Additionally, its support for SQL functions means that, while the frontend is being generated, the backend is as well in a very tightly integrated and powerful way.
Of course, just like Catacumba, this raises a question as to whether or not ClojureHomePage is appropriate for any situation in which a homepage is not being generated. One could argue that it might be effective to create an API as if it were a homepage, creating a webservice couched in a portal, but in some cases, a Clojure API may never be public facing in that way. Accordingly, in this case, consider requirements before implementation.
Another of the single page web application frameworks, Hoplon provides a great number of Clojure and ClojureScript libraries to develop these website-applications. Hoplon utilizes three principle libraries – HLisp is utilized for forming custom HTML elements as regular functions, Javelin provides a spreadsheet-like interface for modeling reactive behavior, and Castra facilitates all server interactions.
Again, though, Hoplon is quite limited to a singular function, and the modules that are required to do those functions. As such, it is highly limited, and if you don’t actually intend on building a single-page web application, its value is quite limited.
Luminus defines itself as a micro-framework, and that’s pretty much what it is – as such, it only does a few things, but does them very well. Lumius functions as a sort of templating system, providing an embedded development system and some default modules to jumpstart development.
That being said, there is something to be said for creating purpose-built modules for your specific implementation, and opting to not do that in exchange for a more speedy development can incur its own costs in future development.
Of course, there is a huge benefit to pre-defined modularity, and it should be noted that being able to spin up a project almost immediately while also disabling modules you have no use for results in a low-resource cost, high efficacy implementation.
Moustache is another micro-framework for Clojure, but the space it operates is quite important – it acts as a sort of transit between Ring and various other middlewares. While this might seem simple – and, in fact, the only macro needed to run it is
app according to its own documentation – its use facilitates a wide range of interactions. Anything that needs a Ring handler, a defined route, a string of plaintext, or an HTTP method dispatch can utilize Moustache.
This is another case wherein the framework is so “micro” that it’s hard to consider it a framework proper – that being said, due to the potential it enables, it could be considered for Clojure APIs, especially considering the fact that REST APIs can benefit from its focus on tying HTTP content together, which is in essence a major stage of facilitation for hypermedia integration.
Macchiato is on the other end of the spectrum compared to Moustache, offering a veritable breadth and range of options within its various libraries. Each component is self-contained, making it highly modular, and these modules themselves facilitate quite a bit of functionality.
For instance, we can look at three random libraries implemented in the framework to see how broad these options are.
macchiato-crypto is a library that securely hashes passwords utilizing either bcrypt or scrypt. It also supports checking these passwords against a plaintext password for hashing purposes.
macchiato-db-scratchpad is an experimental system that allows for testing different database implementations and approaches within Macchiato.
macchiato-template is a simple templating system that allows for rapid creation of Macchiato-based apps.
As you can see, the wide range of options that Macchiato enables is quite valuable, and as such, if you’re beginning your development from the ground up, it’s certainly something to consider.
Duct is an Integrant-based application creation framework, designed to be highly modular. In essence, Duct forms a type of master “blueprint” for applications using an immutable configuration file with a high preference for local state over global state. Additionally, Duct prefers grouping functions by purpose rather than by layer, which makes development somewhat more intuitive and expressive compared to the traditional “layer” grouping that often dominates traditional development methods.
That being said, calling Duct a framework is a bit off – in practice it’s much more of a template, albeit a powerful one. Again, if you are starting development from the bottom, it’s definitely a consideration worth making, but for existing APIs and applications, the idea of converting to a whole new template could make its adoption hard to go along with.
Conjure is based around the idea of simplifying database-backed applications through the use of a Rails-like framework. To this end, Conjure relies heavily on existing third party libraries, as admitted to in its own documentation. That being said, Conjure is a powerful library for simplifying the database aspects of a modern application, which is often the most laborious and complex stage of the development lifecycle. For this reason alone, it should be considered, though within the confines of understanding what it functionally does, how limited it is to a specific functional area, and the fact that it has third-party dependence.
Pedestal is a very powerful set of libraries designed specifically around the construction of APIs. While many on this list focus on the creation of single-page applications, referred to as the “page model” in Pedestal documentation, Pedestal is designed to specifically build APIs first. It is highly modular as well, making it a prime choice for API-specific development.
The real power behind Pedestal is the fact that it is built upon protocol connections, allowing for a high degree of customization, almost bordering on “plug and play.” Additionally, the fact that it supports secure headers and other best practices makes it a key offering for APIs in Clojure that intend to have quality security, as any good modern application should be.
- Fulcro: A library for development of single-page full-stack web applications in clj/cljs
- Re-frame: A Reagent Framework For Writing SPAs, in Clojurescript.
Clojure is a perfect example of modular power. While Clojure itself is certainly a useful language, it’s somewhat limited to a specific use case and function. Adopting additional modular libraries and frameworks makes Clojure a strong contender for the development of modern web applications, services, and APIs.
That being said, each of these frameworks have their own caveats, not the least of which is the fact that a given application might require several libraries, modules, and frameworks to even do what is required in the first place. While that might be a con to some, however, it is a benefit to others – the idea of a highly modular system is certainly valuable, and in conjunction with proper design ethos and approaches can result in something greater than the sum of its parts.