Actors using Scala & Akka — Part 1 : Introduction
A quick guide to get started with akka actors with scala for beginners
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.
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 `libraryDependencies` collection.
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 `Main` file
Notice the little green triangle near Main object, you can click on it to run the Main program.
hello scalaProcess finished with exit code 0
“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 `deposit` and `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 `behavior(balance)`.
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 `Behaviours.empty` or `behaviour(balance = 0)` or `SpawnProtocol()`. `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.
balance = 0
balance = 150
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.