What is Clojure and why do you care?
Because every post about Clojure or Clojurescript (CLJS) has to explain what Clojure(script) is and why you should use it:
But in all seriousness a bunch of people have done better introductions to Clojure then this post will:
-
Clojure for the Brave and True
- How I learned Clojure (Long)
-
Learn Clojure in Y Minutes
- A great overview of the language in a file you can run at the REPL to test (Short)
-
Hypi's Intro to Clojure Post
- A good middle ground between the above
🔥Serverless🔥
Unfortunately there isn't an amazing parody commercial for Serverless so instead let's just try to actually motivate why you should care about it. Serverless is just a shorthand for describing a way to run code without worrying about infrastructure. What does that mean practically? Give them and zip and they throw it on some unused hardware, that's it!
It supported on AWS, Google Cloud, and Azure and probably other clouds. Because the examples are built with AWS that is where we will focus.
Probably the biggest motivator for migrating to or build new services with serverless is cost. The the free tier is very generous and it's pay as you go so it's free to prototype!
You also get ease of deployment/operation and native integration with your cloud provider making it super easy to tap into services like cloud provided DBs, metrics/analytics, queues, email services, etc.
CLJS + Lambda
While there exist ways for you to plug together Clojurescript and Lambda they are coupled to specific build tooling and are a bit too much like a black box for my liking. This also is a good excuse to learn how all the pieces plug together. This post is a distillation of how to do just that. Before finally getting to the meat of the post we have to introduce one more player to the stage.
Shadow-CLJS
Shadow-CLJS is an alternative frontend to the Clojurescript compiler, it is fills the same role as lein-cljsbuild or Figwheel-main if you are familiar with the space. The big advantage it has over the others tools is awesome npm support. We can use basically all of npm with CLJS via Shadow which is important if we want to leverage things like AppSync or the AWS Node SDK.
MAKE THE DAMN LAMBDA ALREADY
Setup
Okay to being lets make a lambda, eventually we will throw away everything inside of it, but for now just leave it be.
And with our basic lambda up we just need our tools and some example code, first the tools:
brew install yarn #Or your OS equiv, apt-get, yum, etc
And that's it!
Example Code
For the example code I have forked Chenyong's minimal Shadow + Node example to create a hello world lambda with Shadow:
royalaid / minimal-shadow-cljs-nodejs
shadow-cljs hot code swapping for Node.js
Node.js example for shadow-cljs
Develop
Watch compile with with hot reloading:
yarn
yarn shadow-cljs watch app
Start program:
node target/main.js
REPL
Start a REPL connected to current running program, app
for the :build-id
:
yarn shadow-cljs cljs-repl app
Build
shadow-cljs release app
Compiles to target/main.js
.
You may find more configurations on http://doc.shadow-cljs.org/ .
Steps
- add
shadow-cljs.edn
to config compilation - compile ClojureScript
- run
node target/main.js
to start app and connect reload server
License
MIT
git clone https://github.com/royalaid/minimal-shadow-cljs-nodejs.git /tmp/lambda-example
cd /tmp/lambda-example
yarn
and now all of your deps are setup!
Code Breakdown
While the project should be pretty self explanatory I will given a high level overview of the important bits, the shadow-cljs.edn
and src/server/main.cljs
files.
shadow-cljs.edn
The shadow-cljs.edn
is the equivalent of project.clj
for lein or boot.clj
for boot, its the build configuration file for Shadow. The most important part of that file for us is the :target :node-library
line which tells shadow to setup module.exports
for use by node, this is bridge between the CLJS code with write and the Javascript that Node reads. The second most important line is :exports {:handler server.main/handler}
. This line tell Shadow what to call the export and then what CLJS function to bind to it, in our case the handler function in the Clojurescript namespace server.main
. This function is what AWS will end up calling when we execute the lambda.
main.cljs
As for the src/server/main.cljs
, this is what will eventually be run by AWS after Shadow transpiles it for us. The lone function inside of the namespace is what actually invoked. Important note, the function signature has to have 3 args (2 if using async/await, which we aren't) and must "return" its value by then invoking the cb
arg and passing the result to the second arg of that callback (cb
) invocation.
We need to do this because Lambda expects the result to come as a promise or from the callback passed in. This took me waaaaaaaaay too long too piece together and hopefully this won't trip up anyone else either. Additionally we need to be sure to return Javascript values and not Clojurescript values hence the use of #js
for the array.
Build and release time
Note: I am going to skip the process of actually developing the code but check out the earlier linked Shadow + Node example for details and workflow for how to use Shadow with Nodejs and hot-reloading.
cd /tmp/lambda-example # Get you back to the correct directory
yarn shadow-cljs release app # This transpiles and optimizes your CLJS
zip -r archive.zip target # Prep your output for upload to Lambda
Then we just upload our new zip to our old Lambda and point the handler function at our CLJS function (which will be .handler, main.handler in the recording).
🎉🎉🎉
And there we have it! If you have any questions or feedback reach out on Twitter, Mastodon, or @royalaid on the Clojurians Slack or Zulip
Top comments (0)