Note that there are some explanatory texts on larger screens.

plurals
  1. POTransferring typical 3-tier architecture to actors
    primarykey
    data
    text
    <p>This question bothers me for some time now (I hope I'm not the only one). I want to take a typical 3-tier Java EE app and see how it possibly can look like implemented with actors. I would like to find out whether it actually makes any sense to make such transition and how I can profit from it if it does makes sense (maybe performance, better architecture, extensibility, maintainability, etc...).</p> <p>Here are typical Controller (presentation), Service (business logic), DAO (data):</p> <pre><code>trait UserDao { def getUsers(): List[User] def getUser(id: Int): User def addUser(user: User) } trait UserService { def getUsers(): List[User] def getUser(id: Int): User def addUser(user: User): Unit @Transactional def makeSomethingWithUsers(): Unit } @Controller class UserController { @Get def getUsers(): NodeSeq = ... @Get def getUser(id: Int): NodeSeq = ... @Post def addUser(user: User): Unit = { ... } } </code></pre> <p>You can find something like this in many spring applications. We can take simple implementation that does not have any shared state and that's because does not have synchronized blocks... so all state is in the database and application relies on transactions. Service, controller and dao have only one instance. So for each request application server will use separate thread, but threads will not block each other (but will be blocked by DB IO).</p> <p>Suppose we are trying to implement similar functionality with actors. It can look like this:</p> <pre><code>sealed trait UserActions case class GetUsers extends UserActions case class GetUser(id: Int) extends UserActions case class AddUser(user: User) extends UserActions case class MakeSomethingWithUsers extends UserActions val dao = actor { case GetUsers() =&gt; ... case GetUser(userId) =&gt; ... case AddUser(user) =&gt; ... } val service = actor { case GetUsers() =&gt; ... case GetUser(userId) =&gt; ... case AddUser(user) =&gt; ... case MakeSomethingWithUsers() =&gt; ... } val controller = actor { case Get("/users") =&gt; ... case Get("/user", userId) =&gt; ... case Post("/add-user", user) =&gt; ... } </code></pre> <p>I think it's not very important here how Get() and Post() extractors are implemented. Suppose I write a framework to implement this. I can send message to controller like this:</p> <pre><code>controller !! Get("/users") </code></pre> <p>The same thing would be made by controller and service. In this case the whole workflow would be synchronous. Even worse - I can process only one request at time (in meantime all other requests would land in controller's mailbox). So I need to make it all asynchronous.</p> <p><strong>Is there any elegant way to perform each processing step asynchronously in this setup?</strong></p> <p>As far as I understand each tier should somehow save the context of the message it receives and then send message to the tier beneath. When tier beneath replies with some result message I should be able to restore initial context and reply with this result to the original sender. Is this correct?</p> <p>Moreover, at the moment I have only one instance of actor for each tier. Even if they will work asynchronously, I still can process in parallel only one controller, service and dao message. This means that I need more actors of the same type. Which leads me to LoadBalancer for each tier. This also means, that if I have UserService and ItemService I should LoadBalace both of them separately.</p> <p>I have feeling, that I understand something wrong. All needed configuration seems to be overcomplicated. What do you think about this?</p> <p>(PS: It would be also very interesting to know how DB transactions fit into this picture, but I think it's overkill for this thread)</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload