Composer makes it possible to assemble actions into rich workflows called compositions. An example composition is described in ../README.md.
Compositions can express the control flow of typical a sequential imperative programming language: sequences, conditionals, loops, structured error handling. This control flow is specified using combinator methods such as:
composer.sequence(firstAction, secondAction)
composer.if(conditionAction, consequentAction, alternateAction)
composer.try(bodyAction, handlerAction)
Combinators are described in COMBINATORS.md.
Combinators return composition objects, i.e., instances of the Composition
class.
A composition, like any action, accepts a JSON dictionary (the input parameter object) and produces a JSON dictionary (the output parameter object). An output parameter object with an error
field is an error object. A composition fails if it produces an error object.
By convention, an error object returned by a composition is stripped from all fields except from the error
field. This behavior is consistent with the OpenWhisk action semantics, e.g., the action with code function main() { return { error: 'KO', message: 'OK' } }
outputs { error: 'KO' }
.
Error objects play a specific role as they interrupt the normal flow of execution, akin to exceptions in traditional programming languages. For instance, if a component of a sequence returns an error object, the remainder of the sequence is not executed. Moreover, if the sequence is enclosed in an error handling composition like a composer.try(sequence, handler)
combinator, the execution continues with the error handler.
The field name $composer
is reserved for composer internal use. Compositions and composed actions should not expect or return parameter objects with a top-level field named $composer
.
The invocation of a composition triggers a series of computations (possibly empty, e.g., for the empty sequence) obtained by chaining the components of the composition along the path of execution. The input parameter object for the composition is the input parameter object of the first component in the chain. The output parameter object of a component in the chain is typically the input parameter object for the next component if any or the output parameter object for the composition if this is the final component in the chain.
For example, the composition composer.sequence('triple', 'increment')
invokes the increment
action on the output of the triple
action.
Some combinators however are designed to alter the default flow of data. For instance, the composer.merge('myAction')
composition merges the input and output parameter objects of myAction
.
Components of a compositions can be actions, Python functions, or compositions.
Python functions can be viewed as simple, anonymous actions that do not need to be deployed and managed separately from the composition they belong to. Functions are typically used to alter a parameter object between two actions that expect different schemas, as in:
composer.sequence('getUserNameAndPassword', params => ({ key = btoa(params.user + ':' + params.password) }), 'authenticate')
Combinators can be nested, e.g.,
composer.if('isEven', 'half', composer.sequence('triple', 'increment'))
Compositions can reference other compositions by name. For instance, assuming we deploy the sequential composition of the triple
and increment
actions as the composition tripleAndIncrement
, the following code behaves identically to the previous example:
composer.if('isEven', 'half', 'tripleAndIncrement')
The behavior of this last composition would be altered if we redefine the tripleAndIncrement
composition to do something else, whereas the first example would not be affected.
A composition can embed the definitions of none, some, or all the composed actions as illustrated in demo.js:
composer.if( composer.action('authenticate', { action: function ({ password }) { return { value: password === 'abc123' } } }), composer.action('success', { action: function () { return { message: 'success' } } }), composer.action('failure', { action: function () { return { message: 'failure' } } })) )
Deploying such a composition deploys the embedded actions.
Compositions are implemented by means of OpenWhisk conductor actions. Compositions have all the attributes and capabilities of an action, e.g., default parameters, limits, blocking invocation, web export. Execution traces and limits of compositions follow from conductor actions.