Mutiny is a novel reactive programming library.
It provides a simple but powerful asynchronous development model that lets you build reactive applications.
Mutiny can be used in any Java application exhibiting asynchrony. From reactive microservices, data streaming, event processing to API gateways and network utilities, Mutiny is a great fit.
We are living in a distributed world.
Most of the applications built nowadays are distributed systems. The Cloud, IoT, microservices, mobile application, even simple CRUD applications are distributed applications.
Still, developing distributed systems is hard!
Communications in distributed systems are inherently asynchronous and unreliable. Anything can go wrong, anytime, and often with no prior notice.
Network disruptions, unavailable services, software, or hardware failures are just a tiny subset of the wide variety of failures that can happen in distributed systems.
Correctly building distributed applications is a considerable challenge, as it requires re-assessing almost everything we know from traditional software development.
Most classic applications use a synchronous development model. Synchronous code is easy to reason about, more comfortable to write and read than asynchronous code, but it has some hidden cost. This cost emerges when building I/O intensive applications, quite common in distributed applications.
In general, these traditional applications assign one thread per request, and so they handle multiple concurrent requests with multiple threads. When the request processing needs to interact over the network, it uses that worker thread, which blocks the thread until the response has been received. This response may never come, so you need to add watchdogs handling timeouts and other resilience patterns. And, to handle more requests concurrently, you need to create more threads.
Threads come at a cost. Each thread requires memory, and the more threads you have, the more CPU cycles are used to handle the context switches. Thus, this model ends up being costly, limits the deployment density, and on the Cloud means that you pay bigger bills.
Fortunately, there is another way, and it relies on non-blocking I/O, an efficient way to handle I/O interactions that do not require additional threads. While applications using non-blocking I/O are more efficient and better suited for the Cloud’s distributed nature, they come with a considerable constraint: you must never block the I/O thread. Thus, you need to implement your business logic using an asynchronous development model.
I/O is not the only reason why asynchronous is essential in Today’s systems. Most of the interactions in the real world are asynchronous and event-driven. Representing these interactions using synchronous processes is not only wrong; it also introduces fragility in your application.
Asynchronous is a significant shift. Mutiny helps you to take the plunge.
Mutiny is a reactive programming library. If you look on Wikipedia for reactive programming, you will find the following definition:
Reactive Programming combines functional programming, the observer pattern, and the iterable pattern.
While correct, we never found this definition very helpful. It does not convey clearly what’s reactive programming is all about. So, let’s make another definition, much more straightforward:
Reactive programming is about programming with data streams.
That’s it. Reactive programming is about streams and especially, observing them. It pushes that idea to its limit: with reactive programming, everything is a data stream.
With reactive programming, you observe streams and implement side-effects when something flows in the stream:
It’s asynchronous by nature as you don’t know when the data is going to be seen. Yet, reactive programming goes beyond this. It provides a toolbox to compose streams and process events.
There are other reactive programming libraries out there. In the Java world, we can mention Project Reactor and Rx Java.
So, what makes Mutiny different from these two well-known libraries? The API!
As said above, asynchronous is hard to grasp for most developers, and for good reasons. Thus, the API must not require advanced knowledge or add cognitive overload. It should help you design your logic and still be intelligible in 6 months.
To achieve this, Mutiny is built on three pillars:
Event-Driven - with Mutiny, you listen for events and handle them,
API Navigability - based on the event-driven nature, the API is built around the type of events and drive the navigation based on the kind of event you want to handle,
Simplicity - Mutiny provides only two types (
Uni), which can handle any kind of asynchronous interactions.
When you use Mutiny, you design a pipeline in which the events flow. Your code observes these events and react.
Each processing stage is a new pipe you append to the pipeline. This pipe may change the events, create new ones, drops, buffers, whatever you need.
In general, events flow from upstream to downstream, from source to sinks. Some events can swim upstream from the sinks to the source.
Events going from upstream to downstream are published by
Publishers and consumed by (downstream)
Subscribers, which may also produce events for their own downstream, as illustrated by the following diagram:
Four types of events can flow in this direction:
Subscribed - indicates that the upstream has taken into account the subscription - more on this later,
Items - events containing some (business) value,
Completion - event indicating that the source won’t emit any more items,
Failure - event telling that something terrible happened upstream and that the source cannot continue to emit items.
Completion are terminal events.
Once they are sent, no more items will flow.
Three types of events flow in the opposite direction, i.e. from downstream to upstream:
Subscription - event sent by a subscriber to indicate its interest for the events (such as items) emitted by upstream
Requests - event sent by a subscriber indicating how many items event it can handle - this is related to back-pressure
Cancellation - event sent by a subscriber to stop the reception of events.
In a typical scenario:
A subscriber subscribes to the upstream - the upstream receive the
subscription request, and when initialized sends the
subscribedevent to the subscriber
The subscriber gets the
subscribedevent with a subscription used to emit the
The subscriber sends a
requestevent indicating how many items it can handle at this moment; it can request 1, n, or infinite.
The publisher receiving the
requestevent starts emitting at most n item events to the subscriber
The subscriber can decide at any time to request more events or to cancel the subscription
request event is the cornerstone of the back-pressure protocol.
A subscriber should not request more than what it can handle, and a publisher should not emit more items than the amount of request received.
Mutiny’s back-pressure is based on Reactive Streams.
Don’t forget to subscribe
If no subscriber subscribes, no items would be emitted. More importantly, nothing will ever happen. If your program does not do anything, check that it subscribes, it’s a very common error.
Mutiny is an event-driven API.
For each type of event, there is an
on associated method that lets you handle this specific event.
Multi<String> source = Multi.createFrom().items("a", "b", "c"); source .onItem() // Called for every item .invoke(item -> log("Received item " + item)) .onFailure() // Called on failure .invoke(failure -> log("Failed with " + failure)) .onCompletion() // Called when the stream completes .invoke(() -> log("Completed")) .onSubscription() // Called when the upstream is ready .invoke(subscription -> log("We are subscribed!")) .onCancellation() // Called when the downstream cancels .invoke(() -> log("Cancelled :-(")) .onRequest() // Called on downstream requests .invoke(n -> log("Downstream requested " + n + " items")) .subscribe() .with(item -> log("Subscriber received " + item));
Of course, the methods presented in this snippet are not very interesting, although they are quite useful to trace what’s going on.
You can see a common pattern emerging:
onEvent().invoke(event -> ...)
invoke is just one of the methods available.
Each group proposes methods specific to the type of event. For example,
onCompletion().continueWith and so on.
Mutiny defines two reactive types:
Multi- represents streams of 0..* items (potentially unbounded)
Uni- represents streams receiving either an item or a failure
The Mutiny name comes from the contraction of
Multi are asynchronous types.
They receive and fire events at any time.
You may wonder why we make the distinction between
Uni is a
In practice, you don’t use
Multis the same way.
The use cases and operations are different.
Unidoes not need the complete ceremony presented above as the request does not make sense.
subscribeevent expresses the interest and triggers the computation, no need for an additional request.
Unican handle items having a
nullvalue (and has specific methods to handle this case).
Multidoes not allow it (because the Reactive Streams specification forbids it).
Publisherwould be a bit like having
In other words,
can receive at most 1
itemevent, or a
cannot receive a
nullin the case of 0 items)
cannot receive a
The following snippet shows how you can use
Multi.createFrom().items("a", "b", "c") .onItem().transform(String::toUpperCase) .subscribe().with( item -> System.out.println("Received: " + item), failure -> System.out.println("Failed with " + failure) ); Uni.createFrom().item("a") .onItem().transform(String::toUpperCase) .subscribe().with( item -> System.out.println("Received: " + item), failure -> System.out.println("Failed with " + failure) );
The following paper discusses the performance and costs of Mutiny and friends:
Julien Ponge, Arthur Navarro, Clément Escoffier, and Frédéric Le Mouël. 2021. Analysing the Performance and Costs of Reactive Programming Libraries in Java. In Proceedings of the 8th ACM SIGPLAN International Workshop on Reactive and Event-Based Languages and Systems (REBLS ’21), October 18, 2021, Chicago, IL, USA. ACM, New York, NY, USA, 10 pages. (PDF)