Composing functions into applications

The Serverless Way

rodric rabbah
Apache OpenWhisk
4 min readOct 16, 2017

--

Serverless computing is a new model of programming for the cloud that has gained traction with developers because of key value propositions: little to no concern about infrastructure operations, auto provisioning and auto scaling, and pay-per-use with zero cost for idle time. The fastest disruption in serverless has occurred with Functions-as-a-Service (FaaS), which are cloud-native, usually stateless, functions that scale on demand. FaaS is now offered by all major cloud vendors, and IBM was an early entrant into this new market with OpenWhisk, now an open source Apache incubator and a growing open source community around it.

It does not take long programming in this style to realize that FaaS fosters a model where code is organized into small functions, and developers need a way to orchestrate an application’s execution through many functions. IBM Cloud Functions, built on Apache OpenWhisk, offers sequences as a form of composition to construct data pipelines, where the result of one function becomes the input of another.

But applications are more than just sequential compositions, and control flow orchestration is ubiquitous to programming languages. I’ll illustrate some examples below, you can imagine how these might look in your favorite language.

  • A sequence is the equivalent of a semicolon:
function main(args) { 
let result = A(args);
return B(result);
}
  • Control flow using test and branch or more commonly if-then-else:
function main(args) { 
if ( C(args) ) {
return A(args);
} else {
return B(args);
}
}
  • And of course, exception and error handling with try-catch:
function main(args) { 
try {
return A(args);
} catch (e) {
return B(e);
}
}

It’s also common for a language to offer various looping constructs such as do, for and while, among others.

With functions-as-a-service, the building blocks of most applications and REST APIs can run serverlessly. Functions are activated in response to incoming events (which might be HTTP requests for example), and resources are automatically reserved for the duration of that function’s execution. As more functions are activated, more resources may be allocated.

What about the compositions however? If one represents the composition as shown above and runs it as a serverless function, they are subject to “double billing” — the composition waits for the result of each function it calls, and hence must pay for the wait time. This violates one the serverless principles: pay for what you use, not idle time.

The illustration shows a manually composed sequence, which is double billed. The parent main calls function A, waits for A to complete to retrieve its result and then calls B. The composition (main) is billed for the resources it is consuming, in addition to the resources consumed by A and B. The wait time in the composition violates a serverless principle: do not pay for idle time.

Serverless compositions, simply put, eliminate the double billing dilemma. In fact, without intrinsic support from the serverless platform, it is impossible to avoid double billing and still be serverless.

Composer for IBM Cloud Functions is a new programming model from IBM Research for building serverless applications that compose functions, APIs and services. It is built on top of Apache OpenWhisk and works with standalone deployments or with IBM Cloud Functions.

Composer is not a new programming language. Instead, it is a library of combinators — meta programming using functional programming — which you use in your preferred language. It makes it possible to write straightforward code to express control and data flow, and may be used with polyglot functions. The composer library is currently available in Node.js but the same approach can be generalized to other programing languages, like Python, Swift, and Java. It is worth noting that even though the library is available in JavaScript, it does not require one to develop in that language. Our goal is compositions that are easy to express and program, familiar to developers, and which offer the same benefits that make FaaS attractive and disruptive.

Here is a sequence, if-then-else and try-catch using composer, matching the examples shown earlier.

composer.sequence('A', 'B')  // functions are referenced by name
composer.if('C', 'A', 'B') // if ( C(args) ) A(args) else B(args)
composer.try('A', B') // try { A(args) } catch(e) { B(e) }

In IBM Cloud Functions, compositions are represented as and execute as OpenWhisk actions. They run with support from a composition conductor, which manages the program state as the composition executes. The conductor also runs as an action, but unlike the manual composition shown earlier, it will not suffer from double billing. Instead, the execution of an application unfolds with continuation passing between functions, to resolve the next state of the computation and hand off the control flow. This is illustrated below.

The current technology preview of Composer does not yet fully utilize continuation passing, which is in the works and will be contributed to Apache OpenWhisk. We have made it available on GitHub for feedback, early adopters, and contributors.

We believe this is only the start of new programming abstractions and models for the serverless cloud. It does not take much to imagine a rich future of innovation around programming models, compilation, and runtime optimizations. Our group at IBM Research is excited about the opportunities and is at the forefront tackling these issues.

--

--