Search code examples
javaconcurrencyakkaquasar

Differences Message Driven Application between simple method calls


I've been reading something about, found some libraries which really messed with my thoughts, like Akka, Quasar, Reactor and Disruptor, Akka and Quasar implements the Actor Pattern and the Disruptor is a Inter-Thread Messaging library, and Reactor is based on . So what are the advantages, use cases for using a message driven architecture over simple method calls?

Given a RabbitMQ queue listener, I receive a message from the method, decide which Type the RabbitMQ message is (NewOrder,Payment,...).

With a Message Driven library I could do.

Pseudo code:

actor.tell('decider-mailbox',message)

Which basically says "I'm putting this message here, when you guys can handle it, do it") and so on until it gets saved. And the actor is ready again to receive another message

But with directly calling the method like messageHandler.handle(message), wouldn't be better and less abstracted ?


Solution

  • The Actors Model looks a lot like people working together; it is based on message-passing but there is much more to it and I'd say that not all message-passing models are the same, for example Quasar actually supports not only Erlang-like actors but also Go-like channels which are simpler but don't provide a fault-tolerance model (and fibers BTW, that are just like threads but much more lightweight, which you can use even without any message-passing at all).

    Methods/functions follow a strict, nestable call-return (so request-response) discipline and usually don't involve any concurrency (at least in imperative and non-pure functional languages).

    Message passing instead, very broadly speaking, allows looser coupling because doesn't enforce a request-response discipline and allows the communicating parties to execute concurrently, which also helps in isolating failures and in hot-upgrades and generally maintenance (for example, the Actors Model offers these features). Often message passing will also allow looser data contracts by using a more dynamic typing for messages (this is especially true for the Actors Model where each party, or actor, has a single incoming channel, that is his mailbox). Other than that the details depends a lot on the messaging model/solution you're considering, for example the communication channels can synchronize the interacting parts or have limited/unlimited buffering, allow multiple source and/or multiple producers and consumers etc.

    Note that RPC is really message passing but with a strict request-response communication discipline.

    This means that, depending on the situation, one or the other may suit you better: methods/functions are better when you're in a call-return discipline and/or you're simply making your sequential code more modular. Message-passing is better when you need a network of potentially concurrent, autonomous "agents" that communicate but not necessarily in a request-response discipline.

    As for the Actors Model I think you can build more insight about it for example by reading the first part of this blog post (notice: I'm the main author of the post and I'm part of the Parallel Universe - and Quasar - development team):

    The actor model is a design pattern for fault-tolerant and highly scalable systems. Actors are independent worker-modules that communicate with other actors only through message-passing, can fail in isolation from other actors but can monitor other actors for failure and take some recovery measures when that happens. Actors are simple, isolated yet coordinated, concurrent workers.

    Actor-based design brings many benefits:

    • Adaptive behaviour: interacting only through a message-queue makes actors loosely coupled and allows them to:
      • Isolate faults: mailboxes are decoupling message queues that allow actor restart without service disruption.
      • Manage evolution: they enable actor replacement without service disruption.
      • Regulate concurrency: receiving messages very often and discarding overflow or, alternatively, increasing mailbox size can maximize concurrency at the expense of reliability or memory usage respectively.
      • Regulate load: reducing the frequency of receive calls and using small mailboxes reduces concurrency and increases latencies, applying back-pressure through the boundaries of the actor system.
    • Maximum concurrency capacity:
      • Actors are extremely lightweight both in memory consumption and management overhead, so it’s possible to spawn even millions in a single box.
      • Because actors do not share state, they can safely run in parallel.
    • Low complexity:
      • Each actor can implements stateful behaviour by mutating its private state without worrying about concurrent modification.
      • Actors can simplify their state transition logic by selectively receiving messages from the mailbox in logical, rather than arrival order.