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.