Merge pull request #26 from softinio/intro-to-actors
3 new blogs on Actors plus updates to about page
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
/public
|
||||
.DS_Store
|
||||
/resources/_gen/
|
||||
/result
|
||||
|
|
2
.gitmodules
vendored
|
@ -1,3 +1,3 @@
|
|||
[submodule "themes/hugo-clarity"]
|
||||
path = themes/hugo-clarity
|
||||
url = https://github.com/chipzoller/hugo-clarity
|
||||
url = git@github.com:softinio/hugo-clarity.git
|
||||
|
|
|
@ -8,7 +8,7 @@ toc = false
|
|||
featureImage = ""
|
||||
thumbnail = ""
|
||||
shareImage = ""
|
||||
codeMaxLines = 10
|
||||
codeMaxLines = 30
|
||||
codeLineNumbers = false
|
||||
figurePositionShow = false
|
||||
keywords = []
|
||||
|
|
|
@ -26,7 +26,7 @@ paginate = 10
|
|||
author = "Salar Rahmanian"
|
||||
twitter = "@SalarRahmanian"
|
||||
largeTwitterCard = false
|
||||
introDescription = "Software Engineer based in San Francisco Bay Area with interests in Scala, Java, Haskell"
|
||||
introDescription = "Software Engineer based in San Francisco Bay Area with interests in Scala, Java, Haskell & NixOS"
|
||||
ga_analytics = "UA-47014432-1"
|
||||
numberOfTagsShown = 14
|
||||
fallBackOgImage = "salar.jpg"
|
||||
|
|
|
@ -5,11 +5,9 @@ description = "About Salar Rahmanian"
|
|||
keywords = ["Salar", "Rahmanian", "Salar Rahmanian"]
|
||||
+++
|
||||
|
||||
I , [Salar Rahmanian](https://www.softinio.com), am a software developer based in San Francisco Bay area, California.
|
||||
I , [Salar Rahmanian](https://www.softinio.com), am a software engineer based in San Francisco Bay area, California.
|
||||
|
||||
I am married to my wonderful wife, [Leila Rahmanian](https://www.rahmanian.xyz/categories/leila-rahmanian/). We have two sons and a daughter, [Valentino Rahmanian](https://www.rahmanian.xyz/categories/valentino-rahmanian/) who was born in December 2013, [Caspian Rahmanian](https://www.rahmanian.xyz/categories/caspian-rahmanian/) who was born in December 2014 and [Persephone Rahmanian](https://www.rahmanian.xyz/categories/persephone-rahmanian/) who was born in February 2017.
|
||||
I have been developing software since the age of eleven. My current passion is functional programming and distributed systems hence I like to spend my time with technologies like Scala, ZIO, AKKA and Kafka.
|
||||
|
||||
I have been developing software since the age of eleven. My current passion is functional programming and distributed systems hence I like to spend my time with technologies like Scala, Nix, AKKA and Kafka.
|
||||
|
||||
![Salar Rahmanian Family](/img/SalarRahmanianFamily.jpg)
|
||||
![Salar Rahmanian's Family](/img/SalarRahmanianFamily.jpg)
|
||||
|
||||
|
|
161
content/post/introduction-to-akka-typed-using-scala.md
Normal file
|
@ -0,0 +1,161 @@
|
|||
+++
|
||||
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 = 30
|
||||
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", "swift language 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.
|
||||
|
||||
## Creating the Actor System ##
|
||||
|
||||
```scala
|
||||
object ActorsMain {
|
||||
def apply(): Behavior[Customer] =
|
||||
Behaviors.setup { context =>
|
||||
val subscriber = context.spawn(Subscriber(), "subscriber")
|
||||
val db = context.spawn(Datastore(), "db")
|
||||
|
||||
Behaviors.receiveMessage { message =>
|
||||
val replyTo = context.spawn(Reply(), "reply")
|
||||
subscriber ! Message(message.firstName,message.lastName,message.emailAddress,Add,db,replyTo)
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Pat extends App {
|
||||
val actorsMain: ActorSystem[Customer] = ActorSystem(ActorsMain(), "PatSystem")
|
||||
actorsMain ! Customer("Salar", "Rahmanian", "code@softinio.com")
|
||||
}
|
||||
```
|
||||
|
||||
The root actor spawns all the new actors and as a result has the `ActorRef` for all the actors.
|
||||
|
||||
Looking at the above code snippet you can see that we are creating a `Behavior` of type `Customer`, which is the type of the message we are going to receive. As this is the outer most actor in our actor hierachy, `Behaviors.setup` is used to spawn all the actors we have. When a new message is received, a message is sent to the subscriber actor (which is our validate email address actor).
|
||||
|
||||
In the applications `main` (i.e. `Pat` object), we create the `ActorSystem` and start sending `Customer` messages.
|
||||
|
||||
## 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)
|
98
content/post/introduction-to-the-actor-model.md
Normal file
|
@ -0,0 +1,98 @@
|
|||
+++
|
||||
title = "Introduction to the Actor Model"
|
||||
date = 2020-10-04T11:23:12-07:00
|
||||
description = "In this post I am going do a quick introduction to the actor model and the problems it is trying to solve."
|
||||
toc = true
|
||||
featured = false
|
||||
draft = false
|
||||
featureImage = "https://shared.softinio.com/Screen-Shot-2020-10-17-15-56-15.66.png"
|
||||
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", "swift language actors"]
|
||||
tags = ["actor model", "concurrency", "distributed systems"]
|
||||
categories = ["concurrency", "distributed systems"]
|
||||
+++
|
||||
|
||||
My first proper computer was an IBM PC clone with an Intel 486 processor. It had a button on it called `turbo` that when you pushed it would run the processor at double the speed. I say proper as before that I had started my computer journey with a Sinclair ZX Spectrum (48k Ram) and a Commodore 64 (64k Ram) hence didn't consider them as serious contenders for this post. It has been a really exciting ride watching hardware and software evolve together. The interesting observation has been that as faster more powerful hardware has come out, software has been quick to grab all the extra speed and resources the new hardware provides.
|
||||
|
||||
Watching how CPU's have evolved, in the early days it was all about clock speed and cache as the main optimizations to improve performance. However in the later years multicore and hyperthreading has been the driving factor to improve performance. This means that in current times to reap the maximum performance benefits that are offered by todays processors we need to write our software leveraging concurrency.
|
||||
|
||||
There are many methods and patterns of concurrency. In this post I am going to focus on the actor model.
|
||||
|
||||
## What is an Actor ? ##
|
||||
|
||||
An actor is an entity that receives a message and does some processing on the received message as a self contained concurrent operation.
|
||||
|
||||
![Actor](/img/actor.png)
|
||||
|
||||
An Actor consists of an isolated internal state, an address, a mailbox and a behavior.
|
||||
|
||||
The address is a unique reference used to locate the actor so that you can send a message to it. It will include all the information you need, such as the protocol used to communicate with the actor.
|
||||
|
||||
Once an actor receives a message, it adds the message to its mailbox. You can think of the mailbox as queue where the actor picks up the next message it needs to process. Actors process one message at a time. The actor patterns specification does say that the order of messages received is not guaranteed, but different implementations of the actor pattern do offer choices on mailbox type which do offer options on how messages received are prioritized for processing.
|
||||
|
||||
The behavior determines what the actor needs to do with the message it receives. It will involve some processing of the message with some business logic which may include a side effect. Plus it will need to decide what to do next after processing.
|
||||
|
||||
Once it has finished processing the message, the actor will do one of the following:
|
||||
|
||||
- Does nothing, only needed to run the behavior on the received message
|
||||
- Sends messages to other existing actors
|
||||
- Creates a new actor and sends a message to it
|
||||
- Respond to the sender zero or more times
|
||||
|
||||
## Characteristics of an Actor ##
|
||||
|
||||
One of the differentiating characteristics of an actor is that it persists and has an isolated internal state.
|
||||
|
||||
What this means is that once an actor is started it will keep running, processing messages from its inbox and won't stop unless you stop it. It will maintain its state through out and only the actor has access to this state. This is unlike your traditional asynchronous programming, such as using Future in Scala or promises in javascript where once the call has finished its state between calls is not maintained and the call has finished.
|
||||
|
||||
This ability gives actors super powers when building concurrent applications.
|
||||
|
||||
## Handling Failure with Actors ##
|
||||
|
||||
In an actor system every actor has a supervisor who is responsible for handling failures. Supervisors are actors themselves but with the sole responsability of continuity for the actor it is supervising and handling any failures that occur. In the case of the supervisor actor its behavior is what you want it to do in case of such failure. Most implementations of the actor model will provide you with several types of supervisors, each using a different strategy for handling failures but you will have the option of creating your own custom supervisors too. Supervisors make it possible for actor's to self heal and continue.
|
||||
|
||||
## Comparing Actors with traditional object oriented programming ##
|
||||
|
||||
Lets go through a contrived simple example, lets say you are a store that sells oranges. You have 10 oranges in stock. You have two customers who try to buy 7 oranges each. As you don't have enough oranges you cannot serve both customers requests for oranges.
|
||||
|
||||
![Object Oriented Programming Flow Example](/img/oop_oranges_example.png)
|
||||
|
||||
In a normal object oriented (multi-threadeed) program a customer would send a request to a service requesting the oranges they want. They expect a reply right away and wait for the response. When the request is received a check is made against the database to make sure there are enough oranges to fulfill the request (i.e. number of oranges >= 7) and if so, a success will be returned to the customer and 7 oranges will be deducted from the total orange count, python like pseudo code:
|
||||
|
||||
```python
|
||||
def get_oranges(number_of_oranges):
|
||||
oranges_list = db_call_to_get_oranges()
|
||||
oranges_list_len = len(oranges_list)
|
||||
if oranges_list_len >= number_of_oranges:
|
||||
update_oranges(oranges - number_of_oranges)
|
||||
return oranges_list[:number_of_oranges-1]
|
||||
return None
|
||||
```
|
||||
|
||||
If the second customer places their order at the same time, as our application is multi-threaded, there is a chance the check to get the current list of oranges will return the full list before the first customers purchase of 7 oranges has been deducted from the number of oranges available which will result in us returning a success by mistake even though we don't have enough oranges available. To avoid this race condition extra work needs to be done to prevent it, such as using locks or other appropriate mechanisms.
|
||||
|
||||
Using the Actor model this kind of race condition is avoided.
|
||||
|
||||
![Actor Programming Flow Example](/img/actor_oranges_example.png)
|
||||
|
||||
When each of the two requests are received, they will placed in the actor's inbox. The actor processes one request at a time from the inbox. The actors state are the oranges and updates to it are done whilst processing a single request which completes before the next request is fetched and processed from the inbox, thereby avoiding any potention race condition.
|
||||
|
||||
## Notable Implementations of the Actor Model ##
|
||||
|
||||
| Language/Framework | Link | Description |
|
||||
| ------------------ | ---- | ----------- |
|
||||
| akka (Java, Scala) | [akka](https://akka.io) | Framework implementing the actor model on the JVM |
|
||||
| akka.net (C#) | [akka.net](https://getakka.net/index.html) | Framework implementing the actor model for C# and .net |
|
||||
| Erlang | [Erlang OTP](https://www.erlang.org) | A Programming language and VM that implements the actor model |
|
||||
| Elixir | [Elixir](https://elixir-lang.org) | A Programming language that compiles down to Erlang bytecode |
|
||||
| Orleans (C#) | [orleans](https://dotnet.github.io/orleans/) | Framework implementing the actor model for C# and .net |
|
||||
| zio-actors (Scala) | [zio-actors](https://zio.github.io/zio-actors/) | A high-performance, purely-functional library for building, composing, and supervising typed actors based on ZIO using Scala |
|
||||
|
||||
## Next ##
|
||||
|
||||
This was a rather short introduction to the actor model. Follow my blog for my follow-up posts on the actor model and my experience using it. In the meanwhile, it would be wonderful if my readers who are currently using this model could share using the comments below what problems they are solving with it and which implementation (language and/or framework) of the actor model they are using.
|
||||
|
221
content/post/introduction-to-zio-actors.md
Normal file
|
@ -0,0 +1,221 @@
|
|||
+++
|
||||
title = "Introduction to Zio Actors"
|
||||
date = 2020-11-01T14:14:21-08:00
|
||||
description = "Introduction to ZIO Actors"
|
||||
featured = true
|
||||
draft = false
|
||||
toc = true
|
||||
featureImage = "/img/ZIO.png"
|
||||
thumbnail = ""
|
||||
shareImage = ""
|
||||
codeMaxLines = 30
|
||||
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", "swift language actors", "functional programming", "fp"]
|
||||
tags = ["actor model", "concurrency", "distributed systems", "scala", "zio", "zio-actors", "functional programming"]
|
||||
categories = ["concurrency", "distributed systems", "scala", "functional programming"]
|
||||
+++
|
||||
|
||||
In this post I am going to do a quick introduction to using the [ZIO Actors](https://zio.github.io/zio-actors/), a library that implements the Actor model using Scala and ZIO a library for asynchroneous and concurrent programming.
|
||||
|
||||
Before reading this post it is recommended that you read my two earlier posts [Introduction to the Actor Model](/post/introduction-to-the-actor-model/) and [Introduction to Akka Typed Using Scala](/post/introduction-to-akka-typed-using-scala/) as I have assumed the reader will be familiar with the concepts discussed in those posts. Some basic knowledge of [ZIO](https://zio.dev) is assumed.
|
||||
|
||||
I will be going through the same example as I covered in my Introduction to Akka post, only this time I am using ZIO Actors instead of Akka.
|
||||
|
||||
## 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 zio actors to build an application, 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.
|
||||
|
||||
![ZIO Actor System Design Example](/img/zio_actor_system_design.png)
|
||||
|
||||
- `ZIO Actor System`: 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
|
||||
sealed trait Protocol[+A]
|
||||
|
||||
final case class Message(
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
emailAddress: String,
|
||||
command: Command,
|
||||
db: ActorRef[Protocol],
|
||||
replyTo: ActorRef[Protocol]
|
||||
) extends Protocol[Unit] {
|
||||
def isValid: UIO[Boolean] =
|
||||
UIO(EmailValidator.getInstance().isValid(emailAddress))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The message type for sending a reply to the requester:
|
||||
|
||||
```scala
|
||||
final case class SubscribedMessage(subscriberId: Long, from: ActorRef[Protocol])
|
||||
extends Protocol[Unit]
|
||||
```
|
||||
The exception type if an invalid message is sent to an actor:
|
||||
|
||||
```scala
|
||||
case class InvalidEmailException(msg: String) extends Throwable
|
||||
```
|
||||
|
||||
The message type of message retrieved from the datastore when a `Get` command is received:
|
||||
|
||||
```scala
|
||||
final case class Customer(
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
emailAddress: String
|
||||
) extends Protocol[Unit]
|
||||
```
|
||||
|
||||
## Validate Email Address Actor ##
|
||||
|
||||
```scala
|
||||
val subscriber = new Stateful[Console, Unit, Protocol] {
|
||||
override def receive[A](
|
||||
state: Unit,
|
||||
protocol: Protocol[A],
|
||||
context: Context
|
||||
): RIO[Console, (Unit, A)] =
|
||||
protocol match {
|
||||
case message: Message =>
|
||||
for {
|
||||
_ <- putStrLn(
|
||||
s"Validating ${message.firstName} ${message.lastName} with email ${message.emailAddress}!"
|
||||
)
|
||||
valid <- message.isValid
|
||||
self <- context.self[Protocol]
|
||||
_ <- message.replyTo ! SubscribedMessage(1L, self)
|
||||
if (valid)
|
||||
_ <- message.db ! message
|
||||
if (valid)
|
||||
} yield ((), ())
|
||||
case _ => IO.fail(InvalidEmailException("Failed"))
|
||||
}
|
||||
}
|
||||
```
|
||||
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 `Stateful`.
|
||||
|
||||
### What is Stateful ###
|
||||
|
||||
Stateful is the data type we use to describe a behavior with zio-actors:
|
||||
|
||||
```scala
|
||||
Stateful[R, S, -F[+_]]
|
||||
```
|
||||
|
||||
What do the type parameters represents:
|
||||
|
||||
- `R` represents the environment type (similar to `R` in `ZIO[R, E, A]`)
|
||||
- `S` represents the state of the actor that gets updated after every message.
|
||||
- `F` represents the message the actor will receive to process.
|
||||
|
||||
### How Validate Email Address Actor Work ###
|
||||
|
||||
Looking at the code snippet above you will see a new `Stateful` is created, for the `R` (environment type) we are passing Console which is an Environment provided by ZIO which allows you to log messages to the console. The state of this actor does not change after each message is processed and hence it `S` type which represents state is set to `Unit`. `Protocol` is the types of messages our actor can process. We went through definition of this type [earlier in the post]({{< ref "introduction-to-zio-actors.md#messages-and-types-" >}}).
|
||||
|
||||
By creating a new `Stateful` we override its `receive` method (similar to `akka`) that implements the actual behavior. Here we pattern match on the message received to make sure it is of type `Message` and if so we use a for comprehension to go through a few steps:
|
||||
|
||||
- Call the message's `isValid` method to verify it contains a valid email address. Only continue if valid.
|
||||
- Get the `ActorRef` for `self`, i.e. the current actor.
|
||||
- The message received contains an `ActorRef` to reply to. Send a message `SubscribedMessage` to this actor (this includes the `ActorRef` of self that was obtained in the last step).
|
||||
- send the message received onto the datastore actor
|
||||
|
||||
From the pattern matching you can see that if you do not receive a message of type `Message`, `IO.fail` which is provided by `ZIO` is used to surface the failure.
|
||||
|
||||
The `receive` method returns `RIO[Console, (Unit, A)]` which is an alias for `ZIO[Console, Throwable, (Unit, A)]` which highlights that a tuple is returned for a success.
|
||||
|
||||
## Datastore Actor ##
|
||||
|
||||
This actor is responsible for adding, removing and fetching a `Customer` from the datastore.
|
||||
|
||||
```scala
|
||||
val datastore = new Stateful[Console, Unit, Protocol] {
|
||||
override def receive[A](
|
||||
state: Unit,
|
||||
protocol: Protocol[A],
|
||||
context: Context
|
||||
): RIO[Console, (Unit, A)] =
|
||||
protocol match {
|
||||
case message: Message =>
|
||||
for {
|
||||
_ <- putStrLn(s"Processing Command")
|
||||
_ <- message.command match {
|
||||
case Add =>
|
||||
putStrLn(s"Adding message with email: ${message.emailAddress}")
|
||||
case Remove =>
|
||||
putStrLn(
|
||||
s"Removing message with email: ${message.emailAddress}"
|
||||
)
|
||||
case Get =>
|
||||
putStrLn(s"Getting message with email: ${message.emailAddress}")
|
||||
}
|
||||
} yield ((), ())
|
||||
case _ => IO.fail(InvalidEmailException("Failed"))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Similar to the [Validate Email Address Actor]({{< ref "introduction-to-zio-actors.md#how-validate-email-address-actor-work-" >}}), this actor creates a new stateful with same type parameters. Overrides `receive` to implement behavior. Pattern match the received message to make sure it is of type `Message` and if not does `IO.fail`. If correct message it does the following:
|
||||
|
||||
- pattern match on the command field of the message to determine which command we have received.
|
||||
- For each command take the appropriate action. As this is a contrived example we just log what we are doing instead of an actual interaction with a database by calling another actor.
|
||||
|
||||
## Creating the Actor System ##
|
||||
|
||||
```scala
|
||||
val program = for {
|
||||
actorSystemRoot <- ActorSystem("salarTestActorSystem")
|
||||
subscriberActor <- actorSystemRoot.make("subscriberActor", Supervisor.none, (), subscriber)
|
||||
datastoreActor <- actorSystemRoot.make("datastoreActor", Supervisor.none, (), datastore)
|
||||
replyActor <- actorSystemRoot.make("replyActor", Supervisor.none, (), reply)
|
||||
_ <- subscriberActor ! Message(
|
||||
"Salar",.
|
||||
"Rahmanian",
|
||||
"code@softinio.com",
|
||||
Add,
|
||||
datastoreActor,
|
||||
replyActor
|
||||
)
|
||||
_ <- zio.clock.sleep(Duration.Infinity)
|
||||
} yield ()
|
||||
```
|
||||
|
||||
With ZIO Actors as everything is a ZIO effect we can use a for comprehension to create our system. Looking at the above code snippet you can see we start by creating an `ActorSystem`. We then use the `.make` property of our `ActorSystem` to create all the actors. Now we are ready to create our `Message` and start sending to the message to the `subscriberActor` we created. As you can see the `ActorRef` of the `dataStoreActor` and `replyActor` are included in the message so that the correct actor is used to store the message and the correct actor is used to send the reply.
|
||||
|
||||
## Summary ##
|
||||
|
||||
In this post I have just tried to give you a feel for what its like to use zio-actors, how to think about your application in terms of Actors and message passing and to get started. As you can see zio-actors leverages ZIO as a library and functional programmming to the max leading to a very composable and readable solution.
|
||||
|
||||
zio-actors has many other features and patterns of use which I will be blogging in more details about in the future so follow my post as I try to share my learnings with everyone.
|
||||
|
||||
|
||||
## Useful Resources ##
|
||||
|
||||
- [My Sample Application used in this post](https://github.com/softinio/pat)
|
||||
- [ZIO Actors Documentation](https://zio.github.io/zio-actors/)
|
||||
- [ZIO Documentation](https://zio.dev)
|
||||
|
|
@ -2,7 +2,5 @@
|
|||
link: about
|
||||
- name: Resumé
|
||||
link: resume
|
||||
- name: Family
|
||||
link: https://www.rahmanian.xyz
|
||||
- name: Events
|
||||
link: https://www.sfbayareatechies.com
|
||||
|
|
BIN
static/img/ZIO.png
Normal file
After Width: | Height: | Size: 200 KiB |
BIN
static/img/actor.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
static/img/actor_oranges_example.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
static/img/akka_actor_system_design.png
Normal file
After Width: | Height: | Size: 64 KiB |
1
static/img/akka_logo.svg
Normal 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 |
BIN
static/img/oop_oranges_example.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
static/img/zio_actor_system_design.png
Normal file
After Width: | Height: | Size: 66 KiB |
|
@ -1 +1 @@
|
|||
Subproject commit fc4c5812b0ad0a79c5cf367f6e525f89b9014435
|
||||
Subproject commit a870ab6bddf5cd8a9b5bec38a41365060fc67bcf
|