Skip to Content
Course content

126: Building Microservices with Akka and Play

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 SeatActor that maintains a Set[String] of reserved seat numbers (e.g., "1A", "1B").
  • The SeatActor should handle a ReserveSeat(seatNumber: String) message. If the seat is already in the set, it should reply with BookingFailed; otherwise, add it to the set and reply with BookingConfirmed.
  • Implement a Play Controller with a route POST /reserve/:seatNumber.
  • The controller must use the ask pattern to communicate with the SeatActor and return a 200 OK for confirmation or a 400 Bad Request for failure.
  • Ensure you are using a proper implicit Timeout for the ask operation.

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.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.