TLDR; Complex rules in an Aware Process are hard for Aware to evaluate correctly,
So add more sub-Processes to simplify a "parent" Process,
At what overhead cost to start subprocesses?
(feel free to leave comments!)
This post came about due to discussions about Aware not being able to handle complex IF/THEN/ELSE Rules in a Process.
This post
and Mark Bailey here.
Basically, if the Rule will let you enter in 6 IF/THEN/ELSE blocks and it has an internal runtime error where it cannot produce the correct answer, then it's a bug. And I found a way to re-write my rule so that it did give the right answer, but at what (if any) performance penalty.
This entire post was my reply to a post here.
(which was offering a solution where we make MORE processes in Aware (we have to make plenty already, so I'm not a fan).
And it got me thinking about what goes on in Aware when we call Processes - I always wonder what [Negative] effect is calling a subProcess going have if I pass it 3 or 4 BOs - is it ok? Should I re-write or do something a different way? Should I even worry about it at all? Does it use more memory to pass several big BOs? etc etc
So I'm removing it from that post for discussion here - so there's an awareness of these internals for people to consider when designing your systems.
(explanation of this in the next post in this thread)
The Limitation in Aware of having only 1 IF/THEN/ELSE per Rule is a pain.
Example:
Desired
Rule1: IF State = FL THEN
..... bunch of stuff here
IF AccessLevel="Admin" THEN WriteFLORIDALog
...is not allowed IN a SINGLE RULE.
OK, so this isn't the end of the world. Not worth abandoning Aware for. I'm sure we can AGREE to settle on Option1 or Option2, right?
You could write it in a Single Rule like this:
Option1
IF State = FL and AccessLevel="Admin" THEN .... bunch of stuff here... WriteFLORIDALog
ELSE
IF State = FL THEN .... bunch of stuff here
Or, You have to break it into 2 rules in the Process:
Option2
Rule1: IF State = FL and AccessLevel="Admin" THEN .... bunch of stuff here... WriteFLORIDALog
Rule2: IF State = FL and AccessLevel<>"Admin" THEN .... bunch of stuff here
1st Discussion:
One main issue I have with both options is that I have physically copied my complex code 4 times in this process, instead of 1.
the "bunch of stuff here" could be this, for example:
FIND Contacts WHERE Contacts=TicketE.ps_Contact1
CREATE NP_Print_Header WITH NP_Print_Header.DeptID=RO.ps_Dept.ID
Calc_Contact_Print_Headers_Estimate
EXPORT DOCUMENT RO_Estimate TO FILE SystemSettings.DocumentPath+'RO'+RO.RONo+'\Est_'+TicketE.TicketNum+'_1.PDF'
CREATE OutgoingEmail_to_Multiple WITH OutgoingEmail_to_Multiple.pm_Recipients=TicketE.ps_Contact1,OutgoingEmail_to_Multiple.Type='E',OutgoingEmail_to_Multiple.State='Queue',OutgoingEmail_to_Multiple.ps_RO=RO,OutgoingEmail_to_Multiple.Message=UNDEFINED,OutgoingEmail_to_Multiple.Subject='Repair Order Estimate for RO # '+RO.RONo,OutgoingEmail_to_Multiple.Attachment1_Path=SystemSettings.DocumentPath+'RO'+RO.RONo+'\Est_'+TicketE.TicketNum+'_1.PDF',OutgoingEmail_to_Multiple.Link1=`/logonOp.aw?e=`+ENCRYPT_B64(`domain=AAA&userName=`+Contacts.LoginName+`&password=`+Contacts.Password_Clear_Temp+`&testingMode=false&firstCommand=startProcess2,Estimate_Approve_OR_Reject,TicketE,`+TicketE.ID+`,main`)
log2 Contacts.LoginName
IMPORT DOCUMENT OutgoingEmail_to_Multiple.Attachment1 FROM SystemSettings.DocumentPath+'RO'+RO.RONo+'\Est_'+TicketE.TicketNum+'_1.PDF'
And I've been using a simple "State = FL" - when really thats something more realistic like:
IF TicketE.ps_Contact1 IS DEFINED AND TicketE.ps_Corp.PORequiredYN='Yes' AND TicketE.sc_Status='NEW' and AccessLevel='Admin'
MODIFY this with a NOT(), an OR or 2 of these in Parens and it gets complex fast.
Point is, to do Option1 or Option2 (which we agreed are perfectly good alternatives, didn't we?) now requires me to copy this large block of code 4 times - which makes it VERY easy for me to screw something up - and hard to maintain because whatever I did in that code needs to be changed in 4 places when the boss wants a change.
--> No reason to argue that all that code would be taken into a subprocess - cause thats the purpose of this whole thread anyway. Just go with it.
2nd Discussion:
The alternative post (way up at the top) would have me restructure my Process to say:
IF STATE=FL then Process_1
ELSE
IF STATE=TX then Process_2
(of course, with my more realistic test like this:
IF AccessLevel='Admin' AND TicketE.ps_Contact1 IS DEFINED OR ( TicketE.ps_Corp.PORequired<>'Always' AND TicketE.sc_Status='NEW' )
it becomes more difficult to do this anyway.
Anyway, that what starts the more detailed analysis below where I examine whats happening "overhead-wise" when I call another process.
3rd Discussion:
Option2 isn't that bad, but if I have lots of those, then the absence of a BREAK function to stop further processing in this Process makes a LOT more Rules/Rule Expressions have to be unnecessarily evaluated (more below).
TLDR 2 - All of this because Aware can't handle a complex IF/THEN/ELSE rule.
The Desired way, if it worked, wouldn't have me duplicate ANY code.
... and the inability to "BREAK" out of a Rule doesn't help.
In my complex Process in this post, a BREAK would help once the TRUE "IF" branch was found, hopefully saving time by not having to execute further down the Process. When I simplified my Process to 3 separate Rules, the BREAK would exit and prevent further unnecessary tests once the condition was met. So It wouldn't always be all 3 rules executing. In fact, I'd put my "most common" condition first so the majority of the time Aware would never see the 2nd or 3rd Rule because the Break would [gracefully] end the Process.
And While I am used to having a Process with 1 or 2 lines in it (as was described by PointWell when I originally wrote this) and then calling the ".1" or ".2" sub-process to do more Logic, one thing I've been wondering about is the Overhead of Aware writing Context as it spawns other Processes.
ANSWER: In simple tests, I have not seen it "write Context" except when the Process is FIRST started (from a Form button in my explanation below) - So there is no "overhead" of writing the current context IN A PROCESS before that PROCESS calls a subprocess (not that I have seen yet, I may be wrong). When we "pass" a BO by Specifying that BO as Input in a Called Process, Aware re-reads that Record anyway - its not passing current values in Memory.
???eh?
So If I passed in CUST as Input to this top level Process, and I follow your Logic (referring to PointsWell's main argument in that thread) to make a simple test, and then call Process_1 or Process_2, CUST gets also passed to those Processes.
---> I wonder if another internal Context record has to be written to disk to serve as input for those Processes - Aware could have no idea the complexity of the destination Process, and certainly couldn't know this is a tiny process only acting as a "sub-process" to save on Logic complexity. Of course, we'd need an answer from Support OR an analysis of SQL Profiler to see if we can see this context being written....
OK, 2 hours later I'm back with results.
It turns out that that I still really don't know whats in the binary code written into EXECUTION_CONTEXTS. But its not what I thought it was. (BTW, mine is 27,222 characters of ASCII when pasted into an Editor, so its half that in Bytes being written & read.) It probably is some Record IDs (Like the Main BO needed for input to the SubProcess - but its NOT actually the data thats in that Main BO record. We think, from other programming languages, that when we say we "pass" the Cust record to the Process we are passing values IN MEMORY (Sure, it can be call by name, call by reference, call by value - skip that for now) - But thats not what Aware is doing. (I assume this, because if it was the data of a record(s), then why re-read the Main BO from the DB, why not just unpack it from the binary?)
From the trace below, I can see this:
1) Whenever I'm on a Form and I click a Panel Operation to start a Process to send this Customer an Email, for example, Aware 1st re-reads that record (SELECT *) and reads ALL "ps" and "ob" reference tables (and a few more to resolve shortcuts).
*** Just because you "see it" on the form and you think its "current"... its NOT.
2) It then Writes a context record - but it appears it doesn't need to sometimes... it sniffs ahead (somehow) and IF I removed the Display Message action, then it doesn't write context (I only tested this about 10 times cause I couldn't figure out why test2 wasn't Writing/Reading EXECUTION CONTEXTS - I could be wrong, but thats what 30 mins of testing and hair pulling determined.) .
3) The 1st thing the subtask does: Read the CONTEXT record (but not every time, see #2 above)
4) Then it reads the MAIN BO (Select *)
5) Then it [again] reads all "ps" and "ob" reference tables (and a few more to resolve shortcuts).
--- This is where Test1 ends
6) Test2 (identical to test1 up to this point) calls Test2B (Following PointsWell's example/suggestion in this thread) and guess what???
7) The called Process: Read the CONTEXT record, Read the MAIN BO (Select *), Then it [again] ...... Get the idea?
So this begs the question...
DO I REALLY NEED TO WORRY IF AWARE DOES 4 COMPARISONS or 12 - which are in Memory,
OR Do I want the Rules Engine to work a little easier while, IN MY CASE, I'm going to do 9-15 more database reads by calling a SubProcess ?
(9-15?.... the Main BO, the 8 reference tables, plus various shortcuts)
A Simple Process Example:
Test1 - does nothing, only a Display Message
Test2 (and subtask Test2b) - Test2 just calls Test2b (the "sub-process", which also has BO as input), then Display Message
1 Button on a Form of the "Main BO". Button is Start Process. The "Main BO" is input of the Processes
[attachment=0]Screen Shot 2020-01-30 at 10.06.21 PM.png[/attachment]
Profiler Results:]
--> My "Main BO" in these results is called RO (for Repair Order). I've omitted all the reference table reads. Sorry, this is hard to read. All my notes are in <brackets>. Most All else is direct from MSSQL Profiler.
TEST1 MSSQL Profiler
<I am on a Form>
<I press a Panel Operation calling Test1, with RO as Input>
<it appears the server reads the record thats needed by called Process (ie. the Record on the Form)>
exec sp_prepexec @p1 output,N'@P0 bigint',N'SELECT * FROM RO WHERE ID=@P0 ',12914 <-- this is the record ID
<8 more reference tables are read>
<save context>:
exec sp_prepexec @p1 output,N'@P0 bigint,@P1 varbinary(8000),@P2 varbinary(max),@P3 bigint,@P4 nvarchar(4000),@P5 bit',N'INSERT INTO EXECUTION_CONTEXTS VALUES(@P0,@P1,@P2,@P3,@P4,@P5)',8274,NULL,0x789CED7D07981C47957FCF748FB4DA55B6259B60DC5E39DBDA24C996A <snip>
<calls Process>
------- Now the Process starts -----------
<Test1 starts by reading Context passed to it>
exec sp_prepexec @p1 output,N'@P0 bigint',N'SELECT * FROM EXECUTION_CONTEXTS WHERE ID=@P0 ',8274 <-- record ID
<then it re-reads the main RO record that was passed to it>
exec sp_prepexec @p1 output,N'@P0 bigint',N'SELECT * FROM RO WHERE ID=@P0 ',12914
<8 more reference tables are read>
exec sp_prepexec @p1 output,N'@P0 bigint',N'DELETE FROM EXECUTION_CONTEXTS WHERE ID=@P0 ',8274
<nothing actually happens in this Process except DISPLAY MSG>
<end Test1>
-------- Leaves Process -----------
<it appears the main form reads Context back in>
<and main form refreshes - by reading main rec and the 8 reference tables>
exec sp_prepexec @p1 output,N'@P0 bigint',N'SELECT * FROM EXECUTION_CONTEXTS WHERE PGID=@P0 ',8274
exec sp_prepexec @p1 output,N'@P0 bigint',N'SELECT * FROM RO WHERE ID=@P0 ',12914
Read the main BO only 1 time actually in the process
TEST2 MSSQL Profiler
read & save
exec sp_prepexec @p1 output,N'@P0 bigint',N'SELECT * FROM RO WHERE ID=@P0 ',12829
exec sp_prepexec @p1 output,N'@P0 bigint,@P1 varbinary(8000),@P2 varbinary(max),@P3 bigint,@P4 nvarchar(4000),@P5 bit',N'INSERT INTO EXECUTION_CONTEXTS VALUES(@P0,@P1,@P2,@P3,@P4,@P5)',8546,NULL,0x789CED5D09981C4775EE99E991565A1D2BC9960DC4B8BDF2211FDA4B8 ... <snip>
---- process starts -----
read context, BO & reference tables
exec sp_prepexec @p1 output,N'@P0 bigint',N'SELECT * FROM EXECUTION_CONTEXTS WHERE ID=@P0 ',8546
exec sp_prepexec @p1 output,N'@P0 bigint',N'SELECT * FROM RO WHERE ID=@P0 ',12829
now a funky sequence where it Deletes context - then re-reads it (which doesn't make sense to me)
exec sp_prepexec @p1 output,N'@P0 bigint',N'DELETE FROM EXECUTION_CONTEXTS WHERE ID=@P0 ',8546
----- So I guess the SubProcess starts here -----
and repeats the read context, BO & reference tables
exec sp_prepexec @p1 output,N'@P0 bigint',N'SELECT * FROM EXECUTION_CONTEXTS WHERE PGID=@P0 ',8546
exec sp_prepexec @p1 output,N'@P0 bigint',N'SELECT * FROM RO WHERE ID=@P0 ',12829
----- process ends -----
Read the main BO 2 times, 1 in main process, 1 in sub-process
—> JaymerTip Context passing analysis