processi

about processes and engines

ruote and switch

A programming language usually has some kind of super “if”, a switch statement.

Ruote is nothing more that an interpreter, a very patient one, one that can get stopped and restarted. (it also runs multiple process instances concurrently). Since it interprets some kind of high level business process gibberish, a switch statement is nice to have.

This post explores switch statement implementations for ruote, it starts with solutions for ruote 2.1.x and then shows solutions that use two new expressions in the upcoming ruote 2.1.12.

 

before ruote 2.1.12

For ruote [2.1], I wasn’t sure if a switch statement was explicitely needed. With a workitem field or a process variable, it’s easy to switch to a given subprocess.

Suppose we have an item pickup process. The system is onboard, the drivers leaves the depot knowing his immediate pickup point, but not the next, as the system asks after each pickup what to do (via the ‘get_next_task’ subprocess).

Ruote.process_definition do

  define 'coast_pickup' do
    # ...
  end
  define 'mountain_pickup' do
    # ...
  end
  define 'get_back_to_depot' do
    # ...
  end

  sequence do # body of the process

    cursor do
      subprocess 'get_next_task'
      subprocess '${next_task}'
      rewind :unless => '${next_task} == get_back_to_depot'
        # cursor rewinds unless next task is getting back to depot
    end
  end
end

All is well, our switch occurs at “subprocess ‘${next_task}'”. Note that if we wished to, we could use ref instead of subprocess if we wanted to point to participants and/or subprocesses.

But wait… Those subprocesses are defined for the whole process, they are not limited to the switch.

We can go a bit further and isolate the switch and its cases into its own subprocess.

Ruote.process_definition do

  define 'perform_next_task' do

    define 'coast_pickup' do
      # ...
    end
    define 'mountain_pickup' do
      # ...
    end
    define 'get_back_to_depot' do
      # ...
    end

    subprocess '${next_task}'
  end

  sequence do # body of the process

    cursor do
      subprocess 'check_tasks'
      subprocess 'perform_next_task'
      rewind :unless => '${next_task} == get_back_to_depot'
    end
  end
end

This is nice, we have a ‘perform_next_task’ subprocess wrapping the cases, the body of that subprocess calls the right case given the value of the “next_task” workitem field. If there is another subprocess named ‘mountain_pickup’ in the same process definition, the one in the switch subprocess will only shadow it within the case, in other words, the scope of the cases is limited to the switch subprocess.

But, you’ll say, with a real programming language not some toy process definition language, the switch and its cases are all wrapped neatly inline, they are not set apart.

Placing the switch and its cases in the main flow is OK, but it binds subprocesses… It can override subprocesses with the same name.

    cursor do
      subprocess 'check_tasks'
      sequence do
        define 'coast_pickup' do
          # ...
        end
        define 'mountain_pickup' do
          # ...
        end
        define 'get_back_to_depot' do
          # ...
        end
        subprocess '${next_task}'
      end
      rewind :unless => '${next_task} == get_back_to_depot'
    end

We need to look at the next version of ruote to solve that issue.

 

with ruote 2.1.12

For the upcoming ruote 2.1.12, I introduced a let expression :

    cursor do
      subprocess 'check_tasks'
      let do # let's have a new scope, just for our cases
        define 'coast_pickup' do
          # ...
        end
        define 'mountain_pickup' do
          # ...
        end
        define 'get_back_to_depot' do
          # ...
        end
        subprocess '${next_task}'
      end
      rewind :unless => '${next_task} == get_back_to_depot'
    end

The ‘let’ introduces a new scope, where our case subprocesses can get defined without overrides ones with the same name outside of the let block.

Now, how about something that really looks like a switch statement ?

    cursor do
      subprocess 'check_tasks'
      given '${next_task}' do
        of 'coast_pickup' do
          # ...
        end
        of 'mountain_pickup' do
          # ...
        end
        of 'get_back_to_depot' do
          # ...
        end
      end
      rewind :unless => '${next_task} == get_back_to_depot'
    end

With some Ruby/Perl -like magic, all the pickups could get covered by the same case :

    cursor do
      subprocess 'check_tasks'
      given '${next_task}' do
        of /^.+_pickup$/ do
          # ...
        end
        of 'get_back_to_depot' do
          # ...
        end
      end
      rewind :unless => '${next_task} == get_back_to_depot'
    end

The upcoming ruote 2.1.12 has this given expression. Look at its description, it not only covers “given an x of” scenarii but also “given that” ones.

 

conclusion

I still like the first version, it’s vanilla ruote “trigger the subprocess whose name is found in workitem field x”, it’s nice to have subprocesses that can be used as cases or called from other parts of the process definition.

Calling subprocesses doesn’t limit us to processes bound within the same process, processes bound at the engine level (engine variables) or external processes (given by their URI) are callable as well (see the subprocess expression doc for more information).

The given expression is interesting because it covers “given an x of” and “given that” scenarii (see the doc). It also has a “default” part which the first version doesn’t have.

Try to write processes that are concise and that read like english. And test them.

 

Written by John Mettraux

January 3, 2011 at 7:08 am

Posted in bpm, ruby, ruote, workflow