Akka is a powerful toolkit and runtime for building highly concurrent, distributed, and resilient message-driven applications on the JVM. It simplifies the development of complex, distributed systems by providing a higher level of abstraction for dealing with concurrency and distribution.

Key Concepts

  1. Actors

  • Definition: Actors are the fundamental unit of computation in Akka. They encapsulate state and behavior, and they communicate exclusively through message passing.
  • Characteristics:
    • Encapsulation: Each actor has its own state and behavior.
    • Isolation: Actors do not share state directly.
    • Asynchronous Communication: Actors communicate by sending and receiving messages asynchronously.

  1. Actor System

  • Definition: An actor system is a hierarchical group of actors which share common configuration and lifecycle.
  • Purpose: It provides the infrastructure for creating and managing actors.

  1. Messages

  • Definition: Messages are immutable objects that actors use to communicate with each other.
  • Types: Messages can be of any type, but it's a good practice to use case classes or case objects.

  1. Supervision

  • Definition: Supervision is a strategy for handling failures in actor systems. Supervisors manage the lifecycle and error handling of their child actors.
  • Strategies:
    • Restart: Restart the child actor.
    • Resume: Resume the child actor's operation.
    • Stop: Stop the child actor.
    • Escalate: Escalate the failure to the supervisor's supervisor.

Setting Up Akka

  1. Adding Akka to Your Project

To use Akka in your Scala project, you need to add the Akka dependencies to your build.sbt file:

libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % "2.6.16"

  1. Creating an Actor System

Here's a simple example of creating an actor system and defining an actor:

import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors

object HelloWorld {
  def apply(): Behaviors.Receive[String] = Behaviors.receive { (context, message) =>
    context.log.info("Hello, {}!", message)
    Behaviors.same
  }
}

object Main extends App {
  val system: ActorSystem[String] = ActorSystem(HelloWorld(), "hello-akka")
  system ! "World"
  system ! "Akka"
}

Explanation:

  • HelloWorld Actor: Defines an actor that logs a greeting message.
  • ActorSystem: Creates an actor system named "hello-akka" and sends messages to the HelloWorld actor.

Practical Example: Simple Chat System

  1. Define Messages

sealed trait ChatMessage
case class SendMessage(content: String) extends ChatMessage
case class ReceiveMessage(content: String) extends ChatMessage

  1. Define Chat Actor

import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors

object ChatActor {
  def apply(): Behavior[ChatMessage] = Behaviors.receive { (context, message) =>
    message match {
      case SendMessage(content) =>
        context.log.info("Sending message: {}", content)
        Behaviors.same
      case ReceiveMessage(content) =>
        context.log.info("Received message: {}", content)
        Behaviors.same
    }
  }
}

  1. Create Actor System and Send Messages

object ChatApp extends App {
  val system: ActorSystem[ChatMessage] = ActorSystem(ChatActor(), "chat-system")
  system ! SendMessage("Hello, Akka!")
  system ! ReceiveMessage("Hello, User!")
}

Explanation:

  • ChatMessage: Defines the messages that the chat actor can handle.
  • ChatActor: Defines the behavior of the chat actor, logging sent and received messages.
  • ChatApp: Creates an actor system and sends messages to the ChatActor.

Exercises

Exercise 1: Create a Counter Actor

  1. Define a CounterMessage trait with Increment and Decrement case objects.
  2. Create a CounterActor that maintains a count and logs the current count on each message.
  3. Create an actor system and send increment and decrement messages to the CounterActor.

Solution:

sealed trait CounterMessage
case object Increment extends CounterMessage
case object Decrement extends CounterMessage

object CounterActor {
  def apply(): Behavior[CounterMessage] = Behaviors.setup { context =>
    var count = 0
    Behaviors.receiveMessage {
      case Increment =>
        count += 1
        context.log.info("Count: {}", count)
        Behaviors.same
      case Decrement =>
        count -= 1
        context.log.info("Count: {}", count)
        Behaviors.same
    }
  }
}

object CounterApp extends App {
  val system: ActorSystem[CounterMessage] = ActorSystem(CounterActor(), "counter-system")
  system ! Increment
  system ! Increment
  system ! Decrement
}

Exercise 2: Implement a Supervisor Strategy

  1. Create a ChildActor that throws an exception on receiving a specific message.
  2. Create a SupervisorActor that supervises the ChildActor and implements a restart strategy.
  3. Create an actor system and send messages to the ChildActor through the SupervisorActor.

Solution:

import akka.actor.typed.SupervisorStrategy
import scala.concurrent.duration._

object ChildActor {
  def apply(): Behavior[String] = Behaviors.receive { (context, message) =>
    if (message == "fail") throw new RuntimeException("Failure!")
    else {
      context.log.info("Message: {}", message)
      Behaviors.same
    }
  }
}

object SupervisorActor {
  def apply(): Behavior[String] = Behaviors.supervise(ChildActor())
    .onFailure[RuntimeException](SupervisorStrategy.restart.withLimit(3, 1.minute))
}

object SupervisorApp extends App {
  val system: ActorSystem[String] = ActorSystem(SupervisorActor(), "supervisor-system")
  system ! "Hello"
  system ! "fail"
  system ! "Hello again"
}

Conclusion

In this section, we introduced Akka and its core concepts, including actors, actor systems, messages, and supervision. We also provided practical examples and exercises to help you get started with building concurrent and distributed applications using Akka. In the next module, we will delve deeper into the Scala ecosystem and tools, exploring how to effectively build and manage Scala projects.

© Copyright 2024. All rights reserved