sun, 22:43 at the minemoon, waxing crescent, 36% lit - 22:43 at the mine(spicemine)ಠ_ಠ^_^(・o・)β(-_-)

sign inEN日本

mélangej · ABCL on Jooby and Netty

Who it is for

mélangej is for people who want to write Lisp and deploy like everyone else.

That sentence hides a trade most Lisp web stacks make in the other direction. On SBCL you get fast startup, native code and a small image, and in exchange you get an ecosystem where every library is one person's weekend, deployment is a saved core you built yourself, and the operations team has never seen one before. On the JVM you get twenty years of libraries, every monitoring agent, every container base image, every CI runner, and a profiler anyone can read — and normally you pay for it by writing Java.

mélangej is the other arrangement: Lisp for the part that changes, the JVM for the part that has to be boring.

Five people this is for

The Lisp programmer with a production problem. You want macros, conditions, CLOS and a REPL you can reason in, and you also need JGit, a JDBC driver, a TLS terminator and something your hosting provider will run without a conversation. You can have both. The rules are Lisp; the plumbing is a jar.

The team already on the JVM. You have Maven or Gradle, a CI pipeline, a JVM in production and people who know how to read a heap dump. mélangej is a jar on that classpath. Nothing about your deployment changes — same artifact shape, same -Xmx, same metrics agent, same kill -3. The Lisp is an implementation detail of one service.

Someone who wants to read the whole thing. Thirteen files, 7,204 lines, no dependency graph. Every behaviour you can observe is in a file you can open. That is unusual at any size and nearly extinct for anything with authentication and multi-tenancy in it.

A long-lived service whose rules keep moving. Permissions, wording, localization, theming — the parts that change weekly and that a type system does not help with. These are the parts Lisp makes cheap: a message catalog is a list, a theme is an alist, a page is a function. Chapter 11 puts rendering at about 1 CPU-millisecond for a 35 KB page; for a service like this, that is not the constraint.

A self-hoster who intends to still be running it in five years. The runtime is a JVM and the dependencies are three libraries with long histories. There is no framework release treadmill, no transitive graph to re-audit every quarter, and no package manager that has to still exist.

Who it is not for

Anything where process startup is in the request path. Cold start is about two seconds, nearly all of it the JVM and ABCL themselves. Serverless functions, a CLI that starts a server per invocation, autoscaling that spins up under load: wrong shape. If that is your shape, SBCL with a saved core starts in 27 ms and is the right Lisp.

Pages served tens of thousands of times a second. Java renders the same bytes about 8× faster and Rust about 9× faster again (chapter 11). Put a cache in front, write that one page in Java, or use Java — all three are better answers than tuning this.

A team with nobody who wants to read Lisp. This is the real constraint and no benchmark argues with it. A codebase one person can read and nobody else will is worse than a dull one everybody can.

A front-end team that owns the whole markup. The default is s-expressions in Lisp files. There is now a second option — .html files with Lisp inside them (chapter 5) — which is enough for someone to restyle and rearrange a page without touching Lisp source, and the words and colours were already editable without code. It is still not the same as handing the templates over.

What it can't do

It will not give you the CL ecosystem. Taking a Quicklisp dependency means taking its transitive graph, mostly written and tested on SBCL. So some things you would install, you write: this project has its own Markdown renderer, HTML emitter, JSON codec and migration machinery, a few hundred lines each. Affordable because each is small. Not affordable for everything — if your problem needs a PDF library, an image pipeline or a numerical stack, take it from Java rather than from Lisp, and if it has to come from Lisp, this is the wrong architecture.

Java interop leaks, in known places. A java.lang.String out of a generic Java container is a JAVA-OBJECT, and string= on it is silently false. jnew-runtime-class is needed because Jooby reads handler bytecode. java:make-immediate-object is deprecated and still the way to pass a boolean to some setters. mélangej confines all of it to spice-http.lisp, which contains the problem rather than solving it.

Hot upgrade works well, and is still not a deployment method. On a running server you can redefine a function, load a changed file, load a freshly compiled fasl, add a jar, and - through the Attach API - redefine the method bodies of Java classes that are already loaded. What you cannot do is add a URL (Jooby's route table is fixed at start) or change a Java class's shape. Chapter 10 measures all of it.

That it works is not a reason to deploy with it. A hot-patched image behaves like the files plus whatever somebody typed into it, and that second part is in nobody's version control. Use it for the emergency and the autopsy; deploy with the deploy script.

No component model and no client framework — but partial updates do exist. A page is a function of the request and the only server state is a session row, and chapter 6's Datastar fragments do not change that: an event stream is a response, computed from the request exactly as a page is. That is a feature for scaling and a limitation if what you wanted was stateful server-side components, which this will not grow.

Startup is about two and a half seconds, and part of it is ours. On spicemine 0.71.0 and JDK 17: 0.66 s for ABCL itself, 0.55 s to load our 27 compiled files, 1.16 s to start the service (chapter 11). It was 9.4 seconds until two require forms went; there is no third one to remove, but the service's own start has grown with the service, and the JVM's AOT cache takes a quarter off on JDK 25 and later.

The one rule

If you take nothing else: you should be able to read the whole thing. Every decision in this book — no Lisp dependencies, one file per concern, a message catalog instead of strings in pages, tokens instead of colours, escaping by default — falls out of that. The architecture is downstream of wanting to understand it.

← PerformanceWhat comes next →