DK Ancora Imparo

Google Cloud Functions

Google Cloud Functions is a compute solution offering from Google that is described as lightweight and event-based. It allows you to create small, single-purpose functions that respond to cloud events without the need to manage a runtime environment or a server. Cloud Functions however is an alpha release that might be changed in backward-incompatible way and it requires the corresponding Google Cloud Platform account to be whitelisted.

Cloud Functions code are written in JavaScript and executed on Google Cloud Platform in a managed Node.js environment. The code can be triggered asynchronously by events from Google Cloud Storage and Google Cloud Pub/Sub. The code can also be triggered synchronously by HTTP invocation.

Getting Started

Install the Google Cloud SDK: brew cask install google-cloud-sdk. This includes the gcloud tool that provides the primary command-line interface to the Google Cloud Platform. Authenticate the gcloud tool with your Google Cloud Platform account: gcloud init. Lastly, install the alpha commands component: gcloud components install alpha.

Using the web console, create a new project, e.g. DevWorld. And set it as the default project:

$ gcloud projects list
PROJECT_ID              NAME      PROJECT_NUMBER
devworld-1470605832118  DevWorld  386816325421
$ gcloud config set project devworld-1470605832118

Lastly, go to the Cloud Functions web console to enable the API.

Git Repository

When we create a new project, we get a Git repository that we can use to store our files; so that we don’t need to use a Cloud Storage bucket. Note that if you install the SDK with Homebrew Cask, you need to rename the git-credential-gcloud file or create a soft link to git-credential-gcloud.sh, e.g. cd ~/.homebrew/bin/ && ln -s git-credential-gcloud git-credential-gcloud.sh

$ mkdir cloudfunctions && cd cloudfunctions
$ git init
$ touch README.md
$ git add .
$ git commit -m 'Initial commit'
$ git config credential.helper gcloud.sh
$ git remote add origin https://source.developers.google.com/p/devworld-1470605832118/r/default
$ git push --all origin

On another computer, we could just clone the repository: gcloud source repos clone default cloudfunctions --project=devworld-1470605832118

We will use this repository to store our Cloud Functions files.

Example

As an example, let’s create a simple function that can be called by using curl. Create the following index.js file in a new helloMessage folder:

exports.helloMessage = function helloMessage(req, res) {
    var name;
    switch (req.get('content-type')) {
        case 'application/json': // '{"name":"John"}'
            name = req.body.name;
            break;
        case 'text/plain': // 'John'
            name = req.body;
            break;
        case 'application/x-www-form-urlencoded': // 'name=John'
            name = req.body.name;
            break;
    }

    res.status(200).send('Hello ' + (name || 'World') + '!');
};

Commit the code, push it to the remote repository, and deploy the code by executing: gcloud alpha functions deploy helloMessageFunction --source-url https://source.developers.google.com/p/devworld-1470605832118/r/default --source /helloMessage --trigger-http --source-branch master --entry-point helloMessage. This creates a new function with the name helloMessageFunction that uses the code in helloMessage module in the index.js file in the /helloMessage folder. The function can be invoked synchronously via HTTP methods.

We can test the function by directly triggering the function with the gcloud tool: gcloud alpha functions call helloMessageFunction --data '{"name":"Bill"}'. Or, by using curl: curl -X POST https://us-central1-devworld-1470605832118.cloudfunctions.net/helloMessageFunction -H "Content-Type:application/json" --data '{"name":"Steve"}'.

Cloud Functions Types

There are two distinct Cloud Functions types: HTTP (foreground) functions and background functions.

HTTP Functions

HTTP functions are used when we want to invoke them via standard HTTP requests with various methods like: GET, PUT, POST, DELETE, and OPTIONS. The function signature takes HTTP-specific arguments: request and response. The arguments have properties of ExpressJS Request and Response objects that are used to extract and return data. Note that when the function has completed, we need to call a termination method such as send(), json(), or end(). Otherwise, the function may continue to run and be terminated by the system platform.

Background Functions

Background functions are used when we want the Cloud Functions code to be invoked because of a change in a Cloud Storage bucket or a message on a Pub/Sub topic.The function signature takes two arguments: context and data. As in the case with HTTP function, when the background function has completed, we need to call a termination method such as success(), failure(), or done(). Otherwise, the function may continue to run and be terminated by the system platform. The context parameter holds the information about the execution environment and it also includes callback functions to signal completion of the background function. And the data parameter contains the data associated with the event that triggered the function execution.

IBM Bluemix OpenWhisk

IBM Bluemix is a cloud platform as a service developed by IBM. Think of it as an alternative to Amazon Web Services or Google Cloud Platform. Bluemix is developed using Cloud Foundry which is basically a software stack to build your own PaaS (another alternative to Cloud Foundry is OpenStack).

