When I first starting playing around with Akka, I was definitely swimming upstream. I was learning Scala and learning Akka and coming from a very non-functional, non-actor-oriented background. The more I learned about Scala and the more I learned about Akka, I started noticing some places where I had made rather large architectural mistakes.
One thing that I did with Akka 1.x was I exposed public state properties on various actors. While this fixed my problem, it was a bad smell that I didn’t notice at the time. In my ScalaMUD sample, I switched and used an implicit that wraps an external hash map to maintain state of actors. I now even think this is a bad idea and in a new project (yet to be disclosed) I am currently trying to avoid exposing -any- public state on any actors.
The current challenge that I seem to be running into is in figuring out the level of granularity of my actor system. In other words, how do I decide when to create more actors or when to do the work necessary within a def as a plain Scala function?
I’ve come up with the following set of questions that I ask myself now:
- Should this work take place concurrently? If the work in question is something that I think should be fired off in the background, then this is a huge argument leaning toward the use of an Actor.
- Do multiple other actors need this service performed? If the code I’m about to put into a function is something that other actors in the system also need done, then I should really consider using an Actor that performs the work on demand by receiving a message rather than a function. That said, keep in mind that multiple Actors can all make use of shared Scala objects/classes – so re-evaluate the problem against other criteria in this list as well.
- Is this operation purely synchronous or request/response? If so, then you might not need or want to use an actor for it. Sure, there is plenty of syntactical support for request-and-await-reply blocking calls in Akka but the more of those you do, the uglier and harder to read your code gets. If you really need to perform a synchronous, blocking functional call then it might be better as a function and not an Actor.
- Is this really a data storage/retrieval operation? If the work you’re considering offloading to an Actor is actually data storage like putting and getting from hash tables or something similar then you might not need an Actor for it. That said, if the act of storing or retrieving might take a while and you don’t want to block during this, sending a message to a “data actor” and going about your business might actually be the best thing for it.
- Does my current Actor respond to too many messages? One thing I’ve noticed during my work with ScalaMUD is that I’ve created Actors that do too much. Actors, in my experience, seem to be work better the smaller and more purpose-built they are. The actor system has a robust supervisory system that makes actors that control other actors not only possible, but a best practice. I’ve gotten tremendous value from refactoring one big Actor into multiple smaller actors.
- Do I need supervisory control over a service that is being performed? As mentioned above, if there is some service that needs to be performed but you want control over how your application reacts to failures like exceptions, then using a supervisor and wrapping that service as an Actor and setting a control policy can do you a world of good. For example, you can set it so the supervisor receives messages when the child throws an exception, or you can have Akka automatically restart dead child actors. The possibilities are as powerful as they are varied.
- Does this operation need to be thread-safe? If the answer is yes, then you might want to use an actor. The reason for this is that actors pull their messages out of queues in a single thread, which means no work you do in response to receiving a message is ever re-entrant, so you don’t need locks or critical sections. This alone is quite possibly one of the single biggest advantages of actor-based programming and design.
Obviously your mileage may vary and I am still an Akka/Scala newbie but what I have found, especially with my newest project, is that my application almost designs itself automatically when I think in terms of autonomous, supervisory-linked actors rather than thinking in terms of traditional OOP.
I recently tweeted the following statement and I still believe it is true: Actors are to OOP what OOP was to procedural programming.