Maybe you’ve heard all the hype about serverless computing. Maybe you’ve heard disturbing rumors that serverless apps do in fact run, somewhere, somehow, on servers. Maybe you were ok with all that, but not with writing AWS Lambda functions in Java or JavaScript. Me too. Let’s do it in Clojure.
First, the use case: Lambda is great for infrequently-called or bursty services. Data processing pipelines, as an example, are a perfect function to be moved to Lambda. You don’t need to worry about provisioning capacity for big jobs, or paying for an idle server when not in use. Similarly, very small web services that don’t require on-disk operations or shared resources can run (or sleep!) happily on Lambda.
Second, the choice between JVM Clojure and ClojureScript: Even with AOT compilation, the JVM warmup penalty compared to Node.js is huge. Once invoked, Lambda will keep your image warm for an indeterminate (about five minutes, in my experience) period of time, but after that, the next invocation will once again require sometimes something like 15 seconds to get going again. If your function is doing something like chewing through big amounts of data and throwing the results into S3 or a database, that’s not a problem. If you need to quickly respond to a web request, it is.
So even though JVM Clojure is easier to write and reliably deploy (just package up a .jar and upload it), we’re going to be writing a ClojureScript web service here. To demonstrate how to integrate Node libraries with ClojureScript, the service will take a JSON payload via post and return it signed as a JSON Web Token. We’ll use cljs-lambda to get started:
$ lein new cljs-lamba jwtify
Generating fresh 'lein new' cljs-lambda project.
$ cd jwtify/
Now we’ve got a skeleton of a ClojureScript Lambda project we can flesh out to
suit our purposes. We’ll need to add two dependencies that we’re going to use to
project.clj
: [bidi "2.1.1"]
in :dependencies
, and [jsonwebtoken "7.4.1"]
in :npm
-> :dependencies
. Running lein deps
now will pull in both of
those, using npm to install the jsonwebtoken
library.
The src/jwtify/core.cljs
file generated by lein cljs-lambda
comes with a
promise-based demonstration, but we don’t need it. We’ll be using simple
core.async
channels to handle the asynchronous pattern Node.js requires, so
let’s replace all of that and start new:
This is the minimal amount of code that will get us a an entry point into a function from Amazon’s API Gateway and return a status and a JSON payload.
To test it out, we can create a dev/repl.clj
file that looks like:
Then run rlwrap lein trampoline run -m clojure.main dev/repl.clj
to get
yourself into a ClojureScript/Node.js REPL, and try:
What we’re doing with channel
is locally simulating a Lambda event that our
function will eventually receive in real life and putting the result on a
core.async
channel.
To recap: We’ve set up a ClojureScript/Node.js environment with the
cljs-lambda
library that knows how to interpret AWS Lambda events and
return responses in the esoteric format Lambda and the API Gateway expect.
In part two,
we’ll use bidi
to set up routes and response handlers for our
web service, and jsonwebtoken
to generate our JWT payloads. In part three,
we’ll see how to put it all together and deploy it to The Cloud.