OpenWhisk is a distributed event-driven programming service. It provides programming model to upload event handlers to a cloud service, and register the handlers to respond to various events. The events can come from various Bluemix services like Cloudant, external sources, or direct invocations from web or mobile apps over HTTP.

With OpenWhisk, developers only pay for what they use and they don’t have to manage a server. However, OpenWhisk is still an experimental service from IBM that might change in ways that are incompatible with earlier versions.

Getting Started

We assume that the default organisation and space have been set during the IBM Bluemix account signup process.

Install the wsk command line tool: pip install --upgrade https://new-console.ng.bluemix.net/openwhisk/cli/download. Set the OpenWhisk namespace and authorisation key: wsk property set --apihost openwhisk.ng.bluemix.net --auth XXXX:XXXX --namespace "namespace_dev" where the required information can be found on the OpenWhisk web console. Verify the setup: wsk action invoke /whisk.system/samples/echo -p message hello --blocking --result which performs a synchronous blocking invocation of echo with hello as an argument.

Key Concepts

There are several key concepts and terminologies in OpenWhisk:

  • Trigger: a class of events emitted by event sources.
  • Action: the actual code to be executed whenever a trigger fires. It can be a snippet of JavaScript or Swift code, or a custom binary in a Docker container.
  • Rule: an association between a trigger and an action.
  • Feed: a code that configures an external event source to fire trigger events.
  • Package: a bundle of feeds and actions that describe a service in a uniform manner.

OpenWhisk Actions

OpenWhisk supports action code written in Swift that will be executed in a Linux environment. However, be aware that the version of Swift on Linux that is used with OpenWhisk might be different with versions of Swift from stable releases of Xcode. Create the following greetSomeone.swift:

func main(args: [String:Any]) -> [String:Any] {
  if let name = args["name"] as? String {
    return [ "greeting" : "Hello \(name)!" ]
  } else {
    return [ "greeting" : "Howdy stranger!" ]
  }
}

Note that Swift actions always consume a dictionary and produce a dictionary. You can create the OpenWhisk action by executing: wsk action create greetSomeone greetSomeone.swift and invoking it by executing: wsk action invoke --blocking greetSomeone --param name Tim

{
  "result": {
      "greeting": "Hello Tim!"
  },
  "status": "success",
  "success": true
}

The --result flag makes the invocation shows only the result (it requires --blocking flag). If you don’t need the result right away, you can make a non-blocking invocation:

$ wsk action invoke greetSomeone --param name Tim
ok: invoked greetSomeone with id e106be8bf30d4eb598c8bb5141a1b00c
$ wsk activation result e106be8bf30d4eb598c8bb5141a1b00c
{
    "greeting": "Hello Tim!"
}
Default Parameters

OpenWhisk allows us to set default parameters for action invocations. As an example, we can set default parameters for the previous example: wsk action update greetSomeone --param name Steve and invoking it:

$ wsk action invoke --blocking --result greetSomeone
{
  "greeting": "Hello Steve!"
}

Note that it achieves the same thing as the Swift default parameters in functions although it is at a different abstraction level.

Action Sequences

OpenWhisk allows us to create an action from a sequence of actions. Think of it like a pipeline of OpenWhisk actions. This allows us to create abstractions of common functions and compose them to build the desired actions. For example, write the following code as sort.swift and create a new OpenWhisk action: wsk action create sort sort.swift:

func main(args: [String:Any]) -> [String:Any] {
  if let payload = args["payload"] as? [Any] {
    let strings = payload.map { String($0) }
    return ["payload": strings.sort { $0 < $1 }]
  } else if let payload = args["payload"] as? String {
    return ["payload": payload]
  } else {
    return ["payload": [String]()]
  }
}

Write the following code as uppercase.swift and create a new OpenWhisk action: wsk action create uppercase uppercase.swift:

func main(args: [String:Any]) -> [String:Any] {
  if let payload = args["payload"] as? [Any] {
    let strings = payload.map { String($0).uppercaseString }
    return ["payload": strings]
  } else if let payload = args["payload"] as? String {
    return ["payload": payload.uppercaseString]
  } else {
    return ["payload": [String]()]
  }
}

We could test the above two actions:

$ wsk action invoke --blocking --result sort --param payload '["a", "c", "b"]'
{
    "payload": [ "a", "b", "c" ]
}
$ wsk action invoke --blocking --result uppercase --param payload '["a", "c", "b"]'
{
     "payload": [ "A", "C", "B" ]
}

And we could create a new action by composing the previous two actions:

$ wsk action create uppercaseSort --sequence uppercase,sort
$ wsk action invoke --blocking --result uppercaseSort --param payload '["a", "c", "b"]'
{
     "payload": [ "A", "B", "C" ]
}

