I think that this unfamiliarity and intimidation is at least somewhat unfounded because I know that many, even most programmers, have used a paradigm that is very much like the Actor Model. I want to explore familiarities with what you have done before and draw attention to the common ground. Let me explain.
GUIs Use Messaging and Actors
I think it is safe to assume that nearly everyone reading this has done some programming on a graphical user interface, or GUI. Perhaps your GUI experience dates back to the days of the original Windows API or the X Window System. If not, you’ve probably done some work with Swing or SWT or even the Javascript DOM on browsers. They all have similar concepts and programming models, but let’s take the Windows API as an example.
The Windows API features a message loop that dispatches queued GUI interaction messages, such as keyboard key presses and mouse clicks, to the code that backs the windows on which the events happened. For example, when a user clicks the mouse button on a window, that mouse click event is dispatched to the code registered as the message handler for that window. That’s both event-driven and message-driven programming.
Now think of each window as an actor. What is an actor? Actors are objects, similar to those you use with object-oriented languages. However, actors don’t accept direct method invocations. Rather, to request some behavior of an actor you must send it a message. Messages are delivered to actors asynchronously. You can get more detail about actors from a few other blog posts, here and here.
So, if GUI windows are actors, the actors on the display are receiving messages about user gestures that may be useful for carrying out application behavior.
You may be wondering why I chose to use the Windows API as an example rather than a more modern GUI API. Fact is, the Windows API is still relevant. However, my reason is that I want to specifically draw your attention to the long existence of this programming model. The very concepts of the Actor Model have been used prevalently for a long time.
In C each window’s registered message handler looks something like the following.
WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: ... case WM_SIZE: ... case WM_LBUTTONDOWN: ... case WM_LBUTTONUP: ... } }
I won’t spend much time trying to explain the details of the Windows API, or the now dreaded Hungarian notation for types and variables (and note that I left out the confusing return value). Just understand that the hwnd is the handle (an unsigned int) to the window receiving the message, and uMsg (an unsigned int) is the message type. Each uMsg can have two parameters, one being a wParam (word or int) and one being an lParam (a long that can be used as a pointer to some data structure). Consider the hwnd just like an ID or pointer to the window, which can be used to send messages to yourself or include in the handle in messages sent to other windows.
Above, the window is prepared to handle a WM_CREATE message, where Windows is telling the window to do things related to being created, such as creating its child windows, for example. This window can also react to being resized by the user because it handles the WM_SIZE message. And this window can detect mouse button clicks by handling WM_LBUTTONDOWN and WM_LBUTTONUP. Just implement the C code to handle each of these messages and you have a window that is, drum roll… reactive.
Wait a minute! Am I trying to tell you that you that you have probably been doing reactive programming for-like-ever? Yes, I am.
So, don’t be intimidated by the Actor Model. Actors are just that simple. You don’t believe me? Below I provide some Erlang code that would do the same (assuming Erlang had a native protocol for the Windows API) as the above C code. Why Erlang? Because Erlang was the first real actor-based programming language that was viable for industrial use, and its approach to the Actor Model has been used by modern actor frameworks.
process(State = #state{}) -> receive {Sender, Message = #Create{}} -> ... {Sender, Message = #Resize{}} -> ... {Sender, Message = #LeftButtonDown{}} -> ... {Sender, Message = #LeftButtonUp{}} -> ... end.
Here the actor—actually called a process in Erlang—receives messages in its process() function. The process() function runs each time that a new Message is received. Again, I won’t try to explain the Erlang code, such as pattern matching, but you can see that the Erlang code maps to the C code without the need for much Erlang knowledge.
That’s pretty interesting, isn’t it? The Windows API looks and works very much like an Erlang process/actor.
Now consider the same problem above implemented in Java with vlingo/actors. Again, this assumes that there is a protocol for the Windows API for Java and vlingo/actors. There is not, but if so the actors would work as follows.
public class WindowActor extends Actor implements Window { public Result create(Specification spec) { ... } public Result resize(Size size) { ... } public Result leftButtonDown(Mouse mouse) { ... } public Result leftButtonUp(Mouse mouse) { ... } }
Again, it’s just that simple. What’s more, you have familiar type-safe Java code that looks just like what you have been working with.
But what about the asynchrony of the Actor Model? Doesn’t that make adopting it more complex? Here are a few facts to consider.
- The Actor Model is asynchronous, but every actor behaves like a synchronous object when handling a message. Inside the actor you have undisturbed state that you own, and you are handling only one message at a time. There are no races. Inside the actor you have a very simple world to deal with, the business of being that actor’s protocol.
- The Windows environment, and X Window, and whatever GUI environment you work in, is fully asynchronous. Your window reacts to messages as they are received. It knows little about the outside world and it manages its own state undisturbed, dealing with one message at at time. It works like an actor, and an actor works like that.
Let it sink in. Now, why are you intimidated by the Actor Model? You’ve been doing this same kind of programming for years, possibly decades.
Mediators
Now it’s time to go a bit deeper into understanding actors. I am going to introduce you to some actors that collaborate together to solve an interesting problem. At the center of this problem is the use of the GoF Mediator pattern.
A Mediator is meant to decouple components of various kinds that have differing concerns. The classic example used by the GoF book explains the Mediator pattern in terms of a GUI with multiple visual components. As I go through my example of the same, think of each visual component as an actor.
- There is a DialogBox component. The DialogBox has four child components: TextBox; Listbox; Ok Button; Cancel Button.
- When the DialogBox initially becomes visible, it has: a blank Textbox; a Listbox with unselected items; a disabled Ok Button; and an enabled Cancel button.
- The Ok Button will remain disabled until there is a valid entry in the Textbox.
- The user may enter a value directly into the Textbox, or select an item in the Listbox, and that value will be placed in the Textbox. In either case, the Ok button becomes enabled.
- The user may now click Ok to cause the value in the Textbox to be returned to the client of the dialog box.
- The user may also double-click an item in the Listbox. This gesture causes the dialog box to respond as if the user had single clicked the Listbox item and then clicked Ok.
So how would this work? Obviously there must be some special behavior around getting the various visual components to work as expected inside the DialogBox. One option is to make each field-type component a special type for the given problem being solved.
So, for example, the Listbox has to know about the Textbox, and the Textbox has to know about the Ok Button. When the Listbox detects a selection, it would tell the Textbox to accept the selected item text. The Textbox would then tell the Ok button to enable. However, what if the Listbox received an item double-click? Should the Listbox also have a reference to the Ok Button and tell the Ok Button to click? Or should the Listbox pass a flag to the Textbox to tell it that it received a double-click selection, and let the Textbox tell the Ok to click, concluding the DialogBox experience?
SEE ALSO: Discover, configure, and manage your microservices with Alibaba’s new project, Nacos
Phew! I think you would agree that this is getting pretty messy! What can be done? Now’s the time to apply the Mediator pattern. This pattern calls for one of the components in the solution to serve as a Mediator between all the other components. This means that one of the visual components must know about all the others, but all the others know very little about the Mediator, and nothing about any sibling components in the mix. It seems like a perfect solution, just what is need. Consider this.
- The DialogBox already knows about all of its child components. After all, when it receives its create() message it has the opportunity to create each child and perform a desirable layout on its surface.
- When each of the visual child components are created, they are given a reference to their parent window. The parent window is, of course, the DialogBox, but the child components don’t know that. The child components only know that they have a parent window and a reference to it.
- It is the DialogBox that serves as the Mediator. As the user performs various gestures on the child UI components, those child components report to their parent, the DialogBox, what has happened. The DialogBox then manages the collaboration of the various components by mediating a message from one child component as a behavior on another child component.
The Mediator pattern thus provides a nice decoupling of each child component. Each child understands only what it needs to be, such as a Listbox or Textbox, and nothing more, other than reporting actions to its parent window. The children don’t even know that their parent is a DialogBox, only that it is a window. Let’s walk through a scenario.
- The DialogBox is created and it in turn creates its children. It is now in the state of point #3 above.
- The user selects an item in the Listbox, and the Listbox sends a message to it’s parent window indicating the selection.
- The DialogBox receives the message from its Listbox and sends a message to its child Textbox to make the selected item text its current content.
- The Textbox receives this message and sets its content to the text received in the message. It then sends a message to its parent indicating that its contents has changed.
- The DialogBox receives the message from its child Textbox indicating that its contents changed. The DialogBox then sends a message to the Ok Button informing it to enable.
- The user clicks Ok, and the Button sends a message to its parent window indicating that it was clicked.
- The DialogBox sends a message to its client indicating that text was successfully entered.
You can play through any number of the possibly scenarios, and as long as the DialogBox serves as a Mediator in the ways described here, the child components play together perfectly, and without knowledge of their siblings.
Now, if you continued to think of each of the components—DialogBox; Textbox; Listbox; Ok Button; Cancel Button—as actors, they might resemble something like the following.
Each circular object is an actor. The actors are loosely coupled, and the child actors know nothing about their siblings, only their parent. The parent DialogBox can communicate with each of its children, and each child can communicate with its parent. All communication is done with message sending.
Did you notice from the above scenarios that there was no confusion about components operating in an asynchronous messaging environment? These actors are unconcerned with when other actors do things, only that at some point certain events may happen. When those events do happen they are sent to the appropriate actor components and received as asynchronously delivered messages. The actor must only know how to react to messages that it receives and to send messages to one or more other component actors. Together this all makes for a very robust design that scales and performs well.
I have implemented an example of actors demonstrating the above messaging behavior using the vlingo/actors toolkit. I don’t provide all the example code here. You can find the full source on GitHub.
public class SchemaSelectionDialog extends DialogBoxComponent { ... public void createSelf(final Specification specification) { super.createSelf(specification); text = createChild(Textbox.with(self, …, "text")); list = createChild(Listbox.with(self, …, "list")); ok = createChild(Button.with(self, "Ok", …, false, "ok")); cancel = createChild(Button.with(self, "Cancel", …, "cancel")); if (specification.hasParameters()) { list.items(specification.parameter(0)); } } ... }
Above you see the SchemaSelectionDialog, which is a DialogBoxComponent. Don’t get too hung up on the details of the various classes. The names should make sense. Do assume that all major components, such as the dialog box, are actors. Being actors, they operate entirely asynchronously.
SEE ALSO: TIOBE Index July 2019: Perl hits its lowest popularity
When handling the createSelf() message the dialog creates its children, and all are actors. All of these child components are just as described above, a Textbox, a Listbox, and two Button controls. Note that the Ok Button is created with a false flag in one parameter, indicating that it is created disabled by default. Next consider the specific Mediator behavior. As you probably expected, every Window (yes, Window is a foundational protocol implemented by all GUI components) receives on(Event) messages. The dialog box receives these messages whenever anything happens to one of its child controls.
public void on(final Event event) { ... }
This feature could be nicer by breaking out each of the various Event types into separate messages, but perhaps you can take up that task to explore the Actor Model yourself. Here is a hint and documentation to accompany it.
public class SchemaSelectionDialog extends DialogBoxComponent implements TextboxObserver, ListboxObserver, ButtonObserver { ... }
When an item in the Listbox is selected, the dialog box receives an on(Event) message. As previously discussed, this enables the dialog box to mediate the expected behavior by placing the Listbox selected item text into the Textbox.
public void on(final Event event) { switch (event.type) { case "ItemSelected": handleList(event.typed()); break; case "TextChanged": handleText(event.typed()); break; case "Clicked": handleButton(event.typed()); break; ... } }
The ItemSelected Event type is sent by the Listbox to the parent dialog box when an item is selected. This switch-case delegates to handleList(ItemSelected) to mediate putting the selected item text into the Textbox, which is exactly what it does.
private void handleList(final ItemSelected selected) { text.content(selected.content); }
When the Textbox receives the content(String) message it takes the String for its content and then sends a on(TextChanged) message to its parent, the dialog box. You can see above the switch-case for that Event type, which delegates to handleText(TextChanged).
private void handleText(final TextChanged changed) { if (!changed.content.isEmpty()) { list.select(changed.content); value = changed.content; ok.enable(); } else { list.select(-1); value = ""; ok.disable(); } }
The handleText() method tests whether or not there is content. If there is text content then three things happen: an attempt is made to select the Listbox item matching the Textbox text, the text value is preserved in the dialog box state, and the Ok Button is enabled.
But wasn’t the selected Listbox text just placed in the Textbox? Well, we don’t know that for sure. The user may have typed text into the Textbox. Thus, the Listbox must ignore the select(String) message if the same item is already selected, otherwise there would be virtually endless redundant messages (not a good thing).
On the other hand, if the Textbox content is empty then three things must happen: the Listbox is sent a select(-1) message, which causes the deselection of any selected item, the value state is set to an empty String, and the Ok button is disabled.
When the on(Clicked) message is received the handleButton(Clicked) behavior is used.
private void handleButton(final Clicked clicked) { final Result result = clicked.tag.equals("ok") ? Result.SUCCEEDED : Result.CANCELLED; final WindowInfo info = selfWindowInfo(); info.parent.on(new Completed(self, info.id, info.tag, clicked.content, result, value)); }
The handleButton(Click) method is going to complete the dialog box, either as successful or cancelled. The Event is checked to see which one was clicked, Ok or Cancel, and the corresponding Result is used. Following this the dialog box sends the on(Completed) message to its parent window.
There are several more lessons to learn from this example, and I welcome you to take a look and experiment yourself. This approach provides familiar concepts to work from and I am certain you will benefit!
See the following helpful resources.
Code: https://github.com/vlingo
Documentation: docs.vlingo.ioThe open source reactive vlingo/platform code and documentation.
The post Actors Are Ok! appeared first on JAXenter.
Source : JAXenter