Prepare yourself for the longest message in the universe!

*The Problem:*

We've got two process definitions - a "batch" and an "application" process. 
 The batch process gets a new instance launched once per week, and any 
applications that come in during that week are supposed to be part of that 
"batch."  Processing of the batch only happens at the *end* of the given 
week, since we want to be sure the application submission period is closed 
before processing the batch.

Here are rough drafts of the processes, with some of the business 
requirements (that we're puzzled by) in comments:

Ruote.define 'batch_process' do
  change_status :status => 'open'
  # Wait until batch submission period is closed AND all applications have 
been reviewed
  admin :task => 'submit_batch_for_revew'
  # Cancel any hanging 'review_application' tasks in associated 
application_processes
  super_admin :task => 'review_batch'
  rewind :unless => '${f:batch_approved}'
  change_status :status => 'approved'
  # Now trigger the application processes to move on
end

Ruote.define 'application_process' do
  cursor do
    change_status :status => 'unreviewed'
    concurrence :wait_for => 'review_application' do
      admin :task => 'review_application', :tag => 'review_application'
      intern :task => 'collect_additional_documentation'
    end
    change_status :status => 'reviewed'
  end
  # At any time, the above can be rewound, triggered by
  # an explicit 're-review' action through the UI, *unless* the associated 
batch has
  # been submitted for review (see admin task in batch process).
  # If the associated batch has been reviewed but the batch was *not* 
approved,
  # which rewinds the batch process, then the 're-review' action becomes 
available
  # again.

  # Wait here until the batch is "approved"
  finance :task => 'charge_applicant', :if => '${f:approved}'
end

In other words, a weekly flow:

   1. A batch process is kicked off, and applications start coming in for 
   the week.
   2. The application_process is kicked off any time a new application is 
   received, and the application_process knows which batch it's connected to 
   (via a workitem field provided at launch).
   3. On each application, "admins" and "interns" can perform their tasks 
   immediately (these are just workitems in the storage participant that our 
   web app takes care of proceeding), and when both tasks are complete, the 
   application_process will wait for either 1) an admin to click the 
   "re-review" button in the UI, which rewinds the process and kicks off the 
   two tasks again, or 2) the batch to be submitted for review.
   4. When the batch's submission week is over *AND* all applications are 
   in the "reviewed" state, the "admin" gets a new task to submit the batch 
   for review.  Completing this task disables the "re-review" option for 
   applications, since the "super_admin" doesn't want anything to change in 
   the batch while they're completing their "review_batch" task.
   5. If the "super_admin" approves the batch, all application_processes 
   can now move on to the "finance" department's task of "charge_applicant" 
   (or, if they were not approved during the review process, they just 
   terminate).
   6. If, on the other hand, the "super_admin" rejects the batch, the 
   batch_process rewinds, and all application_processes can now be rewound 
   again (using the re-review button in the UI).  The logic here is that a 
   batch will be rejected if too many applications were approved during that 
   week, or too few.. so if the batch is rejected, some applications will have 
   to be re-reviewed and have a different result, before the admin resubmits 
   the batch for review.
   

Phew.  I know this is a hefty problem already, but I figured it's best to 
be wordy and try to explain the situation fully, than to be vague. :)