Note that the parameter name payload is the same for input as well as for output. This simplifies action composition.

Miscellaneous

We could delete unused actions by executing wsk action delete with the action name. For example:

$ wsk action list
actions
/worqbench_dev/uppercaseSort
/worqbench_dev/greetSomeone
/worqbench_dev/uppercase
/worqbench_dev/sort
$ wsk action delete greetSomeone

Triggers and Rules

A trigger can be thought of as a named channel for a class of events. A trigger can be activated or fired by an event which is a dictionary of key-value pairs. Each firing of a trigger results in an activation ID. A trigger can be fired by a user or by an external event source. A feed is a way to configure an external event source to fire a trigger.

A rule associates one trigger with one action. Every firing of the trigger causes the action to be executed with the event as the input. If a trigger is fired without an accompanying rule to match against then it has no visible effect.

As an example, create the following action helloAction.swift:

func main(args: [String:Any]) -> [String:Any] {
      if let name = args["name"] as? String {
          return [ "payload" : "\(name)" ]
      } else {
          return [ "payload" : "anonymous" ]
      }
}
$ wsk action create helloAction helloAction.swift

Create a trigger and confirm that it has been created:

$ wsk trigger create eventUpdate
$ wsk trigger list
triggers
/worqbench_dev/eventUpdate

The above command creates a named channel to which events can be fired. We need to create a rule to observe the effect: wsk rule create --enable myRule eventUpdate helloAction

In a new terminal window, we could poll for the rule invocation: wsk activation poll helloAction. When we fire a trigger event, e.g. wsk trigger fire eventUpdate --param name "Rob". We could see the activation ID and payload as follows:

$ wsk activation poll helloAction
Hit Ctrl-C to exit.
Polling for logs
Activation: helloAction (4542fc011e214fe19ad94d2de583b5c4)
$ wsk activation result 4542fc011e214fe19ad94d2de583b5c4
{
    "payload": "Rob"
}

We can create multiple rules that associate one trigger with different actions. The trigger and action that define a rule must be in the same namespace and cannot belong to a package. If an action belongs to a package, we need to copy it into our namespace first, e.g. wsk action create echo --copy /whisk.system/samples/echo.

API Calls

OpenWhisk actions that we have created can be called directly without using the OpenWhisk CLI. It requires the auth key and token that can be obtained by executing: wsk property get --auth. The strings before and after the colon are the key and token, respectively. We could take a look at the HTTP request and response by using the -v flag. For example:

$ wsk -v action invoke --blocking --result helloAction --param name 'Donny'
{'apihost': 'openwhisk.ng.bluemix.net', 'namespace': 'worqbench_dev', 'clibuild': '2016-03-14T11:48:47-05:00', 'apiversion': 'v1'}
========
REQUEST:
POST https://openwhisk.ng.bluemix.net/api/v1/namespaces/worqbench_dev/actions/helloAction?blocking=true
Headers sent:
{
    "Authorization": "Basic OxLc4NzA3YjYtNTUyMC00YWY3LTgyMzctMDM5YmIxN2QwNzY3OnQ2RjVmdXBiZ1dLVE9kRWxhTWZoZTdxeXVJR3FTTEpQcUs0WUQ0bEF0MDg0MUtibTBHeGk3NFFUSkhSVVhpN1k=",
    "Content-Type": "application/json"
}
Body sent:
{"name": "Donny"}
--------
RESPONSE:
Got response with code 200
Body received:
{
  "name": "helloAction",
  "subject": "donny@gmail.com",
  "activationId": "6874839ad6734954a1f00e8d06df3764",
  "publish": false,
  "annotations": [],
  "version": "0.0.1",
  "response": {
    "result": {
      "payload": "Donny"
    },
    "success": true,
    "status": "success"
  },
  "end": 1470054288984,
  "logs": [],
  "start": 1470054288959,
  "namespace": "donny@gmail.com"
}
========
{
    "payload": "Donny"
}

The above execution is equivalent to the following curl invocation:

