Serverless architectures are becoming increasingly popular, and with good reason. In my experience, unless you are a devops expert or have one on your team, tools like Docker just make devops harder. With FaaS architectures, in theory the only devops you need is bundling and uploading your app. However, a purely stateless function would have to make a separate database connection every time it runs, which is a major performance penalty. Most FaaS services have a workaround that lets you reuse database connections, like AWS Lambda and Azure Functions. IBM Cloud Functions is IBM's equivalent to Lambda and Azure Functions. This article will walk you through setting up an IBM Cloud Function in Node.js that connects to MongoDB and reuses the database connection between requests.

Creating a Simple IBM Cloud Function

Go to IBM's OpenWhisk landing page and log in. OpenWhisk is an Apache open source project that provides the underlying framework for IBM Cloud Functions. In theory, you could run OpenWhisk yourself and use the same deployment strategy you do for IBM Cloud, but I haven't tried this.

Once you've logged in, click on the "Actions" tab on the left and then click "Create."

For this simple example, click on "Quickstart Templates" to get a list of ready-made "actions". OpenWhisk calls functions "actions", presumably because actions have a very specific function signature.

Select the "Hello World" function template.

The next step asks you for a package name, a runtime, and the actual code. The default options are sufficient for this simple example, because IBM Cloud will fill in the code for a rudimentary "Hello, World" action for you.

Great, now you have your first cloud function. You can execute the function, but you still need to expose it via HTTP. Click the "Endpoints" tab on the left to create an endpoint for this function.

Now, click the "Enable as Web Action" checkbox to expose this action via REST API. A web action has a couple special properties as opposed to a regular action. First, a web action must return an object that is serializable into JSON, or a promise that resolves to such an object. Second, if a web action returns an object that has a headers, statusCode, or body property, OpenWhisk will use those properties to structure the HTTP response, rather than just stringifying the resulting JSON object.

Once you've made your action into a web action, you should be able to access it via REST API. Click the copy icon in the "HTTP Method" panel to copy the URL for your function.

Running curl on the URL will then execute your function and give you the result.

$ curl https://openwhisk.ng.bluemix.net/api/v1/web/val%40karpov.io_dev/hello-world/helloworld.json
{
  "greeting": "Hello stranger!"
}
$

Connecting to MongoDB and Reusing the Connection

Currently, you can't install custom npm packages for IBM Cloud Functions through the browser GUI, you need to bundle your node_modules and upload your function via the CLI. However, the IBM Cloud Function runtime has the mongodb driver pre-installed. This means you can start using the MongoDB driver without any extra steps. Below is a simple function that connects to MongoDB Atlas with the sensitive credentials omitted. IBM Cloud Functions use Node.js 8 by default, which means you can use async/await.

const mongodb = require('mongodb');
const pkg = require('mongodb/package.json');

const uri = 'mongodb+srv://OMITTED.mongodb.net/test';

async function main(params) {
  const driverVersion = pkg.version;
  const client = await mongodb.MongoClient.connect(uri);

  const docs = await client.db('test').collection('tests').find().toArray();

  return { body: { result: docs, driverVersion } };
}

exports.main = main;

You can click the "Invoke" button to run your function without having to switch to the command line and use curl. Below is the output of running the above function. Notice that the current MongoDB driver version is 3.0.4, which is the latest version when this article was written.

So the only problem now is that this function creates a new connection to MongoDB Atlas every time. The 900ms runtime is indicative of the fact that the MongoDB driver needs to connect to a MongoDB instance running on AWS every time.

Thankfully, the same trick for reusing connections that works for Lambda and Azure Functions also works for IBM Cloud. Global-scoped variables may be persisted between calls, so if you keep a global reference to your database client you can reuse it.

const mongodb = require('mongodb');

const uri = 'mongodb+srv://OMITTED.mongodb.net/test';

let client = null;

async function main(params) {
  const reused = client != null;
  if (client == null) {
    client = await mongodb.MongoClient.connect(uri);  
  }

  const docs = await client.db('test').collection('tests').find().toArray();

  return { body: { result: docs, reused } };
}

exports.main = main;

Below are two executions of this action back-to-back. Notice that the second execution is much faster because it reused the database connection.

Moving On

IBM Cloud Functions are an appealing alternative to AWS Lambda and Azure Functions. I haven't tried their CLI yet, but between the built-in npm modules and the fact that they use Node.js 8 by default, getting started is very easy and you can get a lot done even without the CLI. I have yet to use IBM Cloud Functions for a production application, but so far IBM Cloud Functions look promising and I look forward to trying them out for a real app.

Found a typo or error? Open up a pull request! This post is available as markdown on Github
comments powered by Disqus