Building on our own shoulders
Previously we built a simple AWS Lambda. Today we are going mess around with the AWS Node SDK and DynamoDB. The main goal of this post is to show more serious inter-op between Clojurescript and Javascript.
AWS Services
AWS offers a lot of services for lot of different business needs and getting started can be really overwhelming. Before starting this blog anytime I needed to use AWS at work I was nervous. Coworkers through around nonsense acronyms left and right. "Check the Codebuild step in the Codepipeline to see if there is a problem with S3 or IAM" makes no sense if you haven't used AWS before. Luckily climbing over that hurdle really doesn't take more than a few hours of googling and poking around and hopefully this post will help anyone trying to get involved in the AWS ecosystem. Just to steer our exploration we will be using DynamoDB but if something else seems cool I highly encourage you to check out the intro project and docs!
πThe actual meatπ
For this posts example code I have ported the Node SDK's Create Table, CRUD Operations, and Delete Table.
Link to repo
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
Tools
We will obviously need a few tools:
- AWS CLI We need the CLI because it gives us auth into AWS, more info on setting up the CLI here
- NPM/Yarn (for package management and to use the Shadow-CLJS CLI)
- Shadow-CLJS (Our CLJS Build tool of choice mainly because it makes consuming npm deps super easy)
CLJS VS JS
Note: I have basically ported the JS to it's literal, but not idiomatic, CLJS equivalent. I would use this code to help get a better understanding of how the two languages relate and how to call one from the other. I would NOT code like this when using CLJS as the primary language.
In this post I will just break down on example, createTable
, because the only difference between any of the examples is the params
var and the dynamodb
/docClient
fn call.
JS for reference
var AWS = require("aws-sdk");
AWS.config.update({
region: "us-west-2",
endpoint: "http://localhost:8000"
});
var dynamodb = new AWS.DynamoDB();
var params = {
TableName : "Movies",
KeySchema: [
{ AttributeName: "year", KeyType: "HASH"}, //Partition key
{ AttributeName: "title", KeyType: "RANGE" } //Sort key
],
AttributeDefinitions: [
{ AttributeName: "year", AttributeType: "N" },
{ AttributeName: "title", AttributeType: "S" }
],
ProvisionedThroughput: {
ReadCapacityUnits: 10,
WriteCapacityUnits: 10
}
};
dynamodb.createTable(params, function(err, data) {
if (err) {
console.error("Unable to create table. Error JSON:", JSON.stringify(err, null, 2));
} else {
console.log("Created table. Table description JSON:", JSON.stringify(data, null, 2));
}
});
CLJS
(ns server.create-table
(:require ["aws-sdk" :as AWS])) ;; Our var AWS = require statement
(AWS/config.update #js{:region "us-east-1"})
;; first example of js interop, the translates to the AWS.config.update above, the AWS/ bit is used for accessing the CLJS "Namespace" for the AWS SDK
(def dynamo (AWS/DynamoDB. #js{:apiVersion "2012-08-10"}))
;; Second example of interop and shows constructor invocation. It can also be written as (def dynamo (new AWS/DynamoDB #js{:apiVersion "2012-08-10"})) because the . is shorthand for new
;;Additionally def is the CLJS equivalentish of var but isn't used as often as in Clojure/script
(def params
(clj->js {:TableName "Movies",
:KeySchema
[{:AttributeName "year", :KeyType "HASH"}
{:AttributeName "title", :KeyType "RANGE"}],
:AttributeDefinitions
[{:AttributeName "year", :AttributeType "N"}
{:AttributeName "title",
:AttributeType "S"}],
:ProvisionedThroughput
{:ReadCapacityUnits 10,
:WriteCapacityUnits 10}}))
(defn invoke []
(.createTable dynamo params
#(if %1
(js/console.error "Unable to create table. Error JSON:"
(js/JSON.stringify %1 nil 2))
(js/console.log "Created table. Table description JSON:"
(js/JSON.stringify %2 nil 2)))))
;; This is the one difference from the AWS example code above, the actual call to AWS is wrapped in a function so it can be call from node.js proper.
This pattern follows through all the the rest of the examples.
Node.js REPL calls
If you want to be able to test the code out for yourself you can call into from a node.js repl just compile and require
npx shadow-cljs compile app
cd target
node
then once in the repl
var m = require('./main.js');
m.aws.createTable() //Other options include getItem, createItem, readItem, deleteTable, deleteItem, updateItem, updateItemConditionally, atomicInc
//Inside of the Shadow-CLJS config is a mapping between the CLJS fn's to the m.aws object
πππ
And there we have it! If you have any questions or feedback reach out on Twitter, or @royalaid on the Clojurians Slack or Zulip
Top comments (1)
I'd love to see more Clojure content here! Keep it coming!