Pragmatism in the real world

Calling an OpenWhisk action in Swift

As OpenWhisk is a Functions as a Service system, it makes sense to create actions that do one thing and call other actions when they want other work done. As an example, in DrinksChooser, the choose action calls the incrementDrinkCount action which increments the count of the recommended drink in Redis. This way, choose doesn’t have to know anything about Redis as that’s not its job.

In OpenWhisk’s Swift environment, there’s the Whisk.invoke() method to do this. This is how we do it.

let env = ProcessInfo.processInfo.environment
let namespace : String = env["__OW_NAMESPACE"] ?? ""
let incrementAction = "/" + namespace + "/DC/incrementDrinkCount"

let invokeResult = Whisk.invoke(actionNamed: incrementAction,
        withParameters: ["name": drink])
let result = JSON(invokeResult)

The action’s name

To invoke an action we need it’s fully qualified name. This is your OpenWhisk namespace concatenated with the action’s name, including it’s package name if it has one.

Let’s start with the namespace:

let env = ProcessInfo.processInfo.environment
let namespace : String = env["__OW_NAMESPACE"] ?? ""

Conveniently, the namespace is held in an environment variable called __OW_NAMESPACE. In Swift, we can retrieve environment variables from the ProcessInfo.processInfo.environment which will return an Optional String. As we’re lazy, we convert the Optional to a concrete String using ?? "". In a proper application, we’d implement Swift’s error handling and do it properly.

Note that the namespace doesn’t start with a leading /, but our fully qualified action name does, so we create our action name like this:

let name = "/" + namespace + "/DC/incrementDrinkCount"

The action’s name is incrementDrinkCount and it’s in the DC package, so we add those in to create our action name.

Invoke the action

Invoking the action is easy enough:

Let p = ["name": "A nice hot cup of tea!"]
let result = Whisk.invoke(actionNamed: name, withParameters: p)

We call Whisk.invoke() with our action name and a dictionary of parameters if we have any. In our case, we pass in the name of the drink who’s count we want to increment.

The action is executed and the result is returned as a dictionary of type [String:Any].

Data returned from Whisk.invoke()

You get a dictionary back from Whisk.invoke() with lots of interesting information:

[
    "activationId": "f91ac41933274f8eb191082a834f70a4",
    "annotations": [
        [
            "key": "limits",
            "value": [
                "logs": 10,
                "memory": 256,
                "timeout": 60000
            ]
        ],
        [
            "key": "path",
            "value": "19FT_dev/DC/incrementDrinkCount"
        ]
    ],
    "duration": 40,
    "end": 1488230688926,
    "logs": [],
    "name": "incrementDrinkCount",
    "namespace": "19FT_dev",
    "publish": false,
    "response": [
        "result": [
            "drink": "A nice hot cup of tea!",
            "new_count": "24"
        ],
        "status": "success",
        "success": true
    ],
    "start": 1488230663040,
    "version": "0.0.3"
]

The interesting information is in the response dictionary and the first thing to check is the success key which is a boolean and and so is either true or false:

let response = result["response"] as! Dictionary
if response["success"] as! Bool == false {
    return ["error": "incrementDrinkCount failed"]
}

As you can see, we have to downcast a lot

As the type of the dictionary is [String:Any], we have to downcast all the time!

To make this easier, we can use the SwiftyJSON library which handles the casting for us:

let jsonResult = JSON(result)
if jsonResult["response"]["success"].boolValue == false {
    return ["error": "incrementDrinkCount failed"]
}

This becomes even more useful as we delve deeper into a nested dictionary!