UNPKG

usher

Version:

Simple DSL for composing decision workflows for AWS Simple Workflow

323 lines (240 loc) 8.14 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Usher Source: workflow.js</title> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/sunlight.default.css"> <link type="text/css" rel="stylesheet" href="styles/site.simplex.css"> </head> <body> <div class="container-fluid"> <div class="navbar navbar-fixed-top "> <div class="navbar-inner"> <a class="brand" href="index.html">Usher</a> <ul class="nav"> <li class="dropdown"> <a href="classes.list.html" class="dropdown-toggle" data-toggle="dropdown">Classes<b class="caret"></b></a> <ul class="dropdown-menu "> <li> <a href="Fragment.html">Fragment</a> </li> <li> <a href="Usher.html">Usher</a> </li> <li> <a href="Workflow.html">Workflow</a> </li> </ul> </li> </ul> </div> </div> <div class="row-fluid"> <div class="span12"> <div id="main"> <h1 class="page-title">Source: workflow.js</h1> <section> <article> <pre class="sunlight-highlight-javascript linenums">/*! * Usher * Copyright(c) 2014 meltmedia &lt;mike@meltmedia.com> */ 'use strict'; var winston = require('winston'), util = require('util'), os = require('os'), _ = require('lodash'), swf = require('aws-swf'), AWS = require('aws-sdk'), sequencify = require('@meltmedia/sequencify'), Context = require('./context'), Fragment = require('./fragment'), WorkflowExecution = require('./execution'); module.exports = Workflow; /** * Represents a single, named workflow, where all activities and decisions are defined. * @constructor * @param {string} name - The name of the workflow. * @param {string} domain - The AWS SWF domain name to execute this workflow in. * @param {string} [tasklist=&lt;name>-tasklist] - The name of the tasklist to listen for tasks on. */ function Workflow(name, domain, tasklist) { if (!(this instanceof Workflow)) { return new Workflow(name, domain, tasklist); } Fragment.call(this, name); if (!_.isString(domain)) { throw new Error('A `domain` is required'); } this.domain = domain; this.tasklist = tasklist; } // Make Workflow an extention of Fragment util.inherits(Workflow, Fragment); /** * Start listening for decision tasks from SWF * @returns {Workflow} This workflow so you can chain commands. */ Workflow.prototype.start = function start() { // Validate workflow for required dependencies and cycles try { this._validate(); } catch (err) { winston.log('error', 'Invalid workflow due to the following error: %s', err.message); throw err; } // If we already have a decider, skip setup if (!_.isEmpty(this.decider)) { return this; } var config = { 'domain': this.domain, 'taskList': { 'name': this.tasklist || this.name + '-tasklist' }, 'identity': this.name + '-' + os.hostname() + '-' + process.pid, 'maximumPageSize': 100, 'reverseOrder': false }; this.decider = new swf.Decider(config, new AWS.SimpleWorkflow()); this.decider.on('decisionTask', this._onDecisionTask.bind(this)); // Start polling for decision tasks this.decider.start(); return this; // chainable }; /** * Stop listening for decision tasks from SWF * @returns {Workflow} This workflow so you can chain commands. */ Workflow.prototype.stop = function stop() { if (!_.isEmpty(this.decider)) { // Stop the poller this.decider.stop(); // Remove the instance so config changes can be made between start/stop cycles delete this.decider; } return this; // chainable }; /** @private */ Workflow.prototype._validate = function _validate() { // Map tasks into sequencify's input format var names = [], items = {}; _.each(this.tasks(), function (item) { names.push(item.name); items[item.name] = { name: item.name, dep: item.deps }; }); var results = sequencify(items, names); // Required dependencies not defined if (!_.isEmpty(results.missingTasks)) { throw new Error('The workflow is missing tasks: ' + JSON.stringify(results.missingTasks)); } // Recursive dependencies found if (!_.isEmpty(results.recursiveDependencies)) { throw new Error('The workflow has recursive dependencies: ' + JSON.stringify(results.recursiveDependencies)); } }; /** @private */ Workflow.prototype._onDecisionTask = function _onDecisionTask(decisionTask) { var context, state, self = this; winston.log('debug', 'Handling new decision task for workflow: %s', this.name); try { var execution = new WorkflowExecution(this.tasks()); context = new Context(decisionTask); execution.execute(context); } catch (e) { // Respond back to SWF failing the workflow winston.log('error', 'An problem occured in the execution of workflow: %s, failing due to: ', this.name, e.stack); decisionTask.response.fail('Workflow failed', e, function (err) { if (err) { winston.log('error', 'Unable to mark workflow: %s as failed due to: %s', self.name, JSON.stringify(err)); return; } }); return; } // If any activities failed, we need to fail the workflow if (context.failWorkflow) { winston.log('warn', 'One of more activities failed in workflow: %s, marking workflow as failed', this.name); // Respond back to SWF failing the workflow decisionTask.response.fail('Activities failed', state.failed, function (err) { if (err) { winston.log('error', 'Unable to mark workflow: %s as failed due to: %s', self.name, JSON.stringify(err)); return; } }); } else { // Check to see if we are done with the workflow if (context.done()) { winston.log('info', 'Workflow: %s has completed successfuly', this.name); // Stop the workflow decisionTask.response.stop({ result: JSON.stringify(context.results) }); } // If no decisions made this round, skip if (!decisionTask.response.decisions) { winston.log('debug', 'No decision can be made this round for workflow: %s', this.name); decisionTask.response.wait(); } // Respond back to SWF with all decisions decisionTask.response.respondCompleted(decisionTask.response.decisions, function (err) { if (err) { winston.log('error', 'Unable to respond to workflow: %s with decisions due to: %s', self.name, JSON.stringify(err)); return; } }); } }; </pre> </article> </section> </div> <div class="clearfix"></div> <footer> <span class="copyright"> Copyright (C) 2014 meltmedia </span> <br /> <span class="jsdoc-message"> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.3.0-alpha5</a> on Mon Jun 23 2014 14:32:21 GMT-0700 (MST) using the <a href="https://github.com/terryweiss/docstrap">DocStrap template</a>. </span> </footer> </div> <br clear="both"> </div> </div> <script src="scripts/sunlight.js"></script> <script src="scripts/sunlight.javascript.js"></script> <script src="scripts/sunlight-plugin.doclinks.js"></script> <script src="scripts/sunlight-plugin.linenumbers.js"></script> <script src="scripts/sunlight-plugin.menu.js"></script> <script src="scripts/jquery.min.js"></script> <script src="scripts/jquery.scrollTo.js"></script> <script src="scripts/jquery.localScroll.js"></script> <script src="scripts/bootstrap-dropdown.js"></script> <script src="scripts/toc.js"></script> <script> Sunlight.highlightAll({lineNumbers:true, showMenu: true, enableDoclinks :true}); </script> <script> $( function () { $( "#toc" ).toc( { selectors : "h1,h2,h3,h4", showAndHide : false, scrollTo : 60 } ); $( "#toc>ul" ).addClass( "nav nav-pills nav-stacked" ); $( "#main span[id^='toc']" ).addClass( "toc-shim" ); } ); </script> </body> </html>