Actor Framework Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 

Best practice guidance: Sibling actor messaging

Solved!
Go to solution

Hello there,

In the situation in the diagram I have two sibling actors talking to each other through their parent. 

Would it be bad practice to give A the message enqueuer for B so they can talk to each other directly? Must they always communicate up and down the tree of actors?  If so is there a best practice way of naming/typing these multi-stage messages please?

Thank you for the help,

Martin

0 Kudos
Message 1 of 12
(9,213 Views)

Read this thread. All of it. It's worth the effort.

Message 2 of 12
(5,120 Views)
Solution
Accepted by MartinMcD

It is absolutely permissible to register two siblings to communicate directly with each other.  It complicates your communications network (you may find that *who* sent the message may suddenly be important), and limits your code reuse going forward (A and B must always be used together), but those costs may be worth the benefits of increased performance and eliminating unnecessary message classes.

It would certainly be appropriate for actors handling centralized error handling or logging, or a shared resource of some kind (a serial or GPIB instrument leaps to mind).

The framework prefers communication along the tree, but does not require it.

0 Kudos
Message 3 of 12
(5,120 Views)

MartinMcD wrote:

Would it be bad practice to give A the message enqueuer for B so they can talk to each other directly? Must they always communicate up and down the tree of actors?

Strictly speaking, no, messages do not always have to go up and down the hierarchy.  However, it is safer and easier to understand than a network of direct connections, so I think hierarchical messaging is preferred unless you have a very specific technical reasons for breaking it.  Consider this comparison of hierarchical messaging (HM) and direct messaging (DM):

HierarchicalMessaging.PNG

Suppose I'm a new developer joining the project and I have to learn how the system works.  With HM it is easy to break down the application and tackle a bit at a time.  I can grab classes B, C, and D and learn how they works as a complete subsystem.  Furthermore, because there is only a single way for the subsystem to interact with external code I can predict how it will behave when integrated into the overall system by looking at A.  Conversely, after a few DM links have been added it becomes more difficult to understand how the BCD subsystem behaves both as an independent component and when it is integrated into the app.

Well-designed applications restrict interactions between components to only those that are necessary.  I recently heard the term "sphere of influence" from David Staab.  I envision it as a series of concentric spheres centered on a given component and it can give you some insight into the amount of coupling you have.

Consider actor D.  In the HM design its tier 1 sphere includes B, its t2 sphere adds A and C, t3 adds E and t4 adds F.  In the DM design D's t1 sphere includes only B and E, but when you extend it to the t2 sphere you end up including the remainder of the application.  The additional messaging links have compressed the influence diagram and made it harder to understand how all the pieces interact to create the overall system functionality.

Sometimes it can be beneficial to send out of hierarchy messages.  How do you know when it's okay?

Broadly, I categorize messages as one of three types:

* Requests - These are messages from an owning actor to the sub actor, asking it to do something.  It could also be considered an instruction or command.

* Status - These are messages from a sub actor to the owning actor, alerting the owner that something just happened.  They are also called event messages.

* Data - These messages can go in either direction, and are used to transfer data from a data collecting actor to data processing, data display, or data storage actors.

As a rule, I always make sure request and status messages (jointly referred to as "control messages") are passed only within the messaging hierarchy.  If I'm moving a lot of data through the message tree and it threatens to delay processing the control messages, I'll set up dedicated data pipes between the data producer and data consumer.  The pipes do not send any sort of information other than data.  No "Hey I'm ready to receive data," "That's all the data I have," or anything like that.  Sometimes the data pipe will be typed to accept only pure data--such as an array of doubles--instead of generic messages.

Message 4 of 12
(5,120 Views)

Great, thank you all for the advice and taking the time to reply.

Martin

0 Kudos
Message 5 of 12
(5,120 Views)

Daklu wrote:

MartinMcD wrote:

Would it be bad practice to give A the message enqueuer for B so they can talk to each other directly? Must they always communicate up and down the tree of actors?

Well-designed applications restrict interactions between components to only those that are necessary. 

I've been trying to come up with a way of providing a "restricted interaction" that works not by number of connections but instead by limits on the connection itself.  Sort of by passing A the message enqueuer of B in a way that prescribes what messages A can send B.  These limits would be defined in the Caller of A and B, and thus would limit the complexity of understanding how the system works, as well as limiting what mischief a future developer of A could do.  My vauge ideas are either use Self-Addressed messages (where the Caller can set the address) or a child of Enqueuer that checks messages against a Caller-approved list before sending.  Any opinion on these ideas?

-- James

0 Kudos
Message 6 of 12
(5,120 Views)

drjdpowell wrote:

