Dynamic rules engine to allow configurable workflow in app without requiring code changes.
Using Redis as backing service, you can design workflows and whenever you
start your workflow,
it will load your stored workflow(s) then attach a
pubsub listener to Redis. Any time your
pubsub channel message appears, it will parse the event object, perform conditional logic, and if true
emit one or more defined actions.
This app is loosely-based on popular enterprise systems workflow rules or business process features.
- Trigger (or event) - something happened
- Conditions (or filters) - criteria to take action
- Actions (or tasks) - what to do
You must have Redis server running. This app treats redis like a persistent backup. It uses
pubsub as a listener
for trigger events. It uses it to save and load workflows to avoid loss. The
get workflows methods
just interact with memory and not database. See the
IWorkflowManager interface source for more.
Most non-public properties and methods are
protected so you may sub-class and override.
RedisWorkflowManager is my
preferred implementation but you could also write a new manager class that implements
npm install redis-workflowyarn add redis-workflow
The test script in
package.json preprocesses the
.ts file and then executes.
npm test (also can run
npm run coverage)
npm run build). You can use in ES5 or later supported environments. The following code snippets are implemented in the
Quick start (Node)
const redis = ;const flow = ;// instantiateconst config = flow;const client = redis; // optionally pass into manager 2nd param to shareconst manager = config;// create first workflowconst trigger = "myTrigger";const rule = "myRule" "foo == bar";const action = "myAction";const workflow = "myWorkflow" trigger rule action;// add workflow to managermanager;// add listener for actionmanager;// add error handlermanager// start workflow enginemanagerstart"myChannel";// publish a test object to the pubsub channel;// sometime later ...;// note, if you publish an event and conditions do not == true, no action taken
See more detailed examples in the respective
Example with multiple rules and actions for a workflow
;; // optionally import each class (see __tests__);;; // just for our example to pubsub message// build test workflow;;;; // context added later when triggered;;// add first workflow to managermanager.setWorkflows;// errorsmanager.onWorkflowEvents.Error,// delayed actionsmanager.onWorkflowEvents.Schedule,;// immediate actionsmanager.onWorkflowEvents.Immediate,;// optionally handle actions by namemanager.on"adjustInventory",;// optionally handle global actions (all types)manager.onWorkflowEvents.Audit,;// optionally handle actions that didn't meet rulesmanager.onWorkflowEvents.Invalid,;// start manager (subscribes to pubsub channel)manager.start"babyDivision".then;// build and publish trigger events to Redis pubsub channel;// simulate time after manager starts before triggers appearsetTimeout, 3000;
pubsub listening for events to start workflow.
message published to the topic will include a stringified JSON object as follows.
"event": "test" // must equal Trigger name"context": "myField": "myValue" "anotherField": "anotherValue"
You define actions when building workflows. The action name will become an
EventEmitter event you handle.
// optionally namedmanager.on"eventName",;manager.onflow.WorkflowEvents.Schedule,;manager.onflow.WorkflowEvents.Immediate,;
Action you add to your workflow, you one or more listeners like above. You can decide what functionality
your application performs if conditions are met, and actions are emitted.
Suggested action types
- Create record(s)
- Update record(s)
- Trigger another workflow
- Send message(s) or notification(s)
This implementation using
pubsub can scale by leveraging different channels per instance, with a fanout. If you
want workers, I would override the
start method to use Redis
blrpoplpush (blocking pop of list) and publish
trigger events to it instead. This way you can spin up unlimited workers and once one pops from list, others ignore.
If you instantiate the manager and pass in the third optional argument
channels: string, the app will attempt
to load workflows from the database.
I haven't thought that far ahead yet. I needed this for my project and wanted to give back. ;-)
MIT (if you enhance it, fork and PR so the community benefits)