React to Database Changes with OpenWhisk Actions

One of the best features of CouchDB is its change feed which allows us to get a feed of the changes happening on our database. It's also possible to have a serverless function (examples are for IBM Cloud Functions but should also work for Apache OpenWhisk) that fires in response to activity on that change feed. I have a database of events that I want to react to when they happen, but the change feed doesn't include the actual document that was added - but you can add one of the built-in functions to a sequence to make that happen. This post will show how to achieve this by wiring up a built-in action to fetch the document with another action of our own that then handles the data.

The Database to Connect to

If you already have a database you want to use, skip to the next section. If not, here's where we set up a Cloudant (hosted Apache CouchDB) database on IBM Cloud.

There is a web interface but I mostly use the command line as I find it faster (also since I'm a disabled user, web interfaces can be painful although IBM Cloud isn't awful). These examples are all command-line based but if you're more of a graphical user, feel free to check out the web version also!

First, create a new database to use for the example. mine is called events and is created with the following command:

bx service create cloudantNoSQLDB Lite events

The command should show that the database was created and it will appear if you run bx service list.

Before we can connect to the database, we'll need some credentials to do that with. Here is how to create a new set of access credentials, in this case called mykey:

bx service key-create events mykey

With this in place, we can create the action that will be invoked when a new database entry is added.

Set up the Actions

In OpenWhisk (and therefore IBM Cloud Functions), there is a concept of "packages" that keep things together. In this example the credentials will belong to the package, and actions will be added to it to make use of those parameters.

Firstly, the package should be created (or updated if it exists)

bx wsk package update dbevent

Next, let's create an action to handle the newly-inserted document. Mine is for an entry in the users database (which must exist) and the code is fairly simple:

function main(data) {
  if(data._id) {
    console.log("New record created for user: " + data.user_name);
  } else {
    console.log("No document received");

This code is in a file called db-listener.js. To add it to the dbevent action, the command is:

bx wsk action update dbevent/db-listener db-listener.js

At this point, we're only halfway there! This action expects the document with its data fields to be available, but by default the Cloudant changes feed only gives document ID. However, help is at hand: the built-in Cloudant package has a "read" action that we can add into a sequence so that the process looks something like this:

  • new document inserted to Cloudant
  • changes feed trigger fires in OpenWhisk
  • rule makes trigger invoke a sequence
  • first action in sequence fetches the actual document rather than just the ID
  • the db-listener action created above then fires

To use the built-in Cloudant features, OpenWhisk needs to know about the Cloudant database to use. It will pick up the database and credentials when this command is run:

bx wsk package refresh

Follow this command with bx wsk package list and the package ending Bluemix_events_mykey should appear in the list. Copy the fully-qualified name containing the account and space currently being used - we'll need that in a moment.

At this point, we're ready to use Cloudant so let's create the sequence to fetch the document with the Cloudant read action and then pass it to the dbevent/db-listener action created above:

bx wsk action update dbevent/db-doc --sequence /Lorna.Mitchell_dev/Bluemix_events_mykey/read,dbevent/db-listener

Now we're ready! By linking the changes trigger to this sequence with a rule, we'll observe the code running in response to database changes.

Set up triggers and rules

First, configure a trigger from the database changes feed (you'll need that copied fully-qualified database package name again from before):

bx wsk trigger create db-change --feed /Lorna.Mitchell_dev/Bluemix_events_mykey/changes --param dbname users

The trigger doesn't fire until it has at least one rule associated with it - so here's the command to link the above trigger to our sequence:

bx wsk rule update db-users db-change dbevent/db-doc
bx wsk rule enable db-users

Everything is ready, let's watch it in action!

Cloudant Database Changes in Action

A simple way to see everything as-it-happens on OpenWhisk is to use the command:

bx wsk activation poll

This command is blocking, and will output information about what is happening. Run this in a separate terminal window.

Time to make a change to that users database!

Using your Cloudant client of choice (I'll admit that I did use curl to quickly add a record here!), add a record to the users database. Remember that the action expects a user_name field ... and watch the activations happen!

I can see that the db-change trigger fired, the db-doc sequence has two activation IDs inside it (one for each action), the built-in read action ran and shows the data it passed on to the db-listener .... which itself output "New record created for user: Alice" as expected.

The output of the activation command can be confusing because the output of each individual piece isn't in the order it actually happened in! Do scroll up as-needed to inspect what happened.

Reacting to Database Changes

Although the db-listener action in this example only outputs a log line, the possibilities here are endless. In my own applications I use code like this to add messages to a queue or to send notifications to the slackbot in response to changes. Hopefully this example with the sequence show helps you to come up with and implement ideas of your own. Let me know what you build!

Leave a Reply

Please use [code] and [/code] around any source code you wish to share.