Skip to content

Instantly share code, notes, and snippets.

@the-frey
Last active March 23, 2021 22:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save the-frey/767a9c403e691df9915e3ecf04f05594 to your computer and use it in GitHub Desktop.
Save the-frey/767a9c403e691df9915e3ecf04f05594 to your computer and use it in GitHub Desktop.
Clojurescript Serverless Workshop

Goal

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.

Getting set up

Pre-requisites: Must have Git and Java 8+ installed.

Install Leiningen

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/

Install Nodejs via NVM

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>

Install Serverless

When node is available,

npm install serverless -g

You can confirm it's available with which serverless if you're not sure it worked.

Set up AWS

Get the Example app

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.

Have an editor set up

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.

A very very brief introduction to Clojure

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.

A very quick aside

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.

Let's go!

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.

A quick aside about other REPL strategies

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.

And because I like EMACS users so much

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.

Local testing of the prod build

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

Test via Serverless local

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!"
    }
}

Testing via Serverless offline

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!"}%

Deploying via Serverless

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.

A development workflow

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:

  1. npm run offline (you can see this task in package.json)
  2. make a change in your file - this can be whitespace
  3. once the dev artifact has built, we can use it to connect to figwheel. Instead of running the long node command, it's available as npm 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!

  1. package.json is where all your node deps live
  2. project.clj is where your CLJS build is configured
  3. serverless.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.

Part II: EFF Dice

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.

@the-frey
Copy link
Author

TinyURL to this set of instructions: https://tinyurl.com/ll-may-cljs-sl
TinyURL to the code repo: https://tinyurl.com/ll-may-code

@the-frey
Copy link
Author

@the-frey
Copy link
Author

@Dzimi2021
Copy link

Hey, the-frey!
I have little digression question.
You mention "the frey" in-joke in C&H? What is it about?

I found Watterson's dedication in The Authoritative Calvin and Hobbes: To Doctor Dave and. Fellow Moosers John, Brad, and The Frey
I can't figure out what that "The Frey" mean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment