Actors, Messages and Behaviors
A free video tutorial from Daniel Ciocîrlan
Software Engineer & Best-Selling Instructor
9 courses
136,386 students
Learn more from the full course
Akka Classic Essentials with Scala
Learn the classic Akka actor model with Scala and write parallel, concurrent and fault-tolerant systems
12:46:42 of on-demand video • Updated April 2024
create concurrent applications with actors instead of threads and locks
design fault-tolerant systems with supervision
configure Akka for various scenarios
confidently test actor systems
use Akka patterns and best practices
English [Auto]
All right. Welcome back. I'm Daniel. And in this video, we are going to see the actor principles straight in the code, even if we have only a vague intuition about what's going on. So don't worry about it. And later, we will explore how these principles solve our earlier frustrations with the thread model and learn how actors actually work under the hood. All right, so we're back to our IDE. And because we started a new section, let's go ahead and create a special package for this section only. So right click on the Scala package here, new package. And let's name this part two actors. And under the newly created Part two actors package, let's create a new Scala application. So right click New Scala class. Let's name this actors intro and let's make this an object. And extends up as usual. Cool. Now, every application will have to start with what is called an actor system. So this is the part one actor systems. The way that we create an actor system is the thing that we did in the playground when we first set up our project. So we would create something like an actor system and we would say actor system. So the apply method of the actor system class, just auto import it and let's give it a name. Let's call this first actor system. And if everything goes well, if we print line actor system dot name, we should be able to see the string to the console. So let's run this and make sure this runs fine. All right. So we have our first actor system. The actor system is a heavyweight data structure that controls a number of threads under the hood, which then allocates to running actors. We will see how actors work very shortly. Now, it's recommended to have one of these actor systems per application instance unless we have good reasons to create more. Okay. And also the name of this actor system has restrictions, so it must contain only alphanumeric characters, so normal characters and non leading hyphens or underscores. That's because actors can be located by their actor system and we cannot have, for example, spaces inside. If we try to do this with a space, this program will crash because we have our illegal name. Okay? Now that we have our actor system, we are ready to move to the more interesting part two, where we create actors within our actor system. So as we mentioned in the previous video, actors are kind of like humans talking to each other. So when you want to interact with another human, you send a message. For example, something like Where can I find the supermarket? And the other actor receives the message when they can hear it or when they are busy doing something else, okay, they think about it or they process your message and they may or may not reply back something like Go there and take the second ride and so on and so forth. Okay, So we now remember the main properties of this interaction. First of all, an actor is uniquely identified, much like humans are uniquely identified by their face or by their Social Security number and things like that. Okay. So an actor is uniquely identified within an actor system. Secondly, messages are passed and processed asynchronously. So you send the message when you need and the actors reply when they can. Third, each actor has a unique behavior or a unique way of processing the message. Thinking back to our human interaction, if that human who you're asking where the supermarket is doesn't like your face, they might not reply back or they might reply with an angry remark or something like that. Okay. And finally, you cannot ever invade the other actor or read their mind or otherwise force them to give you the information that you want. So having said that, let's create our first actor. Let's say our first actor counts words. So let's create our first word count actor. And the way we do that in Akka is by declaring a class let's call this word count actor. And this derives from the actor trait. So we will auto import actor and in order for this class to work, we need to define a method called receive. Notice that the ID wants to autocomplete this for us, but I'm going to write this by hand. So the method receives gets no arguments and its return type is a partial function from any two unit. And let's create a little case. Let's say we have a message, which is a string, and we split this message by spaces. So message split. By spaces. And this word count actor has an internal variable. Let's call this total words, which I initialize with zero. And this total words is incremented by the number of words in this message. So let's say total words plus equals message. Split space dot length. So what we have done here is the definition of an actor, which has some internal data and the behavior or the receive handler that Akka will invoke. So the way that we would communicate with this actor would be first to instantiate an actor and then send it a message, let's say, in case we receive anything else, let's print some unknown message to the console. So let's print word counter. And I cannot understand. Let's say message or MSG to string. Okay. So we would go to part three now where we would instantiate our actor. Now the difference between actors and normal objects in Akka is that you cannot instantiate an actor by calling new, but by using invoking the actor system. So the way that we do that is saying, Val, let's call this word counter equals and watch what I'm writing. I'm going to say actor system dot actor of and I'm going to pass in an object called props of type word count actor. Let's autocomplete this because we don't have it in our scope and then let's give it a name, word counter. So this is the way that we instantiate an actor. You cannot say Val word counter equals new word count actor. That doesn't work. Okay. In order to have a proper actor managed by Akka, you need to have a system and then call its actor of method and then pass in a props object typed with the actor type that you want to instantiate. The other parameter here on the actor of method is the name of your actor. It's often a good idea to name your actors in practice, and the name restrictions for the actors are the same as for the actor system. So you only can use alphanumeric characters and non-leading hyphens and underscores. Okay. And having done that, the result of this expression is an actor reference. So an actor reference is the data structure that Akka exposes to you as a programmer so that you cannot call or otherwise poke into the actual word count actor instance that Akka creates. You can only communicate to this actor via this actor reference. Okay. And speaking of which, we can go to part four now, which is actually communicating with our actor. So communicate. And the way that we communicate with an actor through its actor reference is by saying word counter. Exclamation mark. So this is the method that we use and then the message that we want to send. Say I am learning Akka and it's pretty -- cool. So the exclamation mark is the method that we are invoking. Of course, you know from the Scala basics that the exclamation mark is just the name of the method, so we can invoke it in a normal dot notation, but we often use the infix notation in practice. So let's see what happens if I run this application. So we have the name of the system, but we haven't actually printed out the result of this receive message. So let's go about printing something. So print line, let's say word count and let's create a message. Say I have received message. And let's control space to turn this into an interpolated string. And let's run this application again and we will see this little print line statement. See, which tells us that the actor has received this sentence. And because it has received the sentence, the total words has been incremented with the number of words in it. Now, it may not be apparent from the simple example, but sending the message here is completely asynchronous. So let's say I have another word counter here. And let's copy the actor of expression here, and let's call this another word counter and let's send another message to another word counter. Let's say a different message. Now let's rerun this application and see what happens. So we have here the second word counter. The other word counter receives the message. So the second message before the first word counter receives its own message. So Akka sends messages to actors asynchronously. So even in the small example, we see the actor principles in practice. First of all, actors are uniquely identified. There are no two actors with the same name under the same actor system. If I try to give the same name to both these actors, and if I run this application again, I'm going to get a very nice stack trace. So actor name word counter is not unique. So that's one. Secondly, actors are fully encapsulated. You cannot poke at their data. You cannot call their methods and you cannot even instantiate the actor classes by hand. So if I am very naive and try to instantiate a word count actor myself, and if I run this, I'm going to get another very nice stack trace saying actor initialization exception. You cannot create an instance of word count actor explicitly using the constructor new. Okay, so you cannot do that by hand. You have to use the actor of factory method in the actor system. So actors are actually fully encapsulated. And the only way that you can communicate with an actor is by sending messages via the exclamation method. The exclamation method is also known as tell. And every actor has internal data and behavior. And the behavior is this method receive, which returns an instance of partial function from any to unit. This is the type that Akka will use. This is also aliased by the type receive, which is exactly the same thing. Now, before I close this lecture, let me address a common question. And the question is how do we instantiate actors with constructor arguments? Because if we define plain classes, for example, person with a name which is a string, I can simply instantiate this class by saying new person with name Bob. But the question is, and the question that I often get is if this class person somehow extends actor and has some kind of receive method, okay, which we don't really care, we may leave unimplemented. The question is how do we instantiate an actor of type person with a constructor argument? Because if we look at the way that we instantiate actors before that was with actor system dot actor of and then the props and then the type of the actor. But the constructor arguments are not present at all. So how do we do that? The way that we instantiate such an actor is by using props with an argument and let me show you how to do that. So let's create a Val called person, which is actor system dot actor of and we can pass in props and instead of passing the actor type here, we can call the apply method of the props object and we can pass in an actual actor instance. So new person with a name Bob Now this instantiation with new inside the props apply method is legal, even though we cannot say new Bob from the outside. If you say new Bob from the props apply method you can. So if I run this application again, I should not see that red stack trace anymore. I'm seeing a different stack trace because an implementation is missing for my receive method. So let me add some cases here. Remember, receive is a partial function from any to unit. So let me add some cases here. So case, let's say this person replies to the message Hi. And just print something to the console. So print line. Hi, my name is Dollar name. Okay, so I just hit control space and the name was injected into this interpolated string. And in case I receive anything else, I will just not do anything. So if I rerun this application, I shouldn't even see this stack trace even. All right, let's send a message to this person actor just to make sure that the constructor argument was passed correctly. So I'm just going to say person exclamation mark, which is known as tell. So I'm going to say person tell hi and let's rerun this application and see that the person actor reacts to this message by printing its name to the console. So if we rerun. We see. Hi, my name is Bob, which is fine. So we created a person actor by passing it a props with an actor instance inside. This is legal, but this is also discouraged. I'm going to show you the best practice, the general best practice for creating props with actor instances, especially for these cases where you want to pass in constructor arguments is to declare a companion object. So we declare a companion object person and we define some methods that return props objects so we can say def props and we pass it a name. And this returns a props object with a new person with that name. This has the advantage that we do not create actor instances ourselves. But the factory method creates props with actor instances for us. So instead of props new person. Bob I'm just going to say person dot props with the name Bob. Of course, if I rerun this application, I'm going to see the exactly same thing. Okay, So hi, my name is Bob. So this is the best practice for creating actors with constructor arguments. Define a companion object and define a method that based on some arguments, it creates a props object with an actor instance with your constructor arguments. All right. So in this video, we saw the actor principals in actual code and we've got a taste of actor systems and actors. Let's move on to the next video and see how actors can send references to one another, how they can reply to messages and talk a little bit about message types and behaviors. I'm Daniel. See you there.