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

royalaid / Shadow-Node-AWS 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

to config compilation compile ClojureScript

run node target/main.js to start app and connect reload server License MIT

View on GitHub



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 on the Clojurians Slack or Zulip