Skip to content
Simple Workflow Library for Clojure
Clojure Shell
Pull request Compare This branch is 1 commit ahead of kyleburton:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.
examples
src/impresario
test/impresario/test
.gitignore
README.textile
project.clj
release.sh

README.textile

Impresario

A light-weight workflow (state machine) library in Clojure.

Overview

Impresario is a workflow system – mostly like a finite state machine, with the
exception that an Impresario flow may transition through multiple nodes if the
predicates defined in the flow allow for it.

Impresario flows are implemented as a map that defines the states, transitions,
predicates that determine if those transitions should be followed and call back
handlers which may be attached to specific events.

Workflow state is maintained in a map: the context. The context is availalbe
to all of the predicates and triggers. Just as with many of the Clojure
data strucutres, the context is immutable.

Predicates may access the context, but are unable to modify it. Triggers must
return the context – which allows for modification by triggers.

Workflows once defined must be registered with Impresario in order to be used.
When a workflow is registered, it is validated to ensure that: each state
configured as the target of a transition exists; and each state in the flow is
reachable (a state marked as a start state does not need to be reachable).

An Impresario flow must have a start state (marked with a :start true).
The on-enter! trigger for the :start state can be used to initialize
the workflow’s context.

DSL

To make the process of defining the flows simpler, a DSL has been created. This
is an example (from the unit tests) of a workflow that simulates a door. The door
may be open or closed, and if the door is closed it may be locked. If the door
is locked, it can not be opened.

(ns impresario.test.door-workflow
  (:use
   impresario.dsl
   impresario.core))

(on-enter! :start
  (assoc *context* :transitions []))

(defpredicate :start :closed
  true)

(defpredicate :open :closed
  (get *context* :close-door))

(defpredicate :closed :locked
  (get *context* :lock-door))

(on-enter! :locked
  (assoc *context* :locked? true))

(defpredicate :closed :open
  (and
   (not (get *context* :locked?))
   (not (get *context* :lock-door))))

(defpredicate :locked :closed
  (get *context* :locked?))

(on-transition! :locked :closed
  (dissoc *context* :locked?))

(on-transition-any!
  (update-in
   (dissoc *context* :close-door :lock-door :unlock-door)
   [:transitions]
   conj
   [*current-state* :=> *next-state*]))

(defmachine :door-workflow
  (state :start {:start true :transitions [:closed]})
  (state :closed {:transitions [:locked
                                :open]})
  (state :locked {:transitions [:closed]})
  (state :open   {:transitions [:closed]}))

(register-workflow :door-workflow *door-workflow*)

Bindings

Impresario makes use of several bindings to allow handlers
to not have to take a long list of parameters. The
available bindings within a predicate or handler are:

  • workflow – the definition of the workflow
  • current-state – the current state
  • next-state – the subsequen state (if transitioning)
  • context – the context

defmachine

This is used to define the workflow. It must be given a
keyword which names the workflow. States are defined
within the defmachine form.

States are declared with the state form. They must have a name
and a map of properties. The properties may contain: :start, :transitions or :stop. :start indicates that the state is a start state, where the workflow may be entered. :stop indicates that the state is terminal, and if reached the workflow should no longer transition.

NB: defmachine will create a package level var with the name of your
worklfow. In the example above it creates *door-workflow*. This var contains the definition of your workflow.

To support interactive development, if you type in a defmachine and attempt to evalutate it without defining the required predicates, Impresario will include a simple template for the predicate as part of the error message so that you may cut and paste it into your editor as a baseline.

register-workflow

This funtion adds your workflow into the Impresario registry. It takes the name of the workflow (as a keyword) and the workflow definition.

defpredicate

This is used to define a function in your package that will act as a transition predicate between two states.

on-enter-any!

This declares a trigger that is fired any time a state is entered. Note that the body of this handler must return the context.

on-enter!

Given a state name and a body, this will be the handler called
when the workflow transitions into the given state.

on-exit!

Given a state name and a body, this will be the handler called
when the workflow transitions out of the given state.

on-transition-any!

The body of this form will be called any time any transition
is made. *current-state and *next-state* will be set
to the names of the states involved in the transition.

on-transition!

Given a from state, a to state and a body, will invoke the body
whenever the specified edge is followed in the workflow.

Installation

Leiningen

  [com.github.kyleburton/impresario "1.0.12"]

Maven

<dependency>
  <groupId>com.github.kyleburton</groupId>
  <artifactId>impresario</artifactId>
  <version>1.0.12</version>
</dependency>

Future Direction

Workflow Versioning

Introduce a second form of register-workflow that accepts a version. Make the default (when unspecified) “1.0.0”.

global exception state

A declared state, with possible transition’s out, but no explicit edges leading in. If any predicate or trigger throws an exception, this state is entered. This gives the exception state the ability to handle errors and transition to one of the other states – or abort the workflow completely (by re-raising the exception).

Timeouts

Each node should have the ability to declare a particular edge to be followed after some amount of time has elapsed.

Workflows should support a ‘global’ timeout. If the workflow has not transitioned (or been woken up) in the declared time span, it should cause an exception within the workflow. This allows workflow instances to be expired, discarded, etc.

Something went wrong with that request. Please try again.