Adventures with Akka

I started using Akka about a year ago. Its Actor Model, with its mandatory parental supervision, is a big change from how I have been used to structuring my code. This post will attempt to explore some of the things I’ve learned.

Previously, whenever I wrote Actors that wrapped some blocking operations such as a database call, I wrote something like this (code examples have been simplified for clarity; real code would use mixins to facilitate testing):

class Products extends Actor {
    val router = context.actorOf(
        Props[ProductWorker].withRouter(FromConfig()))

    def receive {
        case x ⇒
            router forward x
    }
}

I would then have a ProductWorker:

class ProductWorker extends Actor {
    val db = Database.fromURL("jdbc:postgres:test")
    def receive {
        case Create(product) ⇒
            sender ! db withSession { ??? }
        case FindById(id) ⇒
            sender ! db withSession { ??? }
    }
}

If the services of the Products actor was required from several other actors, I would create it somewhere higher in the hierarchy and pass it in as a constructor param:

class Designer(Products: ActorRef) extends Actor {
    def receive { ??? }
}

I was doing this for several reasons:

  1. Each instance of the Products actor would create N connections to the database, controlled by the router config. Passing a single Products ActorRef around would allow me to control the total number of connections I made to the database by changing a single router configuration.
  2. I felt that I had to have the two layers so that the Products parent could monitor and restart its children.

More recently I had a bit of a brainwave:

  1. Rather than rely on the number of ProductWorker actors (in other words, the router configuration) to control the number of database connections, I should use a connection pool.
  2. Code like the above bypasses the default supervision hierarchy. Because the Designer actor is not Product‘s parent, it is no longer the one that gets notified if there is a problem.
  3. By default, actors restart their children on failure, which is all I was doing in my Products actor.

It might not be immediately obvious why a connection pool would be useful in this simple example. However, if you consider a larger system with many different actors sharing a pooled data source, it starts to make sense. For example, perhaps different parts of your applications use the connections at different times.

Note that, while a connection pool does give you some benefits, it also adds some complexity. You will have to make sure that the connection pool’s configuration is appropriate for the number of concurrent connections you want, or need, to support.

Putting the observations above together, we can merge the Products and ProductWorker actors into one and move the router up a level to Designer:

object DataSourceHolder {
    lazy val ds = new PooledDataSource(???)
}

class Products extends Actor {
    val db = Database.fromDatasource(DataSourceHolder.ds)
    def receive {
        case Create(product) ⇒
            sender ! db withSession { ??? }
        case FindById(id) ⇒
            sender ! db withSession { ??? }
    }
}

class Designer extends Actor {
    val Products = context.actorOf(
        Props[Products].withRouter(FromConfig()))

    def receive { ??? }
}

There is certainly less boilerplate in this code. Also, it means that Designer gets to make the decision of what to do if Products throws an exception. I think that makes more sense than ProductWorkers always just being restarted. In the above example, the Designer actor doesn’t handle failures of its children more intelligently than just restarting, but at least now it has the option to.

Update: Before you use my above example as a blueprint for your own services, make sure you read Roland’s response below; he is the Akka Tech Lead after all, and officially Knows These Things. I am very thankful that he took the time to correct the error of my ways, and I am looking forward to my further adventures with Akka.

Print Friendly

One thought on “Adventures with Akka

  1. Thanks for the write-up! Your main critique of injecting ActorRefs into other actors (either via the constructor or otherwise) is that this “bypasses the default supervision hierarchy”. When one actor needs the services of another, you argue that the service provider should be a child of the service consumer because only then does it get notified of problems.

    Decoupling the responsibility for fixing problems (i.e. supervision) from the caller–callee relationship is the crucial element of Akka’s resilience story (for more details see http://infoq.com/articles/resilient-software-with-akka). In a nutshell: the supervisor is responsible for resuming/restarting/stopping the child in response to a failure while the communication partners are only responsible for handling the case that no response to their query arrives. Sometimes it makes sense that both are the same entity, but that is the exception and not the rule. If you worry about hearing back about problems then prefer the Stop policy in combination with DeathWatch, then all consumers of a service get notified when a problem occurs and re-initialization of the relationship can be performed via the service’s supervisor (i.e. ask for the new DB connection actor’s reference).

    In your concrete example I would recommend something similar to how Akka’s new remoting layer works. A connection is modeled by an actor (actually a whole sub-tree, but that that is not really relevant is the other very nice things about actors), and when the connection fails a restart is attempted. After a certain number of failures in a row the actor is stopped, making it obvious to all interested parties (the watchers) that currently there is no connection to that specific other system.

    To recap: passing around ActorRefs is not something that should be avoided, but it takes the other Akka features to make effective use of it.

Leave a Reply