Actor Framework Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 

Creating a Sequencer in AF?

Solved!
Go to solution

@zsmorison wrote:

In the past, I've handled more complex sequencing that includes subsequences and branching logic by using the decorator pattern to create a "macro" object that inherits from the "sequence step" object.


That's along the lines of what I was thinking:  a "sequence" step that contains an ordered list of steps.  That's where the stack of sequences come in, with the top element in the stack being your current sequence, i.e. the one that serves up the next step.  If your next step is a sequence, that gets pushed onto the top of the stack.  You need to be able to hold your place in your calling sequence as you drill down into the nested sequence.

 

A loop is a repeating sequence.  A branch picks one of a set of steps based on a condition (with "sequence" being an allowed step type).

0 Kudos
Message 11 of 26
(1,978 Views)

In a couple of durability testers, I implemented a sequence engine as a sibling to other <physical entity> actors. Step pass/fail was not needed, but the client did need to know a step's progress. (Some steps would take tens of minutes - Chilling down to freezing conditions, Vacuum, etc.)

 

The engine used CSV-based text, where each line defined <Target Actor>, <Action Needed>, <Comma-Separated Parameters>. The engine simply sent a step line to its caller Actor, which would parse the first field <Target Actor> and forward the rest of the line to that target's enqueuer via a runStep message. It was the responsibility of each target actor to run the step accordingly and respond with either stepDone, stepError, or stepTimedOut.

 

The sequence engine also had the ability to accept UI requests - pause, resume (variants here - resumeFromBeginningOfStep, resumeFromPausedPoint, resumeFromAnotherStep), abort (variants - userTerminated, abortOnError, eStopped), etc. For looping, I ended up hacking a dictionary, to store the number of iterations done, and any nested loops thereof. Some steps were device independent, e.g. System, Wait, 60 (would simply make the system remain in its prevailing condition for 60 seconds).

 

Ideally, I would have followed the designs you all have written here, which, IMHO, very closely resembles Kent Beck's sUnit Framework.

 

What sequence storage format have you folks used? I would imagine some hierarchical format (YAML, JSON, XML etc.) would have been better suited than my CSV approach. Also, if feasible, how would one validate the file upon loading into the application? (Apart from typographical issues, I am more concerned about the possibility of a mismatch between the physical configuration of the system, and the sequence file contents.)

0 Kudos
Message 12 of 26
(1,962 Views)

@Dhakkan wrote:

The engine used CSV-based text, where each line defined <Target Actor>, <Action Needed>, <Comma-Separated Parameters>.

 This seems like a good approach to make it easy to modify and update sequences, since text is very cheap to modify. 

 


@Dhakkan wrote:

how would one validate the file upon loading into the application? (Apart from typographical issues, I am more concerned about the possibility of a mismatch between the physical configuration of the system, and the sequence file contents.)


To solve the issue with validating the text sequence, the first thought I had was to implement a parser as a factory, so it would take in the text, and then build the step objects to represent the sequence and validate them.

Systems Engineer
SISU
0 Kudos
Message 13 of 26
(1,933 Views)

@Nathan-P wrote:
This seems like a good approach to make it easy to modify and update sequences, since text is very cheap to modify. 

Yes, it also helped that the clients were willing Test Engineers to take on the risk of human error for editing such files.

 


@Nathan-P wrote:

To solve the issue with validating the text sequence, the first thought I had was to implement a parser as a factory, so it would take in the text, and then build the step objects to represent the sequence and validate them.

If I'm following your train of thought, I tried that with a standard QMH approach (before embracing AF) and gave up after getting confused with how to implement inter-module communication. Now, upon revisiting your thought, I can see how one could 'build' the steps with a sequence of Self-Addressed Messages. The enqueuer for each SAM would be the intended <Target>; and the <Action> with associated parameters could be the target's message. I would need a way to translate CSV to Message for each such Actor. Errors encountered when building this sequence of SAMs would result in a 'sequence not valid' error condition. Interesting...

 

Did I understand you correctly?

0 Kudos
Message 14 of 26
(1,890 Views)

Good discussion, but not going in the direction I was hoping for...  Let me try asking my question again, this time using actual code. Attached is my version of the Coffee Shop as an Actor Framework system. In this version, I have a whole sequence of messages that need to flow between the customer actor and coffee shop's actors. It's this messaging sequence that feels so much like spaghetti code.

 

Each message does work, then sends the next message expected in the exchange. Here is a summary:

Coffee Shop.png

 

 

So ultimately I'm trying to determine if there is a better way to implement this message interaction without coding it such that the next message in the sequence is sent directly from the payload of the previous message. 

 

 

A sequencer makes sense, but the difficulty is in how do the messages get/send the necessary data from one actor to the next if the steps are just locked in the sequence?  Could the customer actor be the sequencer in this example? Thoughts? 🙂

 

FYI, the coffee shop is modeled after the one described here: https://forums.ni.com/t5/Actor-Framework-Discussions/Asynchronous-Messaging-explained-as-a-coffee-sh... 

Systems Engineer
SISU
0 Kudos
Message 15 of 26
(1,875 Views)

