The goal is:
- To deploy a serverless application to AWS
- To see the REPL-based workflow in action for a CLJS project
- To build a mini-API
Note to instructors: periodically stop and make sure people are following along with a show of hands. Help out where needed.
Pre-requisites: Must have Git and Java 8+ installed.
This is easiest using a package manager.
Mac, using Homebrew: brew install leiningen
Ubuntu: apt install leiningen-clojure
(other distros are similar)
Windows, using Chocolatey: choco install lein
Alternatively, follow the instructions at https://leiningen.org/
For Ubuntu, follow this guide
For mac, use homebrew:
brew install nvm
nvm ls-remote
nvm install <version-you-want>
nvm use <version-you-want>
When node is available,
npm install serverless -g
You can confirm it's available with which serverless
if you're not sure it worked.
- Get an AWS account and make sure your billing is set to free tier only
- Configure keys for CLI access for your user
- Get the AWS CLI tools
- Set up a user with this policy
Clone this Git repository.
Note that if you wanted to start from scratch, we're just using the figwheel-node
lein package. You can read more about how to use this with Serverless at this blog post, or generate a new project with:
lein new figwheel-node <your-project-name>
If you want a batteries-included but opinionated setup, there's also the serverless-cljs plugin, which uses cljs-lambda under the hood. It uses promises or channels under the hood, which is cool once you get your head around it - the docs are here - but probably will be too unfamiliar for our purposes here.
The method we're describing has the merit of basically looking the same as the Node equivalent of the code, except in Clojurescript.
Emacs: If you're an emacs user, then rejoice! There's a number of different setups you can use. This is probably the best beginner guide, but on the whole if you've got a recent install (25 or 26) then that will do. If you want a slicker experience, then maybe try Spacemacs, although we prefer it in non-Evil mode.
Atom: ProtoREPL for Atom - readme here - this is a great all-rounder with some ace in-line evaluation options, good repl and emacs-style tight integration. If you want to mirror the author of ProtoREPL's setup, then his guide is here.
VS Code: Many people are now beginning to use VS Code for Clojure. There are some decent guides and plugins out there, and with our test group this was the best plugin to use. It seems that before this, another plugin was preferred, but we had issues and it seems we're not the only ones.
Sublime Text: You can do this, but it will be painful. We only edit Clojure in Sublime if we're forced to by natural disaster or lack of laptop.
Let's look at the basic syntax you need to read and write Clojure.
As an aside, it might be good to look at an AST with this tool:
def hello ():
print("Hello, world!")
This is important. If we can modify the AST ourselves, we can extend the language in userspace. This is the power of homoiconicity, and it is leveraged using the macro system.
We'll be using Clojurescript for this session - the code we write will not be compiled and run on the JVM, but instead will be run by the node runtime. Clojurescript uses the Google Closure Compiler (no relation).
As a result, not everything you'd expect to work in JVM Clojure behaves in the same way - for example, threading, async and channels, as you don't have native threads in the same way.
The async primitives of the Clojure language do turn out to be pretty useful for taming Javascript, however.
Change directory into the example project.
We'll need to build the project before we can do anything else - this is accomplished by running
npm install
lein cljsbuild once
After that second command, you'll get a lot of output. Just wait for it to finish, and then it's time to get a REPL running!
If something blows up, and you're using Java 9, you might need to add something to your project.clj
:
:jvm-opts [“ — add-modules” “java.xml.bind”]
In our first tab, we start figwheel:
lein figwheel
Figwheel will compile the code, and then stick on a message, something like:
Prompt will show when Figwheel connects to your application
This is because it's waiting for the app to be run - luckily that's as simple as running the compiled output. We just open a second tab and run the compiled output using node. This should be re-evaluated as we make changes.
node target/js/compiled/cljs_serverless_workshop.js
Once this has run, the figwheel prompt should change:
To quit, type: :cljs/quit
dev:cljs.user=>
To test our function, we can change to its namespace and execute it in the REPL (more advanced users can do this via EMACS or their editor of choice).
dev:cljs.user=> (ns cljs-serverless-workshop.core)
nil
dev:cljs-serverless-workshop.core=> (hello nil nil print)
nil #js {:statusCode 200, :headers #js {:Content-Type application/json}, :body {"msg":"Hello, serverless!"}}
nil
To test our reloading is working, go to the file src/cljs_serverless_workshop/core.cljs
and change the :body
on line 18 so that the message says "Hello <your-name"
. Save the file, switch to the repl and run your function again.
I'll change it to my nick, the-frey
, which is a reference to an in-joke in the comic Calvin and Hobbes.
Because of things being a bit temperamental with connecting to a remote CLJS REPL, I tend to run my CLJS REPL inside of EMACS. Here's how you would do that:
In the terminal
lein repl
In Emacs:
M-x cider-connect
In the prompt:
(use 'figwheel-sidecar.repl-api)
(start-figwheel!)
(cljs-repl)
Then, in another terminal window:
node target/js/compiled/cljs_serverless_workshop.js
I then C-c C-l
to load my ns and switch to it using C-c M-n
in the REPL window.
I actually scripted this one, so if you have started a repl via lein repl
, at the user prompt, you should be able to simply use (go!)
.
user> (go!)
...
cljs.user>
Pretty cool.
Before we go into how you can test your build via the framework, just be aware that this is a production version of the code, saved to the prod
folder in target
.
Running lein cljsbuild once
constantly is boring, so we can instead watch for changes in our files, and constantly rebuild, meaning that we can poke our code either via the REPL, or via cURL (with sls-offline
) or by invoking locally.
To watch for changes:
lein cljsbuild auto
The sls
command allows us to act like mock requests to our lambda. If we lein cljsbuild once
, it will build our prod artifact, which we can test against.
Run it:
sls invoke local -l -f hello
If we do that now, we should get:
{
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"msg": "Hello, the-frey!"
}
}
As we're just using node deps, it's also very easy to use the serverless offline plugin, which I use in my node serverless projects. This allows you to treat your local build like a dev server.
To run it:
sls offline
Then, you can cURL it:
curl -v localhost:3000/hello
Which should give you something like:
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET /hello HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: application/json; charset=utf-8
< cache-control: no-cache
< content-length: 28
< vary: accept-encoding
< accept-ranges: bytes
< Date: Sat, 18 May 2019 14:06:46 GMT
< Connection: keep-alive
<
* Connection #0 to host localhost left intact
{"msg":"Hello, the-frey!"}%
If you have your AWS keys set up correctly - reminder, here's the guide on the serverless framework site - then you should be able to deploy with:
sls deploy
This command will output an endpoint for the function, which you can cURL, if you want to check it's working.
So now you're probably thinking that this sounds pretty intense. Don't worry though! We can use node tools to help us.
I've used the concurrently
package to script our lein auto builder, and our sls offline server. This means that any changes will be picked up automatically, after the first one that triggers a build. To use this:
npm run offline
(you can see this task inpackage.json
)- make a change in your file - this can be whitespace
- once the dev artifact has built, we can use it to connect to figwheel. Instead of running the long
node
command, it's available asnpm run node-watcher
If you're curious as to how these work, look in the package.json
. You'll see the names simply line up with the paths and artifacts found in project.clj
and the artifact location referenced in serverless.yml
. It looks mysterious and scary, but it's really not!
package.json
is where all your node deps liveproject.clj
is where your CLJS build is configuredserverless.yml
is where your deployment is configured - think of this like the YAML files you use for a Kubernetes deployment.
The files in serverless.yml
are also used by sls-offline
, so that's quite a good sense-check.
Phew! That's a lot to take in. Let's take a break.
Note to instructor: use this exercise to continue and implement a passphrase generator.
The first step will be importing the CSV file. It's on GitHub Gist here - if you make a web request, you'll get it back as text/csv
, or you can save the file and load it when the lambda initializes.
If you want an HTTP library, use httpurr because it's fun, and cool, and bundled with this example project.
If you don't want to spend ages hacking around with promises and whatnot, then you can shortcut straight to having a debuggable checkpoint with the CSV working, on this branch.
From there it should be easy to see how the task is completed.
TinyURL to this set of instructions: https://tinyurl.com/ll-may-cljs-sl
TinyURL to the code repo: https://tinyurl.com/ll-may-code