Scala
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Scala
-
Section 4: Functional Scala
-
Section 5: Collections in Depth
-
Section 6: Type System
-
Section 7: Concurrency and Ecosystem
-
Section 8: Practical Projects
-
Section 9: Interview Practice
-
Section 10: Data Structures and Algorithms in Scala
-
Section 11: More Practice Exercises
-
Section 12: Advanced Functional Patterns
-
Section 13: More Ecosystem
-
Section 14: Scala Collections Library Deep Dive
-
Section 15: Scala Standard Library Deep Dive
-
Section 16: Akka Ecosystem Deep Dive
-
Section 17: Cats and Cats Effect Deep Dive
-
Section 18: Play Framework Deep Dive
-
Section 19: Apache Spark with Scala Deep Dive
-
Section 20: Scala Build Tools Deep Dive
-
Section 21: Scala 3 Specific Features
-
90: Union and Intersection Types
-
Section 22: Scala Testing Deep Dive
-
Section 23: Functional Domain Modeling
-
Section 24: More Data Structures and Algorithms in Scala
-
Section 25: Scala for Data Engineering
-
Section 26: More Practical Projects
-
Section 27: More Interview and Review
-
Section 28: ZIO Ecosystem Deep Dive
-
Section 29: Scala for Machine Learning
-
Section 30: Scala Microservices Architecture
-
Section 31: Scala Type System Deep Dive
-
Section 32: More Practice and Drills
-
Section 33: Scala Performance Deep Dive
-
Section 34: Scala Ecosystem Tooling
-
Section 35: Scala for Reactive Systems
-
Section 36: More Real-World Case Studies
-
Section 37: Scala for Financial Systems
-
Section 38: Scala GraphQL and gRPC
-
Section 39: More Final Projects
-
Section 40: More Interview and Final Review
-
Section 41: Scala for Streaming Data
-
Section 42: Scala Security Practices
-
Section 43: More Language Deep Dive
-
Section 44: Scala Command-Line Tools
-
Section 45: Scala Documentation and Style
-
Section 46: Scala Dependency Management
-
Section 47: More Practical Backend Patterns
-
Section 48: Scala for Event-Driven Architecture
-
Section 49: More Practice Drills Round 2
-
Section 50: Scala Compiler Deep Dive
-
Section 51: Scala for Web Frontends
-
Section 52: More Data Engineering Practice
-
Section 53: Scala Observability
-
Section 54: More Advanced Practice Projects
-
Section 55: Scala for Legacy Java Integration
-
Section 56: More Testing Practice
-
Section 57: Final Mastery Review
-
Section 58: Scala History and Ecosystem Context
-
Section 59: More Concurrency Patterns
-
Section 60: Scala for Configuration Management
-
Section 61: More Domain Modeling Practice
33: Introduction to Akka Actors
If you're coming from a Java or standard Scala background, you likely have a mental model of "concurrency" as multiple threads accessing the same object and fighting over a lock. Because of this, the most common misconception I see when people start with Akka is thinking that an Actor is just a fancy object that happens to run on its own thread. You might think that calling a method on an Actor is just "automatic" asynchronous execution.
Actors aren't just objects with threads
Let's look at why that mental model fails. Imagine we're building a simple bank account. In a traditional object-oriented approach, you'd have a BankAccount class with a var balance. To make it thread-safe, you'd use synchronized or an AtomicInteger. When you call account.deposit(100), the code inside that method executes on your current thread. You are reaching inside the object and changing its state.
// The "Wrong" Way (Traditional Shared State)
class BankAccount {
private var balance = 0
def deposit(amount: Int): Unit = synchronized {
balance += amount
}
}
val account = new BankAccount()
// I am calling this from my thread, forcing the account to change state
account.deposit(100)
In Akka, this is forbidden. If you try to treat an Actor like a regular object, you'll find you can't even access its internal methods. This is intentional. If Actors were just objects on threads, we'd still be dealing with race conditions, deadlocks, and the nightmare of managing locks across a distributed system.
The Mailbox: Why we "tell" instead of "call"
The correction is this: An Actor is not an object you call; it is a state machine that processes a queue of messages. You never actually interact with the Actor instance itself. Instead, you interact with an ActorRef—a handle or an "address" for that actor.
When you send a message to an Actor using the ! (tell) operator, you aren't executing a function. You are dropping a letter into the Actor's mailbox and immediately moving on with your life. The Actor then checks its mailbox and processes messages one by one, sequentially. Because the Actor only ever handles one message at a time, the code inside the Actor is effectively single-threaded. I love this because it means you can use a plain var balance inside an Actor without any synchronized blocks or volatile keywords, and it's perfectly safe.
import akka.actor.{Actor, ActorSystem, Props}
// Define the messages the Actor can understand
case class Deposit(amount: Int)
case object GetBalance
class BankAccountActor extends Actor {
var balance = 0 // Safe! No locks needed because only one message is processed at a time.
def receive = {
case Deposit(amount) =>
balance += amount
println(s"Deposited $amount. New balance: $balance")
case GetBalance =>
sender() ! balance
}
}
val system = ActorSystem("BankSystem")
val account = system.actorOf(Props[BankAccountActor], "myAccount")
// This doesn't "call" a method. It sends a message to the mailbox.
account ! Deposit(100)
account ! Deposit(50)
Designing your protocol with Case Classes
Since you can't call methods, the "API" of your Actor is defined by the messages it accepts. In the Scala world, we use case classes and case objects for this. I call this the "Protocol."
If you find yourself sending generic String messages or Any, stop immediately. You lose all type safety and your code becomes a debugging nightmare. Always define a specific set of case classes that represent the intent of the communication. If the Actor doesn't recognize a message, it simply ignores it (or puts it in a "dead letters" queue), which prevents the entire system from crashing just because one component sent a malformed request.
📋 Practical Task
Build a Distributed Temperature Monitor
Your task is to implement a simple system where a TemperatureSensor actor sends readings to a TemperatureMonitor actor. The monitor should keep track of the maximum temperature recorded so far.
Requirements:
- Create a
TemperatureReadingcase class that holds asensorId: Stringand avalue: Double. - Create a
GetMaxTempcase object. - Implement the
TemperatureMonitoractor:- It should maintain a private
var maxTemp(initialized to 0.0). - When it receives a
TemperatureReading, it should updatemaxTempif the new value is higher. - When it receives a
GetMaxTempmessage, it should send the currentmaxTempback to the sender.
- It should maintain a private
- In your main application:
- Spin up the
TemperatureMonitor. - Send three different
TemperatureReadingmessages (e.g., 22.5, 31.2, 18.9) to the monitor. - Send a
GetMaxTempmessage and print the result.
- Spin up the
There are no comments for now.