Transferring typical 3-tier architecture to actors Transferring typical 3-tier architecture to actors spring spring

Transferring typical 3-tier architecture to actors


Avoid asynchronous processing unless and until you have a clear reason for doing it. Actors are lovely abstractions, but even they don't eliminate the inherent complexity of asynchronous processing.

I discovered that truth the hard way. I wanted to insulate the bulk of my application from the one real point of potential instability: the database. Actors to the rescue! Akka actors in particular. And it was awesome.

Hammer in hand, I then set about bashing every nail in view. User sessions? Yes, they could be actors too. Um... how about that access control? Sure, why not! With a growing sense of un-ease, I turned my hitherto simple architecture into a monster: multiple layers of actors, asynchronous message passing, elaborate mechanisms to deal with error conditions, and a serious case of the uglies.

I backed out, mostly.

I retained the actors that were giving me what I needed - fault-tolerance for my persistence code - and turned all of the others into ordinary classes.

May I suggest that you carefully read the Good use case for Akka question/answers? That may give you a better understanding of when and how actors will be worthwhile. Should you decide to use Akka, you might like to view my answer to an earlier question about writing load-balanced actors.


Just riffing, but...

I think if you want to use actors, you should throw away all previous patterns and dream up something new, then maybe re-incorporate the old patterns (controller, dao, etc) as necessary to fill in the gaps.

For instance, what if each User is an individual actor sitting in the JVM, or via remote actors, in many other JVMs. Each User is responsible for receiving update messages, publishing data about itself, and saving itself to disk (or a DB or Mongo or something).

I guess what I'm getting at is that all your stateful objects can be actors just waiting for messages to update themselves.

(For HTTP (if you wanted to implement that yourself), each request spawns an actor that blocks until it gets a reply (using !? or a future), which is then formatted into a response. You can spawn a LOT of actors that way, I think.)

When a request comes in to change the password for user "foo@example.com", you send a message to 'Foo@Example.Com' ! ChangePassword("new-secret").

Or you have a directory process which keeps track of the locations of all User actors. The UserDirectory actor can be an actor itself (one per JVM) which receives messages about which User actors are currently running and what their names are, then relays messages to them from the Request actors, delegates to other federated Directory actors. You'd ask the UserDirectory where a User is, and then send that message directly. The UserDirectory actor is responsible for starting a User actor if one isn't already running. The User actor recovers its state, then excepts updates.

Etc, and so on.

It's fun to think about. Each User actor, for instance, can persist itself to disk, time out after a certain time, and even send messages to Aggregation actors. For instance, a User actor might send a message to a LastAccess actor. Or a PasswordTimeoutActor might send messages to all User actors, telling them to require a password change if their password is older than a certain date. User actors can even clone themselves onto other servers, or save themselves into multiple databases.

Fun!


Large compute-intensive atomic transactions are tricky to pull off, which is one reason why databases are so popular. So if you are asking whether you can transparently and easily use actors to replace all the transactional and highly-scalable features of a database (whose power you are very heavily leaning on in the Java EE model), the answer is no.

But there are some tricks you can play. For example, if one actor seems to be causing a bottleneck, but you don't want to go to the effort of creating a dispatcher/worker farm structure, you may be able to move the intensive work into futures:

val service = actor {  ...  case m: MakeSomethingWithUsers() =>    Futures.future { sender ! myExpensiveOperation(m) }}

This way, the really expensive tasks get spawned off in new threads (assuming that you don't need to worry about atomicity and deadlocks and so on, which you may--but again, solving these problems is not easy in general) and messages get sent along to wherever they should be going regardless.