Sounds like you're looking for a futures/promises framework for AF messages.

~ The wizard formerly known as DerrickB ~
Gradatim Ferociter
0 Kudos
Message 16 of 26
(1,863 Views)

@Nathan-P wrote:

A sequencer makes sense, but the difficulty is in how do the messages get/send the necessary data from one actor to the next if the steps are just locked in the sequence?  Could the customer actor be the sequencer in this example? Thoughts? 🙂

Do you wish to adhere to the Actor Tree messaging model?

0 Kudos
Message 17 of 26
(1,850 Views)
Solution
Accepted by topic author Nathan-P

@Nathan-P wrote:

 

FYI, the coffee shop is modeled after the one described here: https://forums.ni.com/t5/Actor-Framework-Discussions/Asynchronous-Messaging-explained-as-a-coffee-sh... 


Reading that original document I see the following quote:

"The coffee shop interaction is also a good example of a simple but common conversation pattern (see Figure 2). The interaction between two parties (customer and coffee shop) consists of a short synchronous interaction (ordering and paying) and a longer, asynchronous interaction (making and receiving the drink)."

 

What I think you are most missing is the use of synchronous interaction.  You are just using asynchronous interaction for everything, and that is contributing to you wanting some other technique to deal with the complexity of multi-step conversations done purely asynchronously.  Trying to add a sequencer design is just going to make things even more overly complicated.

 

The best (easiest, clearest) synchronous interaction is Request-and-wait-for-Reply.  I would suggest the ordering and paying parts be synchronous Request-Replies.

 

0 Kudos
Message 18 of 26
(1,827 Views)

Synchronous request/reply is one of those things they tell you to avoid at all costs when you first learn about AF because it's often a bad way to program Actors. In other words (IMHO), if you need a lot of synchronization, you should just use regular ol' objects. I think this is helpful to getting up to speed on AF, but in reality the synchronous request-reply is a very useful tool that's absolutely the right thing to do in some situations.

 

In my mind it's kind of like a flat sequence structure or a global variable. New users shouldn't use those as they're usually the wrong way to do simple things, but once you know why NOT to use them you can understand when TO use them (i.e., when functions don't contain built-in flow controls, and for WORM globals or storing language translation strings). It's not like a dynamic data wire, which really don't belong anywhere.

 

I agree with Dr. Powell in that this does seem like a good place for some synchronous interactions, since something like "Pay" cannot fail to return in a timely fashion. Either the customer gives money, or the customer doesn't have the money. Either way, the caller actor isn't stuck waiting for a reply very long. Same with "Order".

 

I don't think that will 100% resolve your problem of trying to de-spaghetti your sequence code, but it could help.

 

With this example specifically I don't know that you need a dedicated "sequencer". Each actor being a state machine will result in an "emergent" behavior of the Coffee Shop, despite never programming in an entire coffee shop anywhere.

 

For example, the Customer could have states of "Wants coffee", "Waiting on coffee", and "Has coffee". The Cashier doesn't need states, as all if its internal interactions are immediate and could be done synchronously. If you wanted to simulate delays, you could have states of "Ready to process order" and "Interacting with customer". The Barista would have states of "Making a drink" and "Waiting to make a drink". Shop doesn't really need states either as in your model it only forwards a drink order to a Customer.

 

The simple example of Barista only has one incoming message, "Order Drink". If it's in the "Making a drink" state, then it doesn't deprocess incoming messages until it's done with the Drink, and sends a Drink Done message to Shop. (In fact, the States of Barista don't need to be explicit states- if the payload of Order contains a Customer ID or something, then the method "Make Drinks" could just wait for 30 seconds or whatever, then send Drink Done, at which point the Actor can receive the next message in its queue. The only potential issue here in "real world applications" is that long processes often need Abort functions, so you'd really want to clear the message queue very quickly and have a helper loop that's "making drinks" so an Abort message will actually get received.)

 

So to sum up my rambling: no, this code is not "self documenting", but if you break it down into "Customer" states, "Cashier" states, etc, then each of those is much easier to document. Perhaps a better way to depict it would be state diagrams with arrows pointing between them with the various messages. In other words, instead of starting with a sequence and saying "How do I program all of this?", you define each of your components behaviors such that it results in the sequence you want. I guess, all in all, this example doesn't really scream "Sequencer" since ALL of the steps are so intermingled with each other. It seems (not exactly) like "behavior-based" programming.

0 Kudos
Message 19 of 26
(1,808 Views)

I usually make sequence steps individual actors, as this allows you to use some of the built in aspects of the actor framework for asynchronous flow. The sequencer actor launches a step, then goes idle until the step is complete, which it knows when it receives the handle last ack message. I generally use a sequence map/lookup table for sharing data between steps - the sequencer holds the map, and prior to launching each step the sequencer calls a load sequence variables method on the actor, where the map is shared with the actor that is about to be launched. Upon completion, I'll unpack the actor in handle last ack and then call a step cleanup method, which returns an array of strings and variants to be put back into the map for later steps to grab.

0 Kudos
Message 20 of 26
(1,797 Views)