Use Clojure to write OpenWhisk actions, Part 2
Connect your Clojure OpenWhisk actions into useful sequences
Learn how by developing an inventory control system
Content series:
This content is part # of # in the series: Use Clojure to write OpenWhisk actions, Part 2
This content is part of the series:Use Clojure to write OpenWhisk actions, Part 2
Stay tuned for additional content in this series.
This is the second in a series of three tutorials that illustrate Clojure and OpenWhisk through the development of an inventory control system. In Part 1, I explained how to use Clojure (a functional programming language that's based on Lisp) to write OpenWhisk actions using the Node.js runtime and the ClojureScript package. Here in Part 2, I show you how to use OpenWhisk sequences to combine actions into useful chunks that do the work of an application. Part 3 will show you how to interface with an external database, and how to use logging to debug your own Clojure in OpenWhisk applications.
By the end of Part 2, you will have most of the functionality of the sample application, the inventory management system. You will be able to use Clojure actions to produce HTML to display information and to use information from HTML forms to modify the information stored in the application.
What you'll need to build your application
This tutorial builds upon information from the first tutorial in this series: "Use Clojure to write OpenWhisk actions, Part 1: Write clear, concise code for OpenWhisk using this Lisp dialect." In addition, you will need:
- A basic knowledge of OpenWhisk and JavaScript (Clojure is optional; this tutorial explains what you need when you need it)
- A free Bluemix account (Sign up here)
From JSON to HTML
Normally, OpenWhisk actions communicate using JSON. However, browsers expect HTML. So we need to find a way to turn JSON into HTML, and do it in a modular fashion using Clojure.
“In this tutorial, you progress from a single action in Clojure and OpenWhisk to sequences that actually communicate with the browser.”
Putting item data into a table
The first step is to put the item data into a table. To do that, create a new action in a new directory. Use the same package.json and main.js files that were created in Part 1. The only file that is different this time is action.cljs:
(ns action.core (:require clojure.string)) (defn cljsMain [params] ( let [ cljParams (js->clj params) data (get cljParams "data") dataKeys (keys data) rowsAsList (map #(clojure.string/join ["<tr><td>" % "</td><td>" (get data %) "</td></tr>"]) dataKeys) rowsAsString (clojure.string/join rowsAsList) ] {"html" (clojure.string/join ["<table><tr><th>Item</th><th>Amt. in Stock</th></tr>" rowsAsString "</table>" ]) } ) )
In the action.cljs listing above, the first name declares the name space,
as before. But ns
has an additional parameter:
(:require clojure.string)
. This parameter imports the
clojure.string
library.
The cljsMain
function is written in a similar style to the one
in Part 1. A number of intermediate values are calculated in a
let
function and then the final value is created:
(defn cljsMain [params] ( let [ cljParams (js->clj params) data (get cljParams "data")
The keys
function takes a hash table and returns a list of the
keys. In this case, the data is the output of the database action, so the
keys are item names:
dataKeys (keys data)
The next line uses one of the most important functions in functional
programming, map
:
rowsAsList (map #(clojure.string/join ["<tr><td>" % "</td><td>" (get data %) "</td></tr>"]) dataKeys)
The map
function takes a function and a list, and returns a
list of the results of running the function on each item in the list.
To see the map
function in action on the REPL website, run
(map #(* 2 %) '(1 2 3 4))
. (For more information about REPL,
see Part 1.) The single quote (') signifies that this is just a list,
not a function call.

The function in the case of action.cljs is
clojure.string/join
. This function receives a vector of
strings, and returns a string that joins all of them. In this case, it is
a table row with two columns: the item's name and the amount.
The result of map
is a list. However, you want to produce a
single value, a string. To do that, you join together all the values. This
gives you a string with all the database rows:
rowsAsString (clojure.string/join rowsAsList) ]
Finally, add the table tags and the header row:
{"html" (clojure.string/join ["<table><tr><th>Item</th><th>Amt. in Stock</th></tr>" rowsAsString "</table>" ]) } ) )
Note: To send actions to OpenWhisk, it is useful to have a script similar to the one below. It performs all the steps, including the deletion of the action if it already exists. This is for debugging purposes; you often need to try multiple potential solutions before you find what works.
#! /bin/sh # Send the action to OpenWhisk npm install zip -r action.zip package.json main.js action.cljs node_modules wsk action delete inventory_json2html wsk action create inventory_json2html action.zip --kind nodejs:6
Putting actions in a sequence
The next step is to combine the two actions you have—the mock database and the translation into an HTML table—into a sequence. To do this, perform these steps:
- Go to the OpenWhisk console in Bluemix.
- Click Develop on the sidebar and then the action inventory_dbase.
- Click Link into a Sequence.
- Click the MY ACTIONS tile.
- Select inventory_json2html and click + Add to Sequence. This will create a two-action sequence.
- Then click → This Looks Good.
- Name the sequence "ShowItems" and then click Save Action Sequence.
- Click ShowItems and then Run this
Sequence using the following parameter:
{ "action": "getAll" }
- Confirm that you get an HTML value with a table in the response.
Create a full HTML file
So now you can produce a table. In a real application, people expect a lot more than that. For example, they expect links to show them other actions they can take. To provide those links, you create another action (call it "inventory_table2html"). As before, the only file that is different is action.cljs.
(ns action.core (:require clojure.string)) (def header (js* "require('fs').readFileSync(__dirname + '/../../../header')")) (def footer (js* "require('fs').readFileSync(__dirname + '/../../../footer')")) (defn cljsMain [params] ( let [ cljParams (js->clj params) htmlTable (get cljParams "html") bootstrapTable (clojure.string/replace htmlTable "<table>" "<table class=\"table table-striped\"> ") delme (prn "Parameter HTML:") delme (prn htmlTable) ] {"html" (clojure.string/join [header (clojure.string/replace bootstrapTable "$$$" "\"") footer])} ) )
I could have specified the HTML code to come before and after the table in string constants. However, that would require me to escape the double quote ("), which is very common in HTML. Instead, it is easier to create two files, called header and footer, for the HTML that comes before and after the table. As long as they are added to the action.zip file, they will be in the same directory as the action.cljs file and it will be able to read them.
However, if you try to use just the file name __dirname + "/header" (in JavaScript code, as explained below), you get an error. When the Clojure file is evaluated, the system is actually three directories deeper, as you can see from this log snippet:

This error is returned from the system call (call to the operating system
kernel) open
. The error code, ENOENT
, stands for
"Error: NO ENTry" because the file does not exist in the directory. The
file name two lines after the error code shows us the directory.
There are several items that are new in this action.cljs file:
- The following two lines read the header and footer files. Instead of
translating the JavaScript code to read a file to Clojure, I decided
to simply use
js*
. For a library function call, I think this is clearer.(def header (js* "require('fs').readFileSync(__dirname + '/../../../header')")) (def footer (js* "require('fs').readFileSync(__dirname + '/../../../footer')"))
- The table we get from the previous action is a standard HTML one.
However, the complete web page uses the Bootstrap
template, which requires that classes be added to the table
tag. The solution is the
clojure.string/replace
function, which can replace one string with another (it could also use regular expressions):bootstrapTable (clojure.string/replace htmlTable "<table>" "<table class=\"table table-striped\"> ")
- For debugging purposes, it is useful to produce log entries. The
function that writes to standard output is
prn
. But to use any function inside thelet
, you need to assign the result somewhere. Here is the code that uses it:delme (prn "Parameter HTML:") delme (prn h tmlTable)
To see the messages, click Show Logs in the OpenWhisk console. You should then see a page that looks like this:
Serve the HTML to the Internet
Now you can produce HTML. However, it is buried inside a JSON structure. You could write an index.html that reads it, as I explain in another tutorial, "Build a user-facing OpenWhisk application with Bluemix and Node.js." But it is also possible to have the sequence served directly to the browser:
- Return to OpenWhisk.
- Click Develop and then the ShowItems sequence.
- Click Extend and select MY ACTIONS > inventory_table2html.
- Click + Add to Sequence.
- Click Save Your Changes.
- Click APIs on the left sidebar.
- Click Create Managed API +.
- Create an API with these parameters:
API name Clojure Inventory Base path for API /clj_inventory CORS Selected (enable CORS) - Create an operation within the API with these parameters:
Path /showItems Verb GET/clj_inventory Package containing action Default Action ShowItems Response content type text/html - Click Save & expose.
- Click API Explorer on the left sidebar, click the one
action in the API (getShowitems), and copy the URL
from the
curl
command. - Paste the URL into a browser, and follow it with
?action=getAll
.
Confirm that you get a table with all the items. - Replace
action=getAll
withaction=getAvailable
.
Confirm that you get a table with only those items that are in stock.
Getting browser input into a sequence
In the previous section, I used a URL query parameter to send information
to an OpenWhisk action. This works well when you have one simple
parameter, such as action
. However, when sending a lot of
information that method looks ugly. It is better to use POST
and send the information without showing it to the user.
There are two ways to accomplish this: You could use JavaScript in the browser (preferably using a library such as Angular) to create JSON to post, or you could create a normal HTTP form and do the transformation in OpenWhisk. For the purpose of learning Clojure on OpenWhisk, the second method is clearly better.
The applications that actually modify the data are composed of two sequences. The first creates a form with all the available items (and the amount in stock), and the second processes the form results. This tutorial explains the point-of-sale application. The other two applications (reorder and correction) are nearly identical.
Create the form
To create the form, create a new action called
inventory_purchaseForm
(see the source code). This action is very similar to
inventory_json2HTML
, except that the table is embedded in a
form, and there is a separate column for the amount purchased.
The one problem is that a form requires the use of double quotes (").
Unfortunately, because of the way clojure.string/replace
operates, having this character in the string, whether it is escaped or
not, confuses the inventory_table2HTML
action. There may be a
more elegant solution, but in the interest of time, I just use three
dollar signs ($$$) instead, and then replace them just before sending the
response out of inventory_table2HTML, in this line:
{"html" (clojure.string/join [header (clojure.string/replace bootstrapTable "$$$" "\"") footer])}
Next, create a sequence called PurchaseForm
. This sequence is
similar to ShowItems
, except that the middle action is
inventory_purchaseForm
. Then add it to the API with the path
/purchase
, the verb GET
, and the reply content
type text/html
.
Process the submitted form
To see what exactly gets submitted, and how it is available, I created an
echo action and configured it as the reply to a POST
verb for /purchase
. This is the response:
{ "T-shirt L": "1", "__ow_method": "post", "__ow_headers": { ... }, "__ow_path": "", "T-shirt S": "", "T-shirt XL": "3", "action": "getAvailable" }
Now you get the parameters from the purchase form, but also
action
(because we didn't change the URL, which includes the
query) and three internal parameters you don't need:
__ow_method
, __ow_headers
, and
__ow_path
. Also, if any rows are unfilled, you will have an
empty string.
To turn this form into JSON that the inventory_dbase
action
understands, create a new action called
inventory_processPurchase
. You can see the source code here. Most of the code is similar to what we have
done before, but there are a couple of differences.
- To remove the keys that do not belong in the data, I use
filter
. However, the condition is a bit more complicated. It uses theor
function with five different conditions.realKeys (filter #(not (or (= % "action") (= % "__ow_method") (= % "__ow_headers") (= % "__ow_path") (= (get usefulParams %) "") )))
In Clojure (and other LISP variants), most calculation functions can take multiple values and handle them. To see this in action on the REPL website, run(* 2 3 4 5 6 7)
:
As you can see, all the numbers are multiplied together to reach 5040. - Another difference is the use of
reduce
to create the data hash table. This function has a function as its first parameter and then a collection (list, vector, and so on). It turns that collection into a single value by running the function on the first two values, then on the result and the third value, and so on. In mathematical terms, it looks likef(f(f(v1,v2),v3),v4)
. To see this in action on the REPL website, run(reduce #(+ %1 %2) '(1 2 3 4 5 6 7))
:
The numbers are added together and you get 28.
To create the hash table, you use the assoc
function which
accepts a hash table as its first parameter. This means that the list
needs to start with an empty hash table. To get that, you use the
list*
function that takes arguments and puts them in the
beginning of the list it receives as its last argument.
data (reduce #(assoc %1 %2 (get usefulParams %2)) (list* {} realKeys))
To see this in action on the REPL
website, run (assoc {} :a :b)
. You get a hash table
with one item whose key is :a and whose value is :b.
Then run (list* 1 2 3 4 5 6 '(7 8))
and see that the result is
a list of numbers 1-8:

Next, create the sequence. Here are the actions and the reasons we have them:
Action | Purpose |
---|---|
inventory_processPurchase | Create the data and action for the database |
inventory_dbase | Actually process the data, returning the item list |
inventory_json2html | Convert the item list into an HTML table |
inventory_table2html | Convert the HTML table into a full web page |
Finally, create an API operation for the path /purchase
, the
verb POST
, and the reply content type text/html
to call the sequence. This allows you to process the result.
The other two pages, which allow the user to either add to the existing inventory or set the amount in the database, are nearly identical. The differences are in:
- The query used to start them (
action=getAll
instead ofaction=getAvailable)
- The value of the
action
parameter after processing the form - The path for the API
Also, when processing a reorder, it is necessary to turn the values into
strings. Otherwise, the plus function "sees" two strings, and it is really
the JavaScript plus, so it concatenates them. The best place to do this is
the database action, using the cljs.reader/parse-int
function.
You can read the code in the repository and see for yourself if you are interested. Note that there is no need for a new sequence to show the form, and the sequence to process it uses only one different action to support having different commands to the database. There are ways to use the same action, but they'd add needless complication.
Conclusion
By following this tutorial, you have progressed from a single action in Clojure and OpenWhisk to sequences that actually communicate with the browser. The application is now complete, except for being completely useless. The information is stored in a "database" which is a variable that gets deleted every time OpenWhisk decides that the database action has not been used in a while and the memory it occupies would be better used for another purpose. In the final tutorial in the series, I show you how to use a more permanent storage option, and I introduce you to some other odds and ends.