I've been trying to come up with a way of providing a "restricted interaction" that works not by number of connections but instead by limits on the connection itself.  Sort of by passing A the message enqueuer of B in a way that prescribes what messages A can send B.  These limits would be defined in the Caller of A and B, and thus would limit the complexity of understanding how the system works, as well as limiting what mischief a future developer of A could do.  My vauge ideas are either use Self-Addressed messages (where the Caller can set the address) or a child of Enqueuer that checks messages against a Caller-approved list before sending.  Any opinion on these ideas?

Hmm... interesting idea.  Having thought about it for all of three minutes I think it could be an improvement over completely open direct communication, but I don't think it would be as good as hierarchical messaging.  For me, the difficulty with figuring out a component's behavior in the context of the overall system seems to be directly related to the number of connections it has to other components.  (i.e. The number of components in its tier 1 sphere of influence.) 

Look at actor E in my diagram above. Even if B is restricted in what messages it can send to E, I still need to know the conditions under which B will and will not send those messages in order to understand the overall system behavior.  That means digging into its code.  Sometimes in order to get sufficient understanding of B I also need to dig into C and D.  That's a lot of extra code to examine.

Routing messages through A also gives A the opportunity to filter messages according to the state of the entire system.  For example, suppose the top component A has an additional subcomponent G.  Furthermore, suppose the system behavior requires the messages D sends to E should only be acted on when G is in a particular state.  There are two ways you can implement that with direct messaging:  You can either implement a new behavioral state in B (and D) which prevents sending the messages, or you can implement a new behavioral state in E that ignores the incoming messages.  Keeping in the spirit of direct messaging, G would need to notify B/D or E directly when its state changed, creating more connections.

With direct messaging you're pushing system level behavior down into the implementation of each component.  If D, taken in its own context, is a component that expects to send out a message under certain conditions, why should it implement a special state to not send out the message when the condition occurs?  Not sending the message doesn't really fit in the context of what D is supposed to do.

For a really bad metaphor, suppose I have an automobile simulator, with a Throttle actor whose sole job was to send out position reports of the accelerator pedal.  Ultimately the ThrottlePosition messages end up at the Engine actor, which uses the information to adjust the fuel flow.  As part of this simulator I've added a Breathalyzer instrument to periodically measure the driver's blood alcohol content, and if it exceeds a certain level I disable the accelerator.  (I don't want to stop the engine--the driver would lose power steering and brakes.)  If Throttle is sending messages directly to Engine we need to add a Disabled state to the throttle or an IgnoreThrottle state to the engine and all the associated messages, neither of which makes sense from the point of view of the individual component.  Assuming the Engine has a SetThrottle method, why would it also have an IgnoreThrottle method?

Capture.PNG

With hierarchical messaging, Throttle always sends out ThrottlePosition messages and Engine always responds to the SetThrottle messages, making the individual components simple to understand, implement, and reuse.  System level behavior--the decisions that require information from multiple components like what value to use for SetThrottle--is defined and centralized in the lowest common owner of the individual components.  In this case that's Car Sim.

Constrained direct messaging (a made up term for your idea) doesn't address the situation I run into most frequently; namely, the ability to define a system's behavior by controlling the message flow between components.  You could add the ability to update the "permitted messages" list in the queue at runtime, but it sounds like the complexity will outweigh any benefit.  Regardless, these are just my initial reactions and I'm open to being convinced.  I'm curious to see what you come up with.   

Message 7 of 12
(5,120 Views)

Daklu/drjdpowell: This goal of restricting the messages A can send to B is exactly what is achieved by Dmitry's work:

https://decibel.ni.com/content/thread/13739?start=0&tstart=0

0 Kudos
Message 8 of 12
(5,120 Views)

AristosQueue wrote:

Daklu/drjdpowell: This goal of restricting the messages A can send to B is exactly what is achieved by Dmitry's work:

https://decibel.ni.com/content/thread/13739?start=0&tstart=0

Ah! Beaten to it! If I hadn't had a client visit this morning I could have posted in time...

I've started pedantically grouping the messages in my current application as "Cmd/Req", "Status/Event" and "Data", too. The more I do it, the more tempted I am to create base classes of these types to enforce distinctions like Dmitry does. I've already created a Broadcasting library for Data messages that handles sub/pub/unsub logic...

I might try creating a utility that supersets the Message Maker to create messages of any "type", outbound (abstract) or inbound (concrete). I think that would be very useful.

0 Kudos
Message 9 of 12
(5,120 Views)

Same goal, different intended use case (unless I misunderstood Dmitry's desires.)

My understanding from that thread (which I admittedly stopped following) was that Dmitry wants to limit how messages travel up and down the tree.  For example, to make sure Engine doesn't send messages to Car Sim that should only be sent by Throttle.  James wants to use it to send messages across the tree, directly from Throttle to Engine.

I image the same technology can be used in both cases, though I'm inclined to think Dmitry's intended use is "more correct" (for lack of a better term) than James'.

0 Kudos
Message 10 of 12
(5,120 Views)