Simple Akka use case
How a newcomer can understand Akka’s principles of work? Couple of examples from the documentation could be not enough. From the other side, a simple application which shows Akka in action is a very good way for demonstration of Actors capabilities. So let’s start the acquaintance with the simple Akka use case!
Task
At start we need to consider following task:
Get all content from files which are located in the “resources” directory and write it in one resulting text file.
Very convenient when a task could be explained in one sentence. And even more convenient when a task is solved with Akka.
Project setup
I work in Intellij IDEA and use SBT, here is my build.sbt file:
name := """akkaTutorial""" version := "0.1" scalaVersion := "2.11.7" libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-actor" % "2.4.1" )
The entire project structure is displayed below. Don’t panic, we will go through a creation of each class and object from this screenshot.
The last thing which can be useful for you in this section is the link to the GitHub repository.
Developing a simple Akka application
According to the task, we need to get a content from the text files which are stored in the “resources” folder and then write it to the result file. How it should be done using Actors? Those of you who have read the Akka reference, probably remember that an Actor which receives a large task can broke the task on smaller chunks and pass them on execution to its children.
In general we can separate main task on two different activities:
- Scan folder with files
- Process files
Actually also we can add one more Actor for writing of final results, but I will not do so. Let it remains as an opportunity for you to modify and enhance my app.
Here is a code of FileReaderActor
it plays role of children in this app:
package com.actor import java.io.File import akka.actor.{PoisonPill, Props, Actor} import akka.event.Logging import scala.collection.mutable.ListBuffer import scala.io.Source class FileReaderActor extends Actor { val log = Logging.getLogger(context.system, this) def receive = { case f: File => { log.info(s"Reading file ${f.getName}") var words = new ListBuffer[String] Source.fromFile(f).getLines().foreach(line => words += line ) sender() ! words.toList self ! PoisonPill } case _ => log.info("Still waiting for a text file") } } object FileReaderActor { def props = Props(new FileReaderActor) }
This Actor works just with File
messages. It reads it, sends a List[String]
as a response to a parent Actor
and then sends to self
the PoisonPill
as a result it dies, because it has only single responsibility – read the text and send the result to the parent.
Now let’s take a look at the FolderScannerActor
. In some sense it’s the main Actor
in the app, because it initiates a process of file reading. It has two Int
fields filesNumber
and responsesNumber
. Based on them we know when to start writing the result file. A logic is simple, when the responsesNumber
equals to the filesNumber
, that’s means all files were processed and we can write the results.
package com.actor import java.io.File import akka.actor.{Props, Actor} import akka.event.Logging import scala.collection.mutable.ListBuffer class FolderScannerActor extends Actor { import FolderScannerActor._ val log = Logging.getLogger(context.system, this) var filesNumber = 0; var responsesNumber = 0; var words = new ListBuffer[String] def receive = { case path: String => { log.info(s"Scanning ${path}") val directory = new File(path) val files = getFilesFromFolder(directory) filesNumber = files.size files.foreach(file => context.actorOf(FileReaderActor.props) ! file) } case wordsList: List[String] => { log.info(s"New words are received ${wordsList}") responsesNumber += 1 words insertAll(words.size, wordsList) if (filesNumber == responsesNumber) { writeResults(words.toList) } } case _ => log.info("Nothing to scan...") } } object FolderScannerActor { def props = Props(new FolderScannerActor) def getFilesFromFolder(folder: File): List[File] = { if (folder.exists && folder.isDirectory) { println("FILES EXIT") folder.listFiles .toList } else { println("FILES DOES NOT EXIT") List[File]() } } def writeResults(words: List[String]) = { import java.nio.file.{Paths, Files} import scala.collection.JavaConverters._ //Location where you want to write results val path = "/Users/Alex/Downloads/results/result.txt" val resultPath = Paths.get(path) if (Files.exists(resultPath)) Files.delete(resultPath) Files.createFile(resultPath) Files.write(Paths.get(path), words.asJava) } }
As you noticed the FolderScannerActor
creates new FileReaderActor
per each file in the folder which it scans. This approach should provide for us fast, non-blocking and concurrent processing of all files.
One of possible message types which FolderScannerActor
handles is List[String]
. This case is exactly for the children responses.
I tried to develop this app as simple as possible, but in the same time I wanted to make it like a real app which does useful job. So forgive me the redundant code in the companion object.
Here is the code for demonstration:
package com.actor import akka.actor.ActorSystem object Demo { def main(args: Array[String]): Unit = { val system = ActorSystem.create("file-reader") val scanner = system.actorOf(FolderScannerActor.props, "scanner") val directoryPath = getClass.getResource("/a-words").getPath scanner ! directoryPath } }
After running the code above you will see the results in the text file.
That’s it. Leave your comments about the app, I’ll be really appreciated to read them. Also subscribe to updates of the “Fruzenshtein Notes”, I’m planning to write a lot of stuff about Akka, Scala and BigData.