Every EIS is based on business processes, and it can be said that workflows are the computational realization of these processes. Therefore it is necessary to provide some workflow engine to EIS Patterns.
Basically, there are two types of workflow engines:
a) Associated to an application development platform: such as the ones found in Web application development environments, like Zope, or associated to databases, such as Oracle, or as part of Groupware environments. In this case, applications are developed "inside" the environment, using the engine's API.
b) Implemented as Libraries: using some programming language, a library of objects representing the workflow features is implemented, such as the State Machine as Python Decorators, or Ruby's AASM. In this case, you must use the libraries' objects to implement your engine.
Enters Fluidity & Extreme Fluidity
Fluidity was developed as a type (b) workflow engine, however, Extreme Fluidity turns it into a third type of workflow engine:
c) Inoculated & Expelled: using the dynamic nature of the underlying programming language, provide a way of making objects workflow-aware, and symmetrically, a way of turning these objects back to their initial structure when desired.
Why this? Because type (a) engines forces you to use a given environment to develop your applications, while type (b) forces you to use specific objects to implement workflows, most of times creating a mixed code with application specific and workflow specific statements.
With Extreme Fluidity (or xFluidity for short) it is possible to insert the code necessary to make your workflow run inside your business objects. In other words, they become workflow-aware objects, while keeping your programming style, standards, and patterns. In order to understand how this happens, let's check how Fluidity can be used when xFluidity is associated to it (the complete source code can be found here).
First, it is necessary to declare a template State Machine:
class LoanProcess(StateMachine):state('requested')state('request_created')state('request_analyzed')state('refusal_letter_sent')state('loan_created')state('value_transfered')initial_state = 'requested'transition(from_='requested', event='create_loan_request', to='request_created')transition(from_='request_created', event='analyst_select_request', to='request_analyzed')transition(from_='request_analyzed', event='loan_refused', to='refusal_letter_sent')transition(from_='request_analyzed', event='loan_accepted', to='loan_created')transition(from_='loan_created', event='time_to_transfer_value', to='value_transfered')
Using xFluidity, the template is injected in another object - in this case a Process:
def setUp(self):self.process = Process()template = LoanProcess()configurator = StateMachineConfigurator(template)configurator.configure(self.process)
Magically, the process object starts to respond to the state machine's events:
def it_makes_the_process_respond_to_the_example_state_machine_events(self):self.process |should| respond_to('create_loan_request')self.process |should| respond_to('analyst_select_request')self.process |should| respond_to('loan_refused')self.process |should| respond_to('loan_accepted')self.process |should| respond_to('time_to_transfer_value')
As expected, the engine avoids the firing of transitions at wrong situations:
In that way, it is possible to use a workflow engine in your own development environment and using your own models. Fluidity also provides guards and state actions, not shown here for the sake of simplicity.def it_runs_the_example_acceptance_path(self):self.process.create_loan_request()self.process.current_state() |should| equal_to('request_created')# the line below means that if the process tries to fire loan_refused# when at 'request_created' state, an exception is generatedself.process.loan_refused |should| throw(InvalidTransition)self.process.analyst_select_request()self.process.current_state() |should| equal_to('request_analyzed')#loan acceptedself.process.loan_accepted()self.process.current_state() |should| equal_to('loan_created')self.process.time_to_transfer_value()self.process.current_state() |should| equal_to('value_transfered')
A Note on Workflow Notations
Fluidity is a state-based engine, which on those days of fancy business process notations may appear a bit old-fashioned. However, state machines represent a well known and easy to understand notation. EIS Patterns' Process and Movements objects are being developed in a way that they don't need to know which notation is in use, at least at the unit level - meaning that some code specific to a given notation may be used to "glue" elements at the application level.
Currently Fluidity and xFluidity are works in progress, including the expelling capabilities and some details on implementation of guards. When these features become fully functional, a Petri Net based engine will be developed. A clear limitation of Fluidity is that it only works for Python objects, but the necessity of developing in a given programming language is a limitation of all workflow engines, although some provide XML-like APIs.