flor workflow engine

flor is a workflow engine. It's written in Ruby.

A workflow engine is a host for workflow executions, business processes that might last from a few minutes to multiple years.

Such an engine takes as input workflow definitions and an initial set of data and controls the execution "flow" until success or failure.

tl&dr

Look at flor's readme, at its one Ruby file doc/quickstart0, or at its more frameworky complex doc/quickstart1.

workflow definitions

At its core, flor is an interpreter for the flor workflow language. The "work" in "workflow" is assumed by taskers. Flor and its language deal with the control flow between taskers.

Flor doesn't use a Ruby DSL or a BPMN diagram to represent its workflows. The flor language is a programming language dedicated to workflows. It has its advantages and disadvantages, as with all pieces of software, we shall hopefully iterate towards a language that covers a large part of its community needs.

Here is an example reporting workflow, it involves five human participants and two automated taskers:

alice 'prepare feedback forms'
concurrence
  eric 'gather customer feedback' timeout: '8d'
  fred 'gather customer feedback' timeout: '8d'
  greg 'gather customer feedback' timeout: '8d'
anonymize_feedback _
loop
  alice 'prepare report'
  brent 'validate report'
  break _ if f.valid
submit_report _
brent 'close report'

This example of workflow definition routes work from Alice, fans out to Eric, Fred, and Greg, giving them each 8 days to gather customer feedback. Once all have replied (or 8 days have elapsed), the feedback is anonymized. A back and forth between Alice and Brent is started, until Brent deems the report ready for submission (by setting the field valid to true). Finally Brent closes the report.

The flow definition weaves in calls to taskers, moving tasks from one to the next.

Any number of workflow executions can coexist in a flor instance. A workflow execution in a given version can coexist with a workflow execution following the same definition or any other definition. Executions following the same definition but in different versions can coexist.

taskers

A tasker is a piece of Ruby code that is called to perform a task. Here is the code behing the anonymize_feedback _ above:

class Acme::AnonymizeFeedbackTasker < Flor::BasicTasker

  def task

    Acme::Models::Forms   #
      .where(exid: exid)  #
      .each(&:anonymize)  # access DB (Sequel style) and anonymize each
      .each(&:save)       # form related to this execution (exid)

    reply                 # hand back task to flor
  end
end

The flor language and the taskers try to be orthogonal. Taskers should be easy to test on their own and be usable from multiple workflow definitions.

When flor is set up with a HashLoader, it's fairly easy to directly bind tasker implementations to the names appearing in the workflow definition:

FLOR = Flor::Unit.new(
  loader: Flor::HashLoader,
  sto_uri: 'postgres://flor:xxx@localhost/flor')

%w[ alice eric fred greg brent ].each do |user|
  FLOR.add_tasker(user, Acme::HumanTasker)
end
FLOR.add_tasker(:anonymize_feedback, Acme::AnonymizeFeedbackTasker)
FLOR.add_tasker(:submit_report, Acme::SubmitReportTasker)

Human taskers are usually placing the incoming task into a worklist, let users edit it and then, when they're done with it, return the task to flor. There is the florist project providing a vanilla flor worklist, but it's work in progress.

launching

In the case of a web application, the launch of a new workflow execution usually happens in a controller, here is an example Sinatra POST endpoint:

post '/xhr/reports/' do

  exid = FLOR.launch(
    REPORT_FLOW,
    payload: prepare_payload(request))

  content_type 'application/json'
  { exid: exid }.to_json
end

Behind the scene, that simply puts a launch message in flor's database and returns quickly the exid for the new workflow execution.

two kinds of configuration

More advanced flor deployments might favour the configuration tree approach demonstrated in flor/quickstart1/. Taskers and flows are placed in a tree hierarchy mimicking an organization's domain and subdomains. (What stands behind a subflow or a tasker might thus depend in which (sub-)domain the flow is launched).

The example in this post is based on the straightforward flor/quickstart0/ which spans a simple Ruby file.

two kinds of setup

The simplest way to deploy flor is within the web application it serves, in the same Ruby process. That's what I use in production, thanks to the wonderful JRuby.

A more classical setup is to have 1 or more Ruby processes dedicated to the web application and 1 or more Ruby processes for flor, a worker process. That's totally fine. The flor unit in the web processes can be instantiated without being started, while the flor unit in the worker processes is started. The flor units share the same database and communicate via it. (See doc/multi_instance.md).

flor characteristics

Ruby might be deemed a slow language, but it's fast enough when thinking of business processes spanning long periods of times.

flor features

over

If you have any question, please ask in the flor Gitter room or via the GitHub issues.