Building microservices: A reactive framework comparison
Most of us have heard the buzz about microservices and their benefits. In a previous post, I discussed microservices and when it may make sense to orchestrate vs. react when implementing them. If you choose to react, there are a number of options available. Reactive programming is a hot topic right now as both Java 9 and Spring Framework 5 were recently released and have reactive capabilities built in. There are many additional options for reactive programming depending on your use case. I tend to think of them in four different categories.
- Javascript libraries such as Angular.js, React.js, Ractive.js, or Node.js that can be used to build front-end reactive applications.
- Languages that have native reactive capabilities. We mentioned Java 9 and Spring Framework 5. Several others (but not limited to) include Clojure, Scala, and GoLang.
- Reactive frameworks that run on top of the JDK and implement the reactive streams specification (which we will discuss in a bit) such as Project Reactor, Akka, Vert.x, and Ratpack.
- Reactive Extensions also known as ReactiveX that provide many reactive capabilities for many languages with one of the most popular being RxJava
There are some key principles that each of these categories is centered around. In 2013, the Reactive Manifesto was written, establishing the four key principles of reactive systems: Responsive, Resilient, Message Driven, and Elastic
Reactive systems should be responsive meaning they should always respond in a timely manner. They should be resilient meaning they should remain responsive when failure happens. They should be message driven and enable asynchronous message passing that establishes a boundary between components (which can help keep them de-coupled and location transparent). Lastly, they should be elastic to enable components to dynamically scale and contract based on the workload demand, efficiently using resources.
In late 2013, another key piece of work began regarding the Reactive Streamsspecification. The main benefit with this specification is it enables reactive libraries that implement it to be interoperable. Specifically, it enables the sequences, cancellation, and backpressure to be composed across library boundaries while allowing the end-user to switch between implementations at their desire. Reactive Streams was released in April 2015, and became part of JDK9 in 2017. There are several reactive frameworks that run on top of the JDK and implement the Reactive Streams specification such as Project Reactor, Akka, Vert.x, and Ratpack.
According to openhub.net, Akka had its first commit in 2009, Vert.x and Reactor in 2013, and Ratpack in 2014. Akka and Vert.x are two commonly used reactive frameworks that run on top of the JDK. Let’s take a deeper look at both and compare/contrast them across the below dimensions:
- Microservice hierarchy and inter-communication
- Delivery mechanisms & recovery capabilities
- Open source perspective and supported languages
- Add-on components along with monitoring options
- Microservice Hierarchy and Inter-Communication
Earlier I mentioned the message driven principle of the Reactive Manifesto, which enables asynchronous message passing between microservices. Both Akka and Vert.x provide a built-in messaging mechanism that has similarities and differences. First, we should discuss the hierarchy paradigm as this is a significant difference between the two.
Akka is built off the actor model (originated in 1973 per Carl Hewitt) which provides a model for handling concurrent computation. In Akka, the unit of execution is the Actor and your microservices are built as actors. An actor is lightweight and there can be several million actors per GB of heap memory. The actor is an object that encapsulates state and behavior, and communicates exclusively by exchanging messages which are placed into the recipient’s mailbox. Below is a diagram that illustrates this.
Actors are part of an actor system and can be arranged in a hierarchy. Below is an example of creating a top-level actor in an actor system:
This means they can split up and delegate tasks to child actors. The child actor is supervised and delegate their failure back to their parent actor. Actors can send messages to other actors in either a request/response pattern known as an “ask” or in a fire and forget mode known as a “tell”. Below is an example of an ask:
Vert.x is based on a flat hierarchy and does not implement the actor model. Instead, it is based on the event loop model. This is one of the first differences to call-out from Akka.
The unit of execution in Vert.x is known as a Verticle. It processes incoming evveents over event-loops, which are typical in asynchronous programming models. Below is a diagram that illustrates this:
Now let’s look at this in code:
A verticle is passed configuration information and can be deployed multiple times, as seen below:
Like Akka, Vert.x provides an internal messaging mechanism through the event bus, which is how verticles communicate through asynchronous message passing:
How the verticles communicate is another difference to call out between Akka and Vert.x. In Akka, inter-communication between actors can happen with the ask and tell methods, which are point-to-point, meaning an actor must know who it wants to send a message to. Akka does also have an out of the box publish and subscribe capability that uses an EventStream construct. Publish and subscribe means a message can be published to the event stream without the publisher knowing who is going to receive the message. In this approach, Actors subscribe to channels that equate to objects (in Java), so an actor specifies a specific class to receive any message of that class or sub-class.
Similar to Akka, Vert.x also supports point-to-point and a publish-subscribe pattern. A difference is the message passing of Vert.x is language agnostic and uses JSON to pass data, whereas Akka uses specific objects from either Scala or Java. Additionally, the subscriber in Akka needs to know the class of the object it wants to receive, where in Vert.x that is not necessary. Below is a Vert.x example:
Register Event Handler:
Sender:
Receiver:
Delivery mechanisms and recovery capabilities
Being resilient is another principle taken from the Reactive Manifesto. How well can something recover from a failure state and continue to be responsive during that time?
Akka provides an additional component known as Akka Persistence that can be used to store the internal state of an actor. This enables the state of an actor to be recovered if it crashes. Akka persistence uses an event sourcing pattern, which stores any changes to an actor’s state in an event store. Events in the event store are immutable. The actor is recovered by replaying the events from the event store.
Below is an Akka Persistence example from the akka.io website that supports event sourcing with the AbstractPersistentActor class. The persist method is used by an actor that extends this class to persist and handle events. The behavior is defined by implementing createReceiveRecover and createReceive.
Currently, Vert.x does not provide out-of-the-box recovery capabilities. From a message delivery reliability standpoint, Akka provides at-most-once out of the box and provides at-least-once when Akka Persistence is leveraged. Similar to Akka, Vert.x provides at-most-once, but does not provide capabilities for at-least-once. At-most-once means that data loss is possible. At-least-once guarantees the message will be delivered at least one time at a minimum.
From a message ordering perspective, Akka can guarantee message ordering when one single actor directly sends a message to another actor using the tell operator. If there are multiple actors sending messages to a common destination, they will be ordered per sender, but there are no global ordering guarantees. Vert.x will also deliver messages to any handler in the same order that they were sent from a particular sender.
Open source perspective and supported languages
Both Akka and Vert.x are open source projects. Akka supports Scala and Java while Vert.x supports Java, Groovy, Ruby, JavaScript, Ceylon, Scala, and Kotlin.
From a maturity perspective, Akka has been around the longest, having started in 2009 and is 8.5 years old at the time of this article. Vert.x started in 2013 and is around 4 years old at the time of this article. Lightbend is the main company that provides the core contributors for Akka, while RedHat has a number of full-time developers working on Vert.x.
As of September 2017 on openhub.net, Akka has very high activity with 1554 commits in the last year and 601 total contributors:
Vert.x also has high activity with 704 commits in the last year and 124 total contributors:
Add-on components and monitoring options
Earlier I mentioned the Akka Persistence component that can be used for additional recovery capabilities. Akka also has additional components including, but not limited to, Akka Clustering and Akka Streams. Akka Clustering helps with the location transparency aspect of actors. If an actor needs to send a message to another actor, it uses an ID (instead of an ActorRef) and Akka Clustering takes care of routing that message to the right cluster. If an actor dies, clustering will take care of restarting that actor. Akka Streams is a streaming abstraction on Akka actors. Dean Wampler has a presentation that compares Akka Streams to other streaming technologies. He mentions it is good for low latency and complex event processing use cases. Akka Streams can handle very high volume in terms of the number of messages it can process per second and it has good support for parallelization of workloads, but it doesn’t support distributed materialization like Spark and Flink do. With Vert.x, there are not any additional components.
From a monitoring perspective, DataDog (see screenshot below) and OpsClarity are at least two options for monitoring Akka.
With Vert.x, there are several options, including Hawkular (see screenshot below) and DropWizard (JMX).
Summary
In this article, we discussed some historic context for reactive programming while also touching on some of the many similarities and differences between Akka and Vert.x. In general, some of the reasons to use Akka vs. Vert.x include:
- Prefer to use the Actor hierarchy model for your microservices
- Are good with using language specific objects to pass message data between Actors
- Need the recovery and persistence capabilities that come along with Akka Persistence so the internal state of an actor can be persisted and recovered in the event of a crash
- If you need at-least-once delivery (Akka Persistence)
In general, some of the reasons to Vert.x vs. Akka include:
- Prefer to use a flat hierarchy and not the Actor model
- Prefer to use language neutral JSON vs. language specific objects when passing message data
- Need wider support for languages outside of Scala and Java (i.e. Groovy, Ruby, JavaScript, Ceylon, Kotlin)
- Are good with at-most-once delivery (which can result in message loss)
The specific needs of your project will help you determine which one is the right choice for you and I encourage you to do a prototype on each. The good news is — both implement the Reactive Streams specification which means you can use them together if that option makes sense for your use case.
Note — The analysis in this article was based on Akka 2.5.4 and Vert.x v3.4.2