Actors using Scala & Akka — Part 1 : Introduction#
Since I am stuck at home due to Covid-19 pandemic. With nothing else to do, I thought the best use of time would be to continue my blog series about actors. In the last article, we saw what are actors, their properties and how these properties can be useful for writing concurrent and stateful applications. It was all theory. In this article, I will demonstrate how to write your first akka actor in scala using IntelliJ IDE. Hop on even if you don’t know scala.
This tutorial uses Scala 2.13. This is not a full tutorial on Scala. To learn complete Scala, I recommend Programming in Scala book.
IDE & Project Setup#
Scala development can be done in many IDEs. We will use IntelliJ with scala plugin.
Let’s create a new scala project
If you are creating a scala project for the first time, it can take a few minutes for the project to load.
Let’s open the
build.sbt file and see what’s inside
build.sbt is the file where we store most of build configurations such as modules, module dependencies and external dependencies, scala version, scala compiler flags, etc. Right now the file contains very minimal information as you can see in the screenshot. Since we will be using akka library for actors, let’s add that dependency. Dependencies can be added by adding an entry into
We are using akka version 2.6.5. You can find the latest version on github.
After you add the dependency, you need to reload the project so that sbt (scala build tool) can download the new dependencies. Download usually takes 10–20 seconds depending on your bandwidth and which library you are downloading.
We will need to reload the project every time we add or modify dependencies. We are now ready to write our code!
Let’s create a new package in
scr/main/scala/ directory and then a
Notice the little green triangle near Main object, you can click on it to run the Main program.
“Main” here, is an object and not a class. In scala, an
object is a “singleton instance” of a class created created for you automatically. We are now ready to write out first actor!
Running from CLI#
To compile and run program from CLI instead of IntelliJ, install sbt. SBT can be installed on macos, windows and linux. After installing sbt, run “sbt” command to enter sbt shell and then execute “run” command.
Running for the the first time, can take a while.
Our first actor : A bank account#
Our first actor will be a representing a bank account. We will begin very simple and keep adding custom requirements as we go on to demonstrate akka actor capabilities.
Our bank account has an account id and balance. Since account id can not change, only balance will be part of state and account id can be the “id” of the actor. The “behaviour” of actor will tree operations
To support these operations, our actor will need to accept 3 “types” of messages. Let’s create them:
We will store these in
BankAccountMessage.scala file in the same package.
Traits in scala are like interfaces in other languages with slight differences. Case classes in scala are used to hold or carry data to functions. They are immutable. Since
PrintBalance does not need any variable, it can be made case object thus making it singleton. We have just defined protocol of our actor. Protocol includes what an actor can accept and what it can return. This actor does not return anything for now. Let’s now define the behaviour.
The behaviour is description of what an actor does when a new message arrives. It’s easy to think of behaviour as a function. A function which takes a message as an input parameter and returns a new behaviour. Why a new behaviour? Well, state is also part of the behaviour, so if a message wants to make a change to state, then you need to return a new behaviour with modified state. We will understand this more in the demo below.
One of the many ways to create a behaviour is via
Behaviors.receiveMessage function. Since it create’s behaviour, let’s call it behaviour factory. It comes from
akka.actor.typed.scaladsl.Behaviors package. Let’s observe the signature of this behaviour factory.
It takes 1 type parameter which will enforce compile time type safety. The actor will only be able to receive given type of messages. The behaviour factory also accepts another parameter which is a function. Notice the function parameter notation in scala A=>B . I love this as it is very easy to read compared to
Func<A,B> in C# and creating a new interface in java.
Here’s the behaviour of our bank account actor
Some points for people who are new to scala:
- 'def' is used to define methods
- here we have used pattern matching over “message” and perform message type specific operation in each 'case'
This code, however is not idiomatic scala. We can make it more concise.
Scala allows reducing the code by allowing to remove
message => message match making it more concise.
Scala also allows much more powerful pattern matching where we don’t have to create variables for
withdraw. Instead, we can directly map variables to their fields.
If we get
Deposit message, we simply return a new
behaviour with added
amount as the new
balance (new state). We are essentially avoiding any variable mutations here by using recursion. Similar for
Withdraw message. If we get a
PrintBalance message, we print current balance and return the same behaviour with same state. We can also use
Behaviors.same instead of
Note that so far we have only described an actor using its protocol, behaviour and state. We still need to “spawn” the actor.
Spawning a new actor requires an actor system. Its like a runtime for all your actors within a JVM. In most cases you will not need more than one actor system in your entire application. Creating ActorSystem will require a guardian behaviour. With guardian behaviour, actor system creates a top level (system level) actor and you can send messages to the guardian actor which will then create child actors at user level. Akka recommends creating all business related actors at user level. This is because if a system actor crashes due an exception, entire actor system will crash destroying all other system and user actors.
In summary, a system actor is an actor spawned using actor system context and a user actor is an actor spawned using an actor context.
Actor system takes a guardian behaviour in the constructor and behaves like a system level actor. In other words, you can start sending messages to actor system and they will be handled by guardian actor.
Right now, for
guardianBehavior parameter, we can pass
behaviour(balance = 0) or
SpawnProtocol() is the recommended way because it creates hierarchy of actors and creates domain actors in user space. But get to it later and pass
Behaviours.empty for now.
Now using this actor system, we will to spawn an (system) actor using bank account behaviour.
Congratulations! 🥳 🎉 you have successfully created your first actor. Notice the type of
account1. It is
ActorRef[BankAccountMessage]. An actor ref is a pointer to an actual actor and all communication with actor has to be done via this actor ref. This is like the address of an actor. The actor also has a name and this name must be unique. In this case we have used account id for actor name. If you print string representation of account1 actor ref, you can see this name.
Since this actor ref is “typed” we can only send messages of type
BankAccountMessage to it. The syntax to send a message is
!. Let’s send some messages.
End of Part 1#
Here’s the entire
Main.scala contents. All the project code can be found on github.
In the next parts, we will do more things such as returning values from actors, spawn protocol, validations, async IO, failure handling, finite state machines, etc.