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
126: Building Microservices with Akka and Play
You’ve likely reached a point where you're comfortable with Scala's type system and basic concurrency. Now we're hitting the "big leagues": microservices. A common trap I see developers fall into when combining Play and Akka is thinking that Play is just a "web wrapper" for Akka, and therefore, the Play Controller is the right place to put your core business logic. They treat the Controller like a traditional Spring or Express handler—doing the database lookups, calculating totals, and updating state all within a single action method.
Thinking Play Controllers Should Manage Business State
Let's look at why that's a mistake. Imagine we're building a high-traffic inventory system for a flash sale. You have a StockController with a method to decrease the count of an item. If you handle the logic in the controller, it looks something like this:
def decreaseStock(productId: String) = Action.async {
future {
val currentStock = db.getStock(productId) // Read
if (currentStock > 0) {
db.updateStock(productId, currentStock - 1) // Write
Ok("Item reserved")
} else {
BadRequest("Out of stock")
}
}
}
In a vacuum, this looks fine. In production, it's a disaster. Because Play is asynchronous and multi-threaded, two requests hitting this method at the exact same millisecond will both read currentStock = 1, both will pass the check, and both will write 0 to the database. You've just sold the same last item to two different people. You might think, "I'll just use a database lock," but then you've turned your high-performance Scala app into a bottlenecked queue. You're fighting the framework instead of using it.
Delegating State and Concurrency to Akka Actors
The correct mental model is this: Play is your edge; Akka is your engine. Play should be a thin translation layer that converts HTTP requests into messages and sends them into the Akka actor system. The Actor is the "single source of truth" for a specific piece of state.
Instead of the controller managing the stock, you create a ProductActor. Because an Actor processes messages sequentially, the race condition vanishes. You don't need locks because the Actor is effectively a single-threaded guardian of that product's inventory. I've found that once you move the "brain" of the operation into an Actor, your controllers become boring—and in software engineering, boring controllers are a sign of a healthy architecture.
Bridging the Gap with the Ask Pattern
Since Play expects a Future[Result] and Akka Actors communicate via fire-and-forget messages, we use the ask pattern (the ? operator). This allows the controller to send a message to an actor and receive a Future that completes when the actor replies.
Here is how that same inventory logic looks when properly decoupled:
// The Actor: The actual source of truth
class ProductActor(productId: String) extends Actor {
var stock = 100 // State is encapsulated here
def receive = {
case ReserveItem =>
if (stock > 0) {
stock -= 1
sender() ! Reserved
} else {
sender() ! OutOfStock
}
}
}
// The Controller: Just a messenger
def decreaseStock(productId: String) = Action.async {
implicit val timeout: Timeout = 5.seconds
// We "ask" the actor for the result
(productActorSystem ? ReserveItem).map {
case Reserved => Ok("Item reserved")
case OutOfStock => BadRequest("Out of stock")
}
}
Notice the shift. The controller no longer cares how the stock is decremented; it only cares that it sent a request and got a response. This decoupling allows you to scale. If your ProductActor becomes too busy, you can move it to a different node in a cluster without changing a single line of code in your Play controller. That's the real power of the Akka/Play synergy.
📋 Practical Task
Implementation: Real-time Flight Seat Reservation System
Your goal is to build a simplified seat reservation system where a Play Controller interfaces with an Akka Actor to prevent double-booking of seats on a flight.
Requirements:
- Create a
SeatActorthat maintains aSet[String]of reserved seat numbers (e.g., "1A", "1B"). - The
SeatActorshould handle aReserveSeat(seatNumber: String)message. If the seat is already in the set, it should reply withBookingFailed; otherwise, add it to the set and reply withBookingConfirmed. - Implement a Play Controller with a route
POST /reserve/:seatNumber. - The controller must use the
askpattern to communicate with theSeatActorand return a200 OKfor confirmation or a400 Bad Requestfor failure. - Ensure you are using a proper
implicit Timeoutfor theaskoperation.
Evaluation Criteria: Your solution must avoid any shared mutable state outside of the Actor and must not use synchronized blocks or manual locks in the Controller.
There are no comments for now.