$ curl -X POST -H "Content-Type: application/json" --data '{"name": "Donny"}' -u 0x78707b6-5520-4af7-8237-039bb17d0767:t6F5fupbgWKTOdElaMfhe7qyuIGqSLJPqK4YD4lAt0841Kbm0Gxi74QTJHRUXi7Y https://openwhisk.ng.bluemix.net/api/v1/namespaces/worqbench_dev/actions/helloAction?blocking=true
{
  "name": "helloAction",
  "subject": "donny@gmail.com",
  "activationId": "3d450ffb41cf407ca0f3109eeece4f6e",
  "publish": false,
  "annotations": [],
  "version": "0.0.1",
  "response": {
    "result": {
      "payload": "Donny"
    },
    "success": true,
    "status": "success"
  },
  "end": 1470058872478,
  "logs": [],
  "start": 1470058868947,
  "namespace": "donny@gmail.com"

All OpenWhisk APIs are protected with HTTP Basic authentication. At the moment, OpenWhisk supports only one key per account. Be aware that your key would need to be embedded in client-side code making it visible to the public.

OpenWhisk iOS SDK

OpenWhisk provides an iOS SDK that allows apps to easily invoke actions and fire remote triggers. It is written in Swift 2.2 and supports iOS 9 and later versions. The SDK can be installed by using Carthage, CocoaPods, or from the source directory. We could get started by getting the starter app example, installing the dependencies, and taking a look at the code:

$ mkdir iOS ; cd iOS
$ wsk sdk install iOS
$ pod install

Take a look at the ViewController.swift file and set the OpenWhisk credentials according to the output of wsk property get --auth.

My Elm adventure

On Sunday, 8th of May 2016, I started reading about Elm. I was interested because of its functional reactive programming nature and it’s one of the inspirations (though not primary) for ReactiveCocoa and Redux. I started reading about its syntax, its architecture, looking at a few examples, and watching several awesome videos about Elm. I especially like the concept of signals and mailboxes. It’s not a foreign concept if you’re coming from ReactiveCocoa or other FRP frameworks. In Elm, signals felt natural and it fit nicely with the language (I believe it’s due to the fact that Elm is functional rather than imperative). Elm signals did not feel bolted-on like in ReactiveCocoa.

Well, on Tuesday (10th of May 2016), signals were removed from Elm :scream: with the post titled A Farewell to FRP. I was completely surprised and dumbstruck. But not for long, the more I read and internalise the post and the discussion on Elm slack channel, it became clear that subscriptions in Elm 0.17 was actually a really big deal and it simplified a lot of things. Rather than setting up the signals wiring, subscriptions let our components sit around and wait for messages, whether it’s from WebSocket, the clock, etc. And it becomes the underlying library code’s responsibility to handle a bunch of tricky resource management stuff behind the scenes (e.g. WebSocket connection). I think it’s a win for users.

Our team used IdeaBoardz to conduct our retros and I thought that its UI could be improved :wink:. Thus, I decided to spend the following week after my learning to build Elmütt that is a fresh clone of IdeaBoardz written in Elm. It uses Elm 0.17 and Bootstrap 4 for the front end; and Python 2.7, Flask, Redis, and WebSocket for the back end. And it is deployable to Heroku.

I am really happy with Elm :thumbsup: :100: and it is such a breath of fresh air. Below is my notes that I took whilst learning about Elm. I think you should try Elm.

Elm

This text file describes the Elm programming language, specifically version 0.17. It was designed by Evan Czaplicki to provide “the best of functional programming in your browser”. Elm is about:

  • No runtime errors in practice. No null. No undefined is not a function.
  • Friendly error messages that help you add features more quickly.
  • Well-architected code that stays well-architected as your app grows.
  • Automatically enforced semantic versioning for all Elm packages.

Elm is functional, no state and no side effects. Elm is a strongly typed language with static typing, similar to Haskell and other ML inspired languages.

Getting Started

Install the necessary software:

  brew install elm
  npm install -g elm-server
  npm install -g watchman

And create a simple project:

  mkdir elm
  cd elm
  elm package install
  elm package install elm-lang/html
  touch Main.elm

Edit the file Main.elm to contain the following code:

  module Main exposing (main)
  import Html
  main = Html.text "Hello World"

Compile the file: elm make Main.elm and open the resultant index.html. Alternatively, run elm reactor and go to http://localhost:8000. Unfortunately, it doesn’t support live reload. To get the live reload functionality, install elm-server and run elm-server Main.elm.

The Elm Architecture

The Elm Architecture is the recommended pattern for building Elm applications. It’s a unidirectional architecture that consists of three components: - Model is a type defining the data structure of the state. - View is a function that transforms state into rendering (e.g. HTML). - Update is a function from previous state and an action to new state. An action is a type that encapsulate a user action or an external event.

In code, it looks like the following:

  module Main exposing (main)
  import Html
  import Html.App
  main : Program Never
  main =
    Html.App.beginnerProgram
      { model = "Hello, world!" , view = Html.text , update = identity }

Or, in the expanded form:

  module Main exposing (main)

  import Html exposing (Html, text, div)
  import Html.App

  type alias Model = String

  model : Model
  model = "Hello, world!"

  view : Model -> Html Msg
  view model =
    div [] [ text (toString model) ]

  type Msg = NoOp

  update : Msg -> Model -> Model
  update msg model =
    model

    main : Program Never
    main =
      Html.App.beginnerProgram
        { model = model
          , view = Html.text
          , update = update
        }

References