23
votes

I'm working on a web application written in Scala, using the Play! framework and Akka. The code is organized basically like this: Play controllers send messages to Akka actors. The actors, in turn, talk to a persistence layer, that abstracts database access. A typical example of usage of these components in the application:

class OrderController(orderActor: ActorRef) extends Controller {
  def showOrders(customerId: Long) = {
    implicit request => Async {
      val futureOrders = orderActor ? FindOrdersByCustomerId(id)

      // Handle the result, showing the orders list to the user or showing an error message.
    }
  }
}

object OrderActor extends Actor {
  def receive = {
    case FindOrdersByCustomerId(id) => 
      sender ! OrderRepository.findByCustomerId(id)
    case InsertOrder(order) =>
      sender ! OrderRepository.insert(order)
      //Trigger some notification, like sending an email. Maybe calling another actor.
  }
}

object OrderRepository {
  def findByCustomerId(id: Long): Try[List[Order]] = ???
  def insert(order: Order): Try[Long] = ???
}

As you can see, this is the basic CRUD pattern, much like what you would see in other languages and frameworks. A query gets passed down to the layers below and, when the application gets a result from the database, that result comes back up until it reaches the UI. The only relevant difference is the use of actors and asynchronous calls.

Now, I'm very new to the concept of actors, so I don't quite get it yet. But, from what I've read, this is not how actors are supposed to be used. Observe, though, that in some cases (e.g. sending an email when an order is inserted) we do need true asynchronous message passing.

So, my question is: is it a good idea to use actors in this way? What are the alternatives for writing CRUD applications in Scala, taking advantage of Futures and the other concurrency capabilities of Akka?

2
Take a look at typed channels - they came out after you posted this. They've got some interesting features for message flow, so you can do things like pass a message through several actors: OrderRepository.insert(order) -!-> sender -?-> sendEmail -?-> displayResult.James Moore

2 Answers

5
votes

Although actor based concurrency doesn't fit with transactional operations out of the box but that doesn't stop you from using actors that way if you play nicely with the persistence layer. If you can guarantee that the insert ( write ) is atomic then you can safely have a pool of actors doing it for you. Normally databases have a thread safe read so find should also work as expected. Apart from that if the insert is not threadsafe, you can have one single WriteActor dedicated simply for write operations and the sequential processing of messages will ensure atomicity for you.

2
votes

One thing to be aware of is that an actor process one message at a time, which would be rather limiting in this case. You can use a pool of actors by using routers.

Your example define a blocking api of the repository, which might be the only thing you can do, depending on your database driver. If possible you should go for an async api there also, i.e. returning Futures. In the actor you would then instead pipe the result of the Future to the sender.