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
67: Akka Supervision Strategies
I've seen this pattern a dozen times: a developer builds a system of actors, tests it with a few happy-path scenarios, and then deploys it to production only to find their logs flooded with thousands of "Actor Restarting" messages. They aren't actually fixing the problem; they're just stuck in a "death spiral."
Let's look at a piece of code that does exactly this. Imagine we're building a DatabaseWriter actor that handles persisting user profiles. The developer has written it to simply throw an exception when something goes wrong, assuming Akka's default supervision will "just handle it."
class DatabaseWriter extends Actor {
def receive: Receive = {
case profile: UserProfile =>
// Imagine this throws a ConstraintViolationException if the email exists
// or a TimeoutException if the DB is lagging
db.save(profile)
println(s"Saved ${profile.name}")
}
}
class PersistenceManager extends Actor {
val writer = context.actorOf(Props[DatabaseWriter], "writer")
def receive: Receive = {
case profile => writer ! profile
}
}
The Infinite Restart Loop
Here is the problem: Akka's default supervision strategy is to restart the actor. Now, if the DatabaseWriter hits a TimeoutException, a restart is actually great. It clears the internal state and tries again. But what happens when the DatabaseWriter hits a ConstraintViolationException (like a duplicate email)?
The actor crashes, the parent sees the exception, and says, "Oh, I'll just restart the child!" The child comes back to life, receives the exact same message from the mailbox, and crashes again. Repeat this 10,000 times a second, and you've just DDoS'd your own logging system without actually processing a single record.
Tailoring the Response with OneForOneStrategy
To fix this, we need to stop treating all failures as equal. We need a OneForOneStrategy. This allows us to decide the fate of the child based on the type of exception thrown. I generally prefer this over the "All For One" strategy unless the children are so tightly coupled that one failure makes the others useless.
Here is how I would rewrite the PersistenceManager to handle this intelligently:
import akka.actor.SupervisorStrategy._ import akka.actor.{OneForOneStrategy, Actor} import scala.concurrent.duration._ class PersistenceManager extends Actor { override val supervisorStrategy: SupervisorStrategy = OneForOneStrategy() { case _: TimeoutException => Restart // Transient error, try again case _: ConstraintViolation => Stop // Fatal data error, kill the actor case _: Exception => Escalate // Something weird? Let my parent handle it } val writer = context.actorOf(Props[DatabaseWriter], "writer") def receive: Receive = { case profile => writer ! profile } }By overriding
supervisorStrategy, we've turned the manager into a conscious supervisor. If aConstraintViolationoccurs, weStopthe actor. This prevents the death spiral. In a real system, you'd likely combine this with a "Dead Letter" queue or a way to notify the user that their data was invalid, rather than just letting the actor vanish.Deciding Between Resume and Restart
You might be wondering when to use
Resume. I like to think ofResumeas "ignore the glitch." When you resume, the actor keeps its current internal state and simply processes the next message in the mailbox.Use
Resumeif the failure is completely irrelevant to the actor's state—for example, if a logging call failed but the actual business logic succeeded. UseRestartif you suspect the actor's internal state has been corrupted by the failure and you need a clean slate. Just remember:Restartwill clear the actor's state, so if you're keeping important counters or caches in memory, you'll need to handle pre-restart snapshots or accept that the data is gone.
📋 Practical Task
Exercise: Implementing a Resilient Weather Data Aggregator
You are building a system that fetches weather data from multiple third-party APIs. You have a WeatherAggregator (parent) that manages several ApiFetcher (children) actors.
Your task is to implement a OneForOneStrategy in the WeatherAggregator that handles the following scenarios:
ApiTimeoutException: These are common. The actor should be restarted to try a fresh connection.InvalidApiKeyException: This is a configuration error. Restarting won't fix it. The actor should be stopped immediately.RateLimitException: This is a temporary pause. The actor should resume, skipping the current failed request but keeping its state to try the next one.- Any other exception: These are unknown. The error should be escalated to the aggregator's own supervisor.
Requirements:
- Create the three custom exception classes mentioned above.
- Implement the
WeatherAggregatoractor with the specificsupervisorStrategylogic. - Implement a simple
ApiFetcheractor that randomly throws one of these exceptions when it receives aFetchDatamessage so you can verify your strategy works.
There are no comments for now.