这是三个教程系列中的第二篇,这些教程通过开发库存控制系统来说明Clojure和OpenWhisk。 在第1部分中 ,我解释了如何使用Clojure(一种基于Lisp的功能编程语言)来通过Node.js运行时和ClojureScript包编写OpenWhisk操作。 在第2部分中,我将向您展示如何使用OpenWhisk序列将动作组合为有用的块,这些块可以完成应用程序的工作。 第3部分将向您展示如何与外部数据库接口,以及如何使用日志记录在OpenWhisk应用程序中调试自己的Clojure。
在第2部分的结尾,您将拥有示例应用程序的大多数功能,即库存管理系统。 您将能够使用Clojure操作生成HTML以显示信息,并使用HTML表单中的信息来修改存储在应用程序中的信息。
构建应用程序所需的条件
本教程以本系列第一篇教程中的信息为基础:“ 使用Clojure编写OpenWhisk操作,第1部分:使用此Lisp方言为OpenWhisk编写简洁明了的代码 。” 此外,您将需要:
- OpenWhisk和JavaScript的基本知识(Clojure是可选的;本教程说明了需要时所需要的内容)
- 一个免费的Bluemix帐户( 在此处注册 )
从JSON到HTML
通常,OpenWhisk操作使用JSON进行通信。 但是,浏览器需要HTML。 因此,我们需要找到一种将JSON转换为HTML的方法,并使用Clojure以模块化的方式进行处理。
“在本教程中,您将从Clojure和OpenWhisk中的单个操作发展为实际与浏览器通信的序列。 ”
将项目数据放入表格
第一步是将物料数据放入表中。 为此,请在新目录中创建一个新动作。 使用在第1部分中创建的相同package.json和main.js文件。 这次唯一不同的文件是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>"
])
}
)
)
在上面列出的action.cljs中,名字像以前一样声明了名称空间。 但是ns
还有一个附加参数:( (:require clojure.string)
。 此参数导入clojure.string
库。
cljsMain
函数的编写方式与第1部分中的类似。 在let
函数中计算许多中间值,然后创建最终值:
(defn cljsMain [params] (
let [
cljParams (js->clj params)
data (get cljParams "data")
keys
函数采用哈希表并返回键列表。 在这种情况下,数据是数据库操作的输出,因此键是项目名称:
dataKeys (keys data)
下一行使用函数式编程中最重要的功能之一map
:
rowsAsList (map #(clojure.string/join ["<tr><td>" %
"</td><td>" (get data %) "</td></tr>"])
dataKeys)
map
函数获取一个函数和一个列表,并返回在列表中的每个项目上运行该函数的结果的列表。
要查看REPL网站上正在运行的map
功能,请运行(map #(* 2 %) '(1 2 3 4))
。 (有关REPL的更多信息,请参见第1部分 。)单引号(')表示这只是一个列表,而不是函数调用。
对于action.cljs,该函数为clojure.string/join
。 该函数接收字符串向量,并返回将所有字符串连接在一起的字符串。 在这种情况下,它是一个包含两列的表格行:项目名称和金额。
map
的结果是一个列表。 但是,您要生成一个值,即字符串。 为此,您需要将所有值结合在一起。 这为您提供了一个包含所有数据库行的字符串:
rowsAsString (clojure.string/join rowsAsList)
]
最后,添加表标签和标题行:
{"html" (clojure.string/join
["<table><tr><th>Item</th><th>Amt. in Stock</th></tr>"
rowsAsString
"</table>"
])
}
)
)
注意 :要将动作发送到OpenWhisk,使用类似于以下脚本的脚本很有用。 它执行所有步骤,包括删除该动作(如果已存在)。 这是出于调试目的; 在找到有效的方法之前,您通常需要尝试多种潜在的解决方案。
#! /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
按顺序放置动作
下一步是将您拥有的两个操作(模拟数据库和转换为HTML表)组合为一个序列。 为此,请执行以下步骤:
- 转到Bluemix中的OpenWhisk控制台。
- 点击制定侧栏上的,然后采取行动inventory_dbase。
- 单击链接到序列中 。
- 单击我的动作磁贴。
- 选择stock_json2html ,然后点击+添加到序列 。 这将创建两个动作序列。
- 然后单击→看起来不错 。
- 将序列命名为“ ShowItems”,然后单击“ 保存操作序列” 。
- 单击ShowItems ,然后使用以下参数运行此序列 :
{ "action": "getAll" }
- 确认您在响应中获得了带表HTML值。
创建完整HTML文件
所以现在您可以制作一张桌子了。 在实际应用中,人们期望的远不止于此。 例如,他们希望链接显示他们可以采取的其他措施。 要提供这些链接,您可以创建另一个操作(将其称为“ inventory_table2html”)。 和以前一样,唯一不同的文件是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])}
)
)
我可以在字符串常量中指定在表格之前和之后出现HTML代码。 但是,这将需要我转义HTML中非常常见的双引号(“)。相反,为表前后HTML创建两个文件分别称为header和footer更为容易。当将它们添加到action.zip文件时,它们将与action.cljs文件位于同一目录中,并且能够读取它们。
但是,如果尝试仅使用文件名__dirname +“ / header”(在JavaScript代码中,如下所述),则会出现错误。 评估Clojure文件时,系统实际上比该目录更深三个目录,如从该日志片段中可以看到的:
从系统调用(对操作系统内核的调用) open
返回此错误。 错误代码ENOENT
代表“错误:没有尝试”,因为该文件在目录中不存在。 错误代码后两行显示了文件名。
此action.cljs文件中有几项新内容:
- 以下两行读取页眉和页脚文件。 我决定不使用
js*
而是翻译JavaScript代码以将文件读取到Clojure。 对于库函数调用,我认为这更清楚。(def header (js* "require('fs').readFileSync(__dirname + '/../../../header')")) (def footer (js* "require('fs').readFileSync(__dirname + '/../../../footer')"))
- 我们从上一个操作中获得的表是标准HTML表。 但是,整个网页使用Bootstrap模板 ,该模板要求将类添加到table标记。 解决方案是
clojure.string/replace
函数,该函数可以将一个字符串替换为另一个字符串(它也可以使用正则表达式):bootstrapTable (clojure.string/replace htmlTable "<table>" "<table class=\"table table-striped\"> ")
- 出于调试目的,产生日志条目很有用。 写入标准输出的函数是
prn
。 但是要使用let
内部的任何功能,您需要将结果分配到某个地方。 这是使用它的代码:delme (prn "Parameter HTML:") delme (prn h tmlTable)
要查看消息,请在OpenWhisk控制台中单击“ 显示日志 ”。 然后,您应该会看到一个如下所示的页面:
将HTML服务到Internet
现在您可以生成HTML。 但是,它埋在JSON结构中。 您可以编写一个index.html来读取它,正如我在另一篇教程“ 使用Bluemix和Node.js构建面向用户的OpenWhisk应用程序 ”中所述。 但是也可以将序列直接提供给浏览器:
- 返回到OpenWhisk。
- 单击“ 开发” ,然后单击“ ShowItems”序列。
- 单击扩展,然后选择我的操作>库存表2html 。
- 单击+添加到序列 。
- 单击保存更改 。
- 点击左侧栏中的API 。
- 点击创建托管API + 。
- 使用以下参数创建一个API:
API名称 Clojure库存 API的基本路径 / clj_inventory CORS 选中(启用CORS) - 在API中使用以下参数创建操作:
路径 / showItems 动词 GET / clj_inventory 包含动作的包裹 默认 行动 ShowItems 响应内容类型 文字/ HTML - 点击保存并公开 。
- 单击左侧栏上的API Explorer ,单击API中的一个动作( getShowitems ),然后从
curl
命令复制URL。 - 将网址粘贴到浏览器中,然后跟随
?action=getAll
。
确认您获得了包含所有项目的表格。 - 将
action=getAll
替换为action=getAvailable
。
确认您得到的表格仅包含那些有库存的物品。
将浏览器输入序列化
在上一节中,我使用了URL查询参数将信息发送到OpenWhisk操作。 当您有一个简单的参数(例如action
时,此方法效果很好。 但是,当发送大量信息时,该方法看起来很难看。 最好使用POST
并发送信息而不向用户显示。
有两种方法可以实现此目的:您可以在浏览器中使用JavaScript(最好使用Angular之类的库)来创建要发布的JSON,或者可以创建普通的HTTP表单并在OpenWhisk中进行转换。 为了在OpenWhisk上学习Clojure,第二种方法显然更好。
实际修改数据的应用程序由两个序列组成。 第一个创建具有所有可用物料(和库存量)的表单,第二个处理表单结果。 本教程说明了销售点应用程序。 其他两个应用程序(重新排序和更正)几乎相同。
创建表格
要创建表单,请创建一个新的动作,称为inventory_purchaseForm
(请参见源代码 )。 此操作与inventory_json2HTML
非常相似,不同之处在于该表以表格形式嵌入,并且有一个单独的列用于表示购买金额。
有一个问题是,形式需要使用双引号(“)的。不幸的是,因为方式的clojure.string/replace
操作,具有字符串中这个角色,无论是逃脱与否,混淆了inventory_table2HTML
行动。有也许是一个更优雅的解决方案,但是为了节省时间,我只使用了三个美元符号($$$),然后在将响应发送出ventory_table2HTML之前替换它们,在此行:
{"html" (clojure.string/join [header (clojure.string/replace bootstrapTable "$$$" "\"") footer])}
接下来,创建一个名为PurchaseForm
的序列。 这个序列是类似于ShowItems
,除了中间的动作是inventory_purchaseForm
。 然后将其添加到具有路径/purchase
,动词GET
和答复内容类型text/html
的API中。
处理提交的表格
为了查看提交的内容及其可用性,我创建了一个echo操作并将其配置为对/purchase
的POST
动词的答复。 这是响应:
{
"T-shirt L": "1",
"__ow_method": "post",
"__ow_headers": {
...
},
"__ow_path": "",
"T-shirt S": "",
"T-shirt XL": "3",
"action": "getAvailable"
}
现在,您可以从购买表单中获取参数,还可以获取action
(因为我们没有更改包含查询的URL)和三个不需要的内部参数: __ow_method
, __ow_headers
和__ow_path
。 此外,如果未填写任何行,则将有一个空字符串。
把这种形式引入JSON的inventory_dbase
动作了解到,创建一个名为新动作inventory_processPurchase
。 您可以在此处查看源代码 。 大多数代码与我们之前所做的相似,但是有一些区别。
- 要删除不属于数据的键,我使用
filter
。 但是,条件要复杂一些。 它在五个不同条件下使用or
函数。realKeys (filter #(not (or (= % "action") (= % "__ow_method") (= % "__ow_headers") (= % "__ow_path") (= (get usefulParams %) "") )))
在Clojure(和其他LISP变体)中,大多数计算函数可以采用多个值并进行处理。 要在REPL网站上实际运行,请运行(* 2 3 4 5 6 7)
:
如您所见,所有数字都相乘达到5040。 - 另一个区别是使用
reduce
创建数据哈希表。 此函数的功能是其第一个参数,然后是一个集合(列表,向量等)。 通过在前两个值上运行该函数,然后在结果和第三个值上运行该函数,将集合变成单个值。 用数学术语来说,它看起来像f(f(f(v1,v2),v3),v4)
。 要在REPL网站上看到此效果,请运行(reduce #(+ %1 %2) '(1 2 3 4 5 6 7))
:
将这些数字加在一起,得到28。
要创建哈希表,请使用assoc
函数,该函数接受哈希表作为其第一个参数。 这意味着列表需要从一个空的哈希表开始。 为此,您可以使用list*
函数,该函数接受参数并将其放在作为最后一个参数的列表的开头。
data (reduce #(assoc %1 %2 (get usefulParams %2)) (list* {} realKeys))
要在REPL网站上看到此效果,请运行(assoc {} :a :b)
。 您将获得一个哈希表,其中包含一个项,其键为:a且值为:b。
然后运行(list* 1 2 3 4 5 6 '(7 8))
并看到结果是数字1-8的列表:
接下来,创建序列。 以下是操作以及我们执行操作的原因:
行动 | 目的 |
---|---|
inventory_processPurchase | 为数据库创建数据和操作 |
inventory_dbase | 实际处理数据,返回物料清单 |
inventory_json2html | 将项目列表转换为HTML表 |
inventory_table2html | 将HTML表格转换成完整的网页 |
最后,为路径/purchase
,动词POST
和答复内容类型text/html
创建API操作以调用序列。 这使您可以处理结果。
其他两个页面几乎相同,它们允许用户添加到现有库存或设置数据库中的数量。 区别在于:
- 用于启动它们的查询(
action=getAll
而不是action=getAvailable)
- 处理表单后,
action
参数的值 - API的路径
另外,在处理重新排序时,有必要将这些值转换为字符串。 否则,加号函数会“看到”两个字符串,而这实际上是JavaScript加号,因此会将它们串联起来。 最好的方法是使用cljs.reader/parse-int
函数执行数据库操作。
您可以阅读存储库中的代码 ,并亲自查看是否有兴趣。 注意,不需要新的序列来显示表单,并且处理它的序列仅使用一个不同的动作来支持对数据库具有不同的命令。 有多种方法可以使用相同的操作,但是会增加不必要的复杂性。
结论
通过遵循本教程,您已经从Clojure和OpenWhisk中的单个操作升级为实际与浏览器通信的序列。 该应用程序现已完成,但完全没有用。 信息存储在“数据库”中,该数据库是一个变量,每次OpenWhisk决定一段时间未使用数据库操作时,该变量都会被删除,并且它占用的内存将更好地用于其他目的。 在本系列的最后一篇教程中,我将向您展示如何使用更永久的存储选项,并向您介绍其他一些零碎的东西。
翻译自: https://www.ibm.com/developerworks/cloud/library/cl-clojure-openwhisk2/index.html