*Let's set up an example, using this week (2013-29-07 to 2013-04-08).*

   1. The batch opens on the 29th of July.
   2. Fifteen applications come in during that week, and the admins get 
   through reviewing about half of them by the 4th of August.
   3. The 4th of August arrives, and the batch closes for submissions, but 
   the "submit_batch_for_review" task is still not available, since there are 
   unreviewed applications.
   4. On the 5th of August, an admin discovers that one of the applications 
   that was already approved is actually invalid.  The admin clicks the 
   "re-review" button and rejects the application.
   5. On the 6th of August, the admins finally finish reviewing all the 
   applications, and the "submit_batch_for_review" task becomes available on 
   the batch process.
   6. An admin completes the "submit_batch_for_review" task (this is just 
   clicking a button in the UI).  At this point, the "re-review" button is no 
   longer visible on any application in the batch.  (Previous to this step, 
   any application could be re-reviewed just as in step 4).  If for some 
   reason any applications are still being reviewed (because a race condition 
   occurred where someone hit the "re-review" button in the UI after the batch 
   had already been submitted for review), those tasks should be cancelled 
   now, moving that application's process forward and changing the status back 
   to "reviewed."
   7. The super_admin starts the "review_batch" task, and finds that 12 
   applications were approved in this batch, but the company can only handle 
   10 this week.  The super_admin rejects the batch, letting the admins know 
   why.
   8. Since the 'batch_approved' workitem field is now false, the batch 
   process rewinds.  The "submit_batch_for_review" task is again available, 
   but the admins have been told not to submit the batch yet, until at least 
   two applications have been rejected.
   9. All applications in the batch can now be "re-reviewed."  Note that 
   this does not mean that they've all been rewound automatically - they're 
   still in the same state they were before - but now the admins can click a 
   "re-review" button to rewind the process and reject a previously approved 
   application (or approve a previously rejected one).
   10. The admins click the "re-review" button on two specific 
   applications, and this time they reject the applications.
   11. Repeat step 6.
   12. This time, the super_admin starts the "review_batch" task, and finds 
   that the batch is acceptable.  The super_admin approves the batch.
   13. The applications can now proceed to the finance task of 
   "charge_applicant," or, if their workitem field 'approved' is set to false, 
   they'll skip that expression and terminate.


So, as you can see, there's a lot of inter-process dependency going on 
here.  Previously to using Ruote, we'd been using Appian, which made it 
relatively easy to do some of the above things - you could send messages 
between processes with arbitrary data, and use that data to make decisions 
in the listening process.  For *many* reasons we've moved away from Appian 
and towards Ruote.

*The main problems are:*


   1. How do we trigger a "rewind" in the application process from the 
   outside, as in step 4 of the example above?
   2. How do we stop listening for a "rewind" trigger at a given point (as 
   in step 6 above)?
   3. How do we cancel an expression from the outside, as in step 6 above, 
   if we have a race condition?
   4. How do we then start listening for a "rewind" trigger again, as in 
   step 9 above?
   5. How do we pause the application processes until their batch is 
   actually approved, so we don't get to step 13 of the example too soon?
   

*Possible Solutions:*

There are a few solutions we've been toying with, to solve the above 
problems.  I'll tackle the problems individually:

1. Triggering a "rewind" from the outside.

  Our first thought was to have a completely separate process that 
basically does nothing but trigger a message for leaving a tag:

  Ruote.define 're_review_application' do
    noop :tag => 're_review_application'
  end

  We would launch this with a workitem field ("re_review_id") that holds an 
ID shared by the targeted application_process.  Then, in the application 
process:

  Ruote.define 'application_process' do
    cursor do
      change_status :status => 'unreviewed'
      concurrence :wait_for => 'review_application' do
        admin :task => 'review_application', :tag => 'review_application'
        intern :task => 'collect_additional_documentation'
      end
      change_status :status => 'reviewed'
      await :left_tag => 're_review_application', :global => true, :where 
=> '${re_review_id} == ${application_id}', :merge => ???
      rewind
    end
    # ...
  end

  (Obviously the process as written above would never continue, since it 
would always wait for a re-review and then rewind, but this is a focused 
example for the first problem.)

  This seems the obvious solution, and the most similar to what we've used 
in the past.  However, the biggest issue here is "passing data".  The await 
expression/attribute seems designed for merging or replacing workitems, 
rather than just containing an arbitrary payload of data that can be 
checked in the :where clause.  We obviously don't want to replace *or* 
merge the workitems, since they're from processes representing two 
completely different business entities, but we do want to be able to 
communicate some information from one process to the other.

  I'm wondering why ruote doesn't have something in the vein of 
"send/receive message" expressions?  Is this something that maybe doesn't 
belong in the core of Ruote, and should be implemented as a set of 
participants that use some kind of storage that can be shared between 
processes?

2. Stop listening to the external "rewind" trigger.

  After the "submit batch for review" task has been completed, we want to 
stop listening to a trigger from the 're_review_application' process.  Of 
course, we could try to "control" this in the UI, by hiding the button to 
launch that process, or something similar, but I'd rather ensure integrity 
in a more secure way.  So what if we add a tag to the 
"submit_batch_for_review" task expression, and wait for that tag in the 
application process?

  Ruote.define 'batch_process' do
    # ...
    admin :task => 'submit_batch_for_revew', :tag => 'batch_submitted'
    super_admin :task => 'review_batch'
    # ...
  end

  Ruote.define 'application_process' do
    cursor do
      # ... same as above, until await expression ...
      concurrence :count => 1 do
        sequence do
          await :left_tag => 're_review_application', :global => true, 
:where => '${re_review_id} == ${application_id}'
          rewind
        end
        await :left_tag => 'batch_submitted', :global => true, :where => 
'${batch_id} == ${batch_id}', :merge => ???
      end
    end
    # ...
  end

  Here we run into an even worse problem than above, since I have no idea 
how to check the incoming (event) workitem's batch_id (from the 
batch_process) against the batch_id in the application process.  Again, if 
we were able to broadcast an arbitrary message that await could listen for, 
with arbitrary data, this would work fine.. something like:

     broadcast_message :name => 'batch_submitted', :batch_id => 
'${batch_id}'

  paired with:

     await :message => 'batch_submitted', :where => '${m:batch_id} == 
${batch_id}', :global => true

  (where something like "m:" is used to reference variables on the message 
itself...)

3. Cancelling a message from the outside

  I imagine this would be as easy as fetching the workitem from the storage 
participant for this expression:

  admin :task => 'review_application', :tag => 'review_application'

  .. and calling Dashboard.cancel on it?  Since the parent concurrence 
continues after the review_application tag is left, will *cancelling* it do 
the same thing?  This is something I can test myself, of course, but 
haven't gotten around to yet.

4. Start listening for "re-review" rewind trigger again

  Not sure how to accomplish this, given the proposed solutions above.  I 
suppose if I tag the concurrence around the await expressions, like this:

  Ruote.define 'application_process' do
    cursor do
      # ... same as above, until await expression ...
      concurrence :count => 1, :tag => 'enable_re_review' do
        sequence do
          await :left_tag => 're_review_application', :global => true, 
:where => '${re_review_id} == ${application_id}'
          rewind
        end
        await :left_tag => 'batch_submitted', :global => true, :where => 
'${batch_id} == ${batch_id}', :merge => ???
      end
    end
    # ...
  end

  ... then I can add a "jump" command to jump back to that tag.  The jump 
command itself would need to be triggered by an "await" that's listening to 
the batch_process, so that when the batch_process gets rewound, the 
application_process jumps the cursor to the "enable_re_review" tag.  Still 
has the same problem of matching up the "batch_id", but not merging in any 
data from the event workitem.

5. Pause application_process until batch_process is approved

  Same potential solution, with same issue regarding receiving the data 
from the event workitem:

  Ruote.define 'batch_process' do
    # ...
    change_status :status => 'approved', :tag => 'batch_approved'
  end

  Ruote.define 'application_process' do
    cursor do
      # ...
    end
    # Wait here until the batch is "approved"
    await :left_tag => 'batch_approved', :global => true, :where => 
'${batch_id} == ${batch_id}', :merge => ???
    finance :task => 'charge_applicant', :if => '${f:approved}'
  end

*The Question:*
*
*
Any advice, before I start either a) implementing some sort of 
inter-process messaging via participants or b) trying to add that 
functionality into a fork of ruote? :)  Maybe I'm thinking about this 
wrong, and there's a more vanilla/standard approach to accomplishing 
something like this?



-- 
-- 
you received this message because you are subscribed to the "ruote users" group.
to post : send email to [email protected]
to unsubscribe : send email to [email protected]
more options : http://groups.google.com/group/openwferu-users?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"ruote" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to