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.