clojure_改善您的OpenWhisk Clojure应用程序

在前两个教程中,您学习了如何使用Clojure(一种基于Lisp的功能编程语言)编写基本的OpenWhisk应用程序,从而为OpenWhisk应用程序创建操作。 在本教程中,我将通过向您展示如何改进任何此类应用程序来结束本系列。 首先,您将学习如何支持包含双引号的参数。 然后,我将向您展示如何使用永久数据库(Cloudant)而不是变量来存储信息。

构建应用程序所需的条件

本教程以“使用Clojure编写OpenWhisk动作”系列的前两个教程中的信息为基础, 第1部分,使用此Lisp方言为OpenWhisk编写简洁明了的代码第2部分,将Clojure OpenWhisk动作连接到有用的序列中 ,因此我建议先阅读这些内容。 此外,您将需要:

  • OpenWhisk和JavaScript的基本知识(Clojure是可选的;本教程说明了需要时所需要的内容)
  • 一个免费的Bluemix帐户( 在此处注册

运行应用程序 获取代码

函数式编程鼓励您隔离副作用,并将其与业务逻辑分开。 这导致应用程序具有更高的模块化,更易于测试和易于调试。

包含引号的参数

在第1部分中,我介绍了main.js JavaScript:

// Get a Clojure environment
var cljs = require('clojurescript-nodejs');


// Evaluate the action code
cljs.evalfile(__dirname + "/action.cljs");


// The main function, the one called when the action is invoked
var main = function(params) {

  var clojure = "(ns action.core)\n ";

  var paramsString = JSON.stringify(params);
  paramsString = paramsString.replace(/"/g, '\\"');

  clojure += '(clj->js (cljsMain (js* "' + paramsString + '")))';

  var retVal = cljs.eval(clojure);
  return retVal;
};

exports.main = main;

该脚本使用非常简单的解决方案将参数传递给Clojure:

var paramsString = JSON.stringify(params);
paramsString = paramsString.replace(/"/g, '\\"'); 
…
'(js* "' + paramsString + '")))';

此解决方案可以产生一个字符串,例如{\"num\": 5, \"str\": \"whatever\"} 。 双反斜杠( \\ )转换为单个反斜杠(即转义字符)。 生成的Clojure代码是(js* " {\"num\": 5, \"str\": \"whatever\"}") 。 由于js*评估它作为JavaScript获得的字符串参数,因此使我们回到了原始参数{"num": 5, "str": "whatever"} 。 问题是,如果字符串参数之一已经包含双引号( " ),则它将与引号完全一样地对待,从而导致诸如{"num": 5, "str": "what"ever"}这样的表达式{"num": 5, "str": "what"ever"}和语法错误。 从理论上讲,您可以通过使用js/<var name>语法来访问带有参数的变量来解决此问题,但是由于某些原因,这在OpenWhisk中不起作用。

在第3部分中,我介绍了fixHash函数,该函数遍历参数(包括任何嵌套的数据结构)并查找字符串。 在字符串中,它将所有双引号替换为\\x22 。 第一个反斜杠转义第二个反斜杠,因此实际值为\x22 。 该值最终会转换为ASCII字符0x22(十进制34,即双引号),但是这种情况会在以后发生,因此replace方法不会修改这些字符。

// Fix a hash table so it won't have internal double quotes
var fixHash = function(hash) {
	if (typeof hash === "object") {
		if (hash === null) return null;
		if (hash instanceof Array) {
			for (var i=0; i<hash.length; i++)
				hash[i] = fixHash(hash[i]);
			return hash;
		}
		
		var keys = Object.keys(hash) 
		for (var i=0; i<keys.length; i++)
			hash[keys[i]] = fixHash(hash[keys[i]]);
		return hash;
	}

	if (typeof hash === "string") {
		return hash.replace(/"/g, '\\x22');
	}

	return hash;
		
};

保存到数据库

在几分钟不使用时重置为初始值的库存管理系统不是很有用。 因此,下一步是设置一个对象存储实例以存储数据库值( inventory_dbase操作中的dbase变量):

  1. 在Bluemix控制台中,单击Menu图标,然后转到Services> Data&Analytics
  2. 单击创建数据和分析服务,然后选择Cloudant NoSQL DB
  3. 将服务命名为“ OpenWhisk-Inventory-Storage”,然后单击“ 创建”
  4. 创建服务后,将其打开,然后单击服务凭证>新建凭证
  5. 将新凭据命名为“库存应用”,然后点击添加
  6. 单击查看凭据,然后将凭据复制到文本文件中。
  7. 选择管理>启动
  8. 单击数据库图标,然后单击创建数据库
  9. 将数据库命名为“ openwhisk_inventory”。
  10. 单击“ 所有文档”行中的加号图标,然后选择“ 新建文档”
  11. dbase.json的内容复制到文本区域,然后单击创建文档

有两种方法可以将Cloudant数据库与应用程序结合。 您可以Cloudant行动要么添加到序列,或修改inventory_dbase行动。 我选择了第二个解决方案,因为它使我可以更改单个操作(因为所有数据库工作都集中在此)。

  1. Cloudantnpm库添加到package.json中的依赖项,并更新ventory_dbase操作的action.cljs文件:
    (ns action.core)
    
    (def cloudant-fun (js/require "cloudant"))
    
    (def cloudant (cloudant-fun "url goes here"))
    
    (def mydb (.use (aget cloudant "db") "openwhisk_inventory"))
    
    
    
    ; Process an action with its parameters and the existing database
    ; return the result of the action
    (defn processDB [action dbase data]
      (case action
        "getAll" {"data" dbase}
    
        "getAvailable" {"data" (into {} (filter #(> (nth % 1) 0) dbase))}
    
        "processCorrection" (do
          (def dbaseNew (into dbase data))
          {"data" dbaseNew}
        )
    
        "processPurchase" (do
          (def dbaseNew (merge-with #(- %1 %2) dbase data))
          {"data" dbaseNew}
        )
    
        "processReorder" (do
          (def dbaseNew (merge-with #(+ (- %1 0) (- %2 0)) dbase data))
          {"data" dbaseNew}
        )
    
        {"error" "Unknown action"}
      )   ;  end of case
    )   ; end of processDB
    
    
    
    (defn cljsMain [params] (
        let [
          cljParams (js->clj params)
          action (get cljParams "action")
          data (get cljParams "data")
          updateNeeded (or (= action "processReorder")
                           (= action "processPurchase")
                           (= action "processCorrection"))
        ]
    
        ; Because promise-resolve is here, it can reference
        ; action
        (defn promise-resolve [resolve param] (let
          [
            dbaseJS (aget param "dbase")
            dbaseOld (js->clj dbaseJS)
            result (processDB action dbaseOld data)
            rev (aget param "_rev")
          ]
            (if updateNeeded
              (.insert mydb (clj->js {"dbase" (get result "data"),
                                      "_id" "dbase",
                                      "_rev" rev})
                #(do (prn result) (prn (get result "data")) (resolve (clj->js result)))
              )
              (resolve (clj->js result))
            )
          )   ; end of let
        )   ; end of defn promise-resolve
    
    
        (defn promise-func [resolve reject]
          (.get mydb "dbase" #(promise-resolve resolve %2))
        )
    
        (js/Promise. promise-func)
      )   ; end of let
    )    ; end of cljsMain

让我们看一下action.cljs的一些更有趣的部分。

您需要获取数据库。 在JavaScript中,您可以这样编码:

var cloudant_fun = require("cloudant ");
var cloudant = cloudant_fun(<<<URL>>>);
var mydb = cloudant.db.use("openwhisk_inventory ");

相同代码的Clojure版本相似,但有一些区别。 首先, require是一个JavaScript函数。 要访问它,您需要使用js名称空间对其进行限定(上述步骤12中清单3的行):

(def cloudant-fun (js/require "cloudant"))

下一行(第5行)非常标准。 URL是数据库凭据中的URL参数:

(def cloudant (cloudant-fun <<URL GOES HERE>>))

要从JavaScript对象获取成员,请使用aget 。 要使用对象的方法,可以使用(.<method> <object> <other parameters>) 。 参见第7行:

(def mydb (.use (aget cloudant "db") "openwhisk_inventory"))

从数据库读取和写入都是异步操作。 这意味着您不能简单地运行它们并将结果返回给调用方(OpenWhisk系统)。 相反,您需要返回Promise对象 。 该构造函数接受一个参数-调用该函数以启动需要结果的进程。 从Clojure调用JavaScript对象的构造函数的语法是(js/<object name>. <parameters>) 。 参见第75行:

(js/Promise. promise-func)

提供给Promise对象的构造promise-func的函数是promise-func 。 它接收两个参数。 一种是在成功的情况下调用的函数(一个参数,即操作的结果)。 另一个是发生故障时要调用的函数(也是一个参数,错误对象)。 在这种情况下,该函数获取dbase文档,然后使用成功函数和该文档调用promise-resolve 。 匿名函数( #(promise-resolve resolve %2) )的第一个参数是错误(如果有)。 因为这是一个演示程序,所以为了简单起见,我们忽略了错误。 参见第71-73行:

(defn promise-func [resolve reject]
      (.get mydb "dbase" #(promise-resolve resolve %2))
    )

promise-funcpromise-resolve都在cljsMain中定义。 原因是promise-resolve需要action参数的值。 通过在cljsMain中定义这些函数,您可以仅使用该局部变量,而不必将其拖向整个调用链。 参见第52-55行:

(defn promise-resolve [resolve param] (let
      [
        dbaseJS (aget param "dbase")
        dbaseOld (js->clj dbaseJS)

获取或修改数据的函数是processDB 。 此函数封装了第1部分中介绍的功能。请参见第56行:

result (processDB action dbaseOld data)

由于Cloudant使用算法来确保状态一致性,因此修订( _rev )是更新数据库所必需的。 阅读文档时,会得到当前的修订版( _rev )。 编写更新的版本时,必须向Cloudant提供要更新的修订。 同时,如果另一个进程更新了文档,则版本将不匹配,并且更新将失败。 参见第57行:

rev (aget param "_rev")

如果数据已修改,请更新Cloudant。 参见第59行:

(if updateNeeded

向数据库提供新数据,文档名称和要更新的修订版。 参见第60-62行:

(.insert mydb (clj->js {"dbase" (get result "data"),
                                  "_id" "dbase",
                                  "_rev" rev})

更新完成后(我们仅假设更新成功;这是一个教育示例,而不是生产代码),请运行以下功能。 注意添加调试打印输出的机制。 使用do可以计算多个表达式,可以调用任意数量的prn函数来打印所需的信息,然后将实际需要的表达式放在最后。

在这种情况下,您调用通过Promise对象获得的resolve函数。 由于此函数在JavaScript中,因此需要接收一个JavaScript对象,而不是Clojure对象,因此请使用clj->js 。 参见第63-64行:

#(do (prn result) (prn (get result "data")) (resolve (clj->js result)))
          )

如果不需要更新Cloudant,只需运行resolve功能。 参见第65-66行:

(resolve (clj->js result))
        )

编译Clojure代码

当前,我们将Clojure代码发送到OpenWhisk,并在Node.js应用程序重新启动时在此处进行编译。 另一种选择是一次将Clojure本地编译为JavaScript,然后发送已编译的版本。 如果您有兴趣,可以在这里查看它的工作方式。 但是,此方法不能显着提高性能。

从下面的屏幕截图中可以看到,即使使用已编译的ClojureScript代码,该操作的第一次调用仍比后续操作花费更多的时间。

该屏幕截图显示了第一次调用动作

速度慢的原因是实际编译不是很耗费资源。 Clojure环境的创建是使用Clojure的资源密集型部分,即使Clojure代码本身已编译为JavaScript,这也是必需的。 编译器生成JavaScript使用一些繁重的库。

结论

到此为止,Clojure中的OpenWhisk操作系列结束了。 我希望我已经向您展示了将函数式编程用于函数即服务(FaaS)的一些优点。 归根结底,几乎每个应用程序都需要在某个时候使用副作用,但是功能编程鼓励您隔离这些副作用并将它们与业务逻辑分开。 这导致应用程序具有更高的模块化,更易于测试和易于调试。 通过将应用程序逻辑分为动作和序列,可以很容易地使应用程序的大规模结构更清晰,并编写单元测试以作为REST调用运行。


翻译自: https://www.ibm.com/developerworks/cloud/library/cl-clojure-openwhisk3/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值