Microservices are steadily gaining the ground these days. Naturally, there are a good many of tools and frameworks that can be used for building microservices architecture. Among them, Akka stands out with its actor model and claim to provide possibilities for creating reactive systems, which were defined in the Reactive Manifesto and have had much excitement surrounding them lately. Besides, Akka is a set of mature libraries with a really good knowledge base and extensive documentation, which means less time and efforts to start with. All these serve as a good motivation to consider its strengths and weaknesses for application development, which is exactly what this article is about.
What Akka is about
Akka is a free and open-source toolkit from Lightbend, initially released in July, 2009 for building concurrent distributed applications. It was inspired by Erlang and originally written in Scala, but today also has API for Java application development. In 2015, Akka won JAX Award for “the Most Innovative Open Source Technology”.
As mentioned above, Akka is built upon the actor model. An actor is a set of code that communicates with other actors via messages. Actors are hardly something new for this world and were already described by Carl Hewitt in 1973.
For actors to be what they are, namely, completely independent units, each of them needs to cover processing, storage and communication. Actors only receive and send messages, while their internal logic always remains intact and cannot be accessed directly. Actors are not supposed to share any mutable states by default and they check all the incoming messages so that they don’t have any.
For managing the messages, each actor has its own mailbox. In the mailbox, a single thread of control enqueues all the incoming messages in a strictly fixed order and generates one message at a time to the actor.
Processing a new message, the actor can react in three possible ways:
- Delegate the message to other actors.
- Create new actors.
- Decide on the way it’ll behave to the next message. For example, one actor contains number 5 and it receives a message, which says to it to subtract 3. In this case, the actor just designates for the next message it receives that its state is 2.
There are also three possible ways for the actor to send a message:
- Fire-and-forget, or async. The message is sent and no response is required.
- Request-reply, or sync. The message is sent and a response is awaited.
- Request-reply-with-future. The message is sent and a reply is provided later as a specific object.
Owing to the actor model, Akka looks similar to Erlang and Cloud Haskell, but offers many other beneficial features in addition, e.g. with Akka, you can create a singleton actor or assign roles for the actors and create a actor tree hierarchy.
Now we are ready to move forward and discover the good and bad about the use of Akka in Java application development.
Microservices with Akka
Akka claims to allow building reactive miсroservices. Behind this plain term is a combination of microservice architecture and reactive systems principles – namely resilience, scalability, elasticity and message-driven character – mentioned in the famous Reactive Manifesto. Actually, one of the Akka’s creators, Jonas Bonér, is also one of the authors of the Reactive Manifesto. It seems, Akka was set to inherit some ‘reactivity’ at the genetic level.
So, how does it work? With Akka, microservices simply turn into actors and this brings the following ‘reactive’ bonuses to the system:
- Elasticity. The actor model tends to use threads in the most efficient way and avoid their wasting: actors are scheduled with dispatchers that stop the connection when it’s not needed so that not to hold a thread in vain.
- Remote deployment. This comes as a merit of actors’ ability to communicate via URL (AkkaRef). Every actor has its URL where the messages are sent and there is no need to keep in mind the physical location of the ‘interlocutor’ (whether it’s running in the same JVM or in a different JVM).
- Resilience. Firstly, resilience to failures - actors form tree hierarchies, where a parent actor takes care of a crashed child block: it receives a notification and decides what to do next (resume, restart, stop its child or stop itself to transfer responsibility for error-handling to the next level), which ensures failure tolerance and self-recovery. Moreover, as actors represent completely isolated units and don’t share any mutable states, the stop of one won’t affect other actors and they will be able to continue their work as usual. Secondly, resilience to high-load - in case some resource becomes unavailable, the server won’t keep hassling it but rather restart itself with ‘an exponentially increasing back-off’ (as the description on the Akka website suggests) until the resource is able to answer. This will allow avoiding a series of denials and will give the time to the server to manage the load and resume its work later.
Meanwhile, the future users, in their turn, benefit from improved UX – as async streams make an application work faster and smoother, improved responsiveness – as in case of any outer or inner system problems there will always be a high-level actor that communicates response to a user not to leave them in the dark, improved performance – as the system is split into independent units and you can easily scale its performance, if needed.
And for worse
However, everything comes with its trade-offs, and Akka is not an exception. The thoughtless use of Akka and lack of team’s experience with this framework may result in rather disastrous mistakes in a future product. The challenges, which you’re likely to encounter during application development employing Akka, involve:
- Hidden shared states. As Akka sticks to the share-nothing approach and the actors are not supposed to share any mutable states, it doesn’t provide any possibility to synchronize threads. However, it is quite easy to overlook some mutable states or, as it often happens with Java developers, get tempted to introduce indirect synchronization. Both options can easily lead to deadlocks or even ruin the whole system.
- Complicated distributed transactions. Message delivery is not guaranteed. Akka usually uses the at-most-once approach as the easiest and the simplest way of delivery, which means that a message is delivered once or not delivered at all. So, in case the event queue happens to be overloaded, the message won’t reach its destination. Consequently, the team should pay special attention in case the message loss is crucial for distributed transactions.
- Actors everywhere. It’s easy to start to see actors everywhere and recklessly introduce them wherever possible. Though actors are lightweight and it can hardly cause any memory overuse, it’s not the best idea to introduce complexity where there is no need to, as it makes the code more difficult to read and maintain.
- Troubled communication with other components – if the actors are to deal with some other components inside or outside your system, keep in mind that these components can be not reactive (dependent, sync, blocking) and there can be a point in introducing some mediator between them to manage encapsulation and async calls.
Akka is an actor-based framework that claims to bring the principles of ‘reactivity’ into microservices architecture. To a certain extent, Akka’s actors can introduce significant benefits to the system, such as high-load tolerance, easy scalability, strong failure resistance and more efficient resources use. However, a sound experience is required to succeed with Akka reactive microservices architecture as there are many sharp edges you can come across at the development process (for example, too many actors, distributed transactions, shared mutable states) and insufficient competence in addressing these issues may lead as far as to a total project failure.