Added Intro to Akka actors

This commit is contained in:
Salar Rahmanian 2020-11-01 14:08:40 -08:00
parent 7ed596d70f
commit 64974b2701
3 changed files with 133 additions and 0 deletions

View file

@ -0,0 +1,132 @@
+++
title = "Introduction to Akka Typed Using Scala"
date = 2020-10-24T20:32:41-07:00
description = "An Introduction to AKKA Typed using Scala with an example"
featured = true
draft = false
toc = true
featureImage = "/img/akka_logo.svg"
thumbnail = ""
shareImage = ""
codeMaxLines = 10
codeLineNumbers = false
figurePositionShow = false
keywords = ["concurrent", "concurrency", "actor model", "actor", "actors", "threads", "petri net", "coroutines", "distributed", "akka", "erlang", "elixir", "akka.net", "microsoft orleans", "orleans", "zio", "zio-actors"]
tags = ["actor model", "concurrency", "distributed systems", "scala", "akka"]
categories = ["concurrency", "distributed systems", "scala"]
+++
In this post I am going to do a quick introduction to using the Akka Typed toolkit that implements the Actor model using Scala. As part of this post I will be developing a simple application using Akka. My goal is to highlight what its like to develop applications using Akka and how to get started with it. I will be following up this post with more posts diving into Akka in more details and exploring more of its features and patterns you can use to solve concurrent and distributed applications.
Before reading this post it is recommended that you read my earlier post [Introduction to the Actor Model](/post/introduction-to-the-actor-model/) as I have assumed the reader will be familiar with the concepts discussed in that post.
## Problem to solve ##
To get start we are going to build a simple mailing list application where a persons name and email address are added to a datastore and we are able to retrieve their details and remove them from the datastore. As the scope of this example is to show how we can use Akka to build applications our data store will be a pretend one.
The diagram below illustrates the actors that we will need and the message flow between each actor.
![Akka Actor System Design Example](/img/akka_actor_system_design.png)
- `Root Actor`: Creates the actor system and spawns all the actors.
- `Validate Email Address Actor`: Validates if the new message received has a valid email address
- `Datastore Actor`: Decides which datastore `Command` (i.e. `Add`, `Get` or `Remove`) we are actioning and calls the relevant actor with the message.
- `Add Action Actor`: Uses the message received to add the received subscriber to the database.
- `Get Subscriber Actor`: Retrieves a subscriber from the database.
- `Remove Subscriber Actor`: Removes a subscriber from the database.
## Messages and Types ##
Lets start by defining the types of the messages our actors are going to be passing.
First our actors are going to be sending on of three types of commands to either add, remove or get a subscriber from the datastore:
```scala
sealed trait Command
final case object Add extends Command
final case object Remove extends Command
final case object Get extends Command
```
The message type to add a subscriber:
```scala
final case class Customer(firstName: String, lastName: String, emailAddress: String)
```
The message type to add a subscriber by the root actor:
```scala
final case class Message(
firstName: String,
lastName: String,
emailAddress: String,
command: Command,
db: ActorRef[Message],
replyTo: ActorRef[SubscribedMessage]
) {
def isValid: Boolean = EmailValidator.getInstance().isValid(emailAddress)
}
```
This type is identical to the `Customer` type except we are including `ActorRef` for the actors to use to save the subscriber and the actor to use to reply to the caller. The `Message` type also has a property that checks to make sure the received message has a valid email address.
## Validate Email Address Actor ##
```scala
object Subscriber {
def apply(): Behavior[Message] = Behaviors.receive { (context, message) =>
context.log.info(s"Validating ${message.firstName} ${message.lastName} with email ${message.emailAddress}!")
if (message.isValid) {
message.replyTo ! SubscribedMessage(1L, context.self)
message.db ! message
} else {
context.log.info(s"Received an invalid message $message.emailAddress")
}
Behaviors.same
}
}
```
When creating actors we need to define how the actor reacts to messages and how they are processed. Looking at the above code you will see that we are creating a `Behavior` which takes a `Message` type. `Behaviors.receive` provides a `context` and the `message` received (which is of type `Message`).
This actors behavior is:
- It uses `context` to log messages
- The Received `message`'s `isValid` property is used to check if the message contains a valid email and if it does then the actors job is done and it sends a message to two other actors, a reply actor confirming the message has been accepted and a message to the storage actor which saves the message in our datastore. Note that the references to the actors it is going to send a message to is included in the message received. If the message is invalid, an appropriate message is logged and no further action is taken.
- Lastly, `Behaviors.same` is called to indicate to the system to reuse the previous actor behavior for the next message. For more information about the different behaviors you can return visit [Behaviors API Docs](https://doc.akka.io/api/akka/current/akka/actor/typed/scaladsl/Behaviors$.html)
## Datastore Actor ##
This actor is responsible for adding, removing and fetching a `Customer` from the datastore.
```scala
object Datastore {
def apply(): Behavior[Message] = Behaviors.receive { (context, message) =>
context.log.info(s"Adding ${message.firstName} ${message.lastName} with email ${message.emailAddress}!")
message.command match {
case Add => println(s"Adding message with email: ${message.emailAddress}") // Send message to Add Action Actor
case Remove => println(s"Removing message with email: ${message.emailAddress}") // Send message to Remove Subscriber Actor
case Get => println(s"Getting message with email: ${message.emailAddress}") // Send message to Get Subscriber Actor
}
Behaviors.same
}
}
```
The `Message` received contains a `Command`, the behavior defined for this actor is to use pattern matching to determine and `Add` to the datastore, `Remove` from the datastore or to `Get` from the datastore. The appropriate message is sent to the actor that will carry out the action as illustrated in my diagram and code snippet above.
The resulting behavior for this actor is also `Behaviors.same` as it is not changing in any way and its behavior will be the same all the time.
## Summary ##
In this post I have just tried to give you a feel for what its like to use akka, how to think about your application in terms of Actors and message passing and to get started. Akka offers a lot of awesome features so stay tuned and follow my blog as we explore and learn about akka. You can look at the application we discussed in this post [here on GitHub](https://github.com/softinio/pat).
## Useful Resources ##
- [My Sample Application used in this post](https://github.com/softinio/pat)
- [Akka Documentation](https://akka.io/docs/)
- [Conference talk introducing Akka Typed](https://www.youtube.com/watch?v=Qb9Cnii-34c)
- [Typecasting Actors: from Akka to TAkka](http://lampwww.epfl.ch/~hmiller/scala2014/proceedings/p23-he.pdf)

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

1
static/img/akka_logo.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 658 270" enable-background="new 0 0 658 270"><g><g fill="#0B5567"><path d="M349.6 105.5v-12.2h19.9v58.4c0 7.1 1.7 9.8 6.1 9.8 1.2 0 2.7-.2 4.1-.3v16.1c-2.2.8-5.5 1.3-9.8 1.3-4.8 0-8.6-.8-11.6-2.7-3.7-2.5-6-5.8-6.8-10.1-5.8 8.8-15.4 13.1-28.7 13.1-11.8 0-21.7-4.1-29.9-12.6-8-8.5-12-18.8-12-31.2s4-22.7 12-31c8.1-8.5 18.1-12.6 29.9-12.6 13.6 0 23.7 6 26.8 14zm-5.9 47.9c5-4.8 7.5-11 7.5-18.3s-2.5-13.4-7.5-18.3c-4.8-4.8-11-7.3-18.1-7.3-7.1 0-12.9 2.5-17.8 7.3-4.6 4.8-7 11-7 18.3s2.3 13.4 7 18.3c4.8 4.8 10.6 7.3 17.8 7.3 7.1 0 13.3-2.5 18.1-7.3zM388.5 177v-115.7h19.8v67.6l30.9-35.5h22.8l-32.7 37.4 36.2 46.3h-22.6l-26.4-33.7-8.3 9.3v24.3h-19.7zM470.8 177v-115.7h19.8v67.6l30.9-35.5h22.9l-32.7 37.4 36.2 46.3h-22.6l-26.4-33.7-8.3 9.3v24.3h-19.8zM607.9 105.5v-12.2h19.9v58.4c0 7.1 1.7 9.8 6.1 9.8 1.2 0 2.7-.2 4.1-.3v16.1c-2.2.8-5.5 1.3-9.8 1.3-4.8 0-8.6-.8-11.6-2.7-3.7-2.5-6-5.8-6.8-10.1-5.8 8.8-15.4 13.1-28.7 13.1-11.8 0-21.7-4.1-29.9-12.6-8-8.5-12-18.8-12-31.2s4-22.7 12-31c8.1-8.5 18.1-12.6 29.9-12.6 13.5 0 23.6 6 26.8 14zm-6 47.9c5-4.8 7.5-11 7.5-18.3s-2.5-13.4-7.5-18.3c-4.8-4.8-11-7.3-18.1-7.3-7.1 0-12.9 2.5-17.8 7.3-4.6 4.8-7 11-7 18.3s2.3 13.4 7 18.3c4.8 4.8 10.6 7.3 17.8 7.3 7.1 0 13.3-2.5 18.1-7.3z"/></g><path fill="#0B5567" d="M230.3 212.8c35.9 28.7 58.9-57 1.7-72.8-48-13.3-96.3 9.5-144.7 62.7 0 0 89.4-32.7 143 10.1z"/><path fill="#15A9CE" d="M88.1 202c34.4-35.7 91.6-75.5 144.9-60.8 12.4 3.5 21.2 10.7 26.9 19.3l-50.4-101.7c-7.2-11.5-25.6-9.1-36-.3l-133.2 111.6c-12.1 10.4-12.8 28.9-1.6 40.1 9.9 9.9 25.6 10.8 36.5 2l12.9-10.2z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB