In our previous blog, we tried to explore the upcoming features of Java 9. So this time, we will focus on Scala. In this article, we will be looking into a new Reactive programming framework for Scala applications — Reactors.io.
Reactors.io fuses the best parts of functional Reactive programming and the Actor Model.
Reactors allow you to create concurrent and distributed applications more easily by providing correct, robust, and composable programming abstractions. Primarily targeting the JVM, the Reactors framework has bindings for both Scala and Java.
Setting Up Reactors.io
To get started with Reactors.io, you should grab the latest snapshot version distributed on Maven. If you are using sbt, add the following to your project definition :
Then, simply import the io.reactors package:
import io.reactors._ and you are ready to go.
Areas where Actors are lacking:
- First, in the basic actor model, Actors cannot simultaneously contain multiple message entry points.
- Second, Actors cannot await specific combinations of messages.
- Third, receive is not a first-class entity. Instead of a value that can be passed to and returned from functions, receive is a static construct.
Combined, these limitations make protocol composition within a single Actor cumbersome, convolute abstractions, and restrict code reuse. The model is sufficiently low-level to express arbitrary message protocols. Composing these protocols is the key to high-level abstractions. Therefore, it is difficult to reuse or compose message protocols with actors. Reactors simplify protocol composition with first-class typed channels and event streams.
Reactors comprise an event-driven programming model. Clients can subscribe and react to event streams, which asynchronously produce events. This makes the Reactor model well-suited for interactive applications, but also ideal for building distributed software, which is typically characterized by latency and asynchrony.
After an event arrives, it gets forwarded to the observers of that event stream.
Concurrent and Distributed
The Reactor Model organizes computations into basic units of concurrency called Reactors. Inside each Reactor, computation is sequential.
At the same time, the Reactor Model is quite location-transparent. This means that you can develop and test the program on a single machine, then seamlessly deploy it on multiple machines that are connected by a network.
While the concept of event streams and callbacks resembles the principles found in traditional Actor systems, event streams are first-class, functional values.
They can be declaratively composed and manipulated in a similar fashion to Scala collections or Java streams. First-class events streams and signals allow a better separation of concerns.
Components are expressed as event stream operations, which can be packaged into modules and later reused or further composed. The subtle interplay between channels and event streams allows composing powerful event exchange protocols in a modular fashion.
Actors vs. Reactors
|Actors receive messages||Reactors receive events|
|An Actor in a particular state has only a single point in its definition where it can receive a message.||A Reactor can receive an event from many different sources at any time.|
|Receive all types of messages||Receive events only of Defined(T) Type in Reactor.apply[T] (Eg. String)|
|Difficult to reuse or compose message protocols with Actors.||Comparatively, protocol composition is much simpler with Reactors.|
Reactor System Basics
Event streams are objects that may asynchronously publish events. Event streams are entities that propagate events within a Reactor, and they cannot be shared between Reactors. An event stream is associated with every channel. Event streams can be used as we use collections in Scala.
Channels are Reactor’s means of communicating with its environment. Every Reactor is created with a default channel called main, which is usually sufficient. Every channel is owned by a single Reactor. Any Reactor can send an event to a channel, but only the channel owner can process that event.
Schedulers in the Reactor Model basically help in assigning a thread to events so that they can be executed, i.e. this helps in Resource Management. Every Reactor system is bundled with a default scheduler and some additional predefined schedulers. When a Reactor is started, it uses the default scheduler, unless specified otherwise.
Every Reactor goes through a certain set of stages during its lifetime, jointly called the Reactor Lifecycle. When the Reactor enters a specific stage, it emits a lifecycle event. All lifecycle events are dispatched on a special daemon event stream called sysEvents. Every Reactor is created with this event stream.
Reactor System Services
The Logging Service
The Logging service is used to print logging messages to the standard output.
The Channels Service
The Channels service provides an event-driven view of all channels that exist in the current Reactor system. This allows polling the channels that are currently available or waiting until a channel with a specific name becomes available.
The Clock Service
The Clock service is capable of producing time-driven events — for example, timeouts, countdowns, or periodic counting. The Clock service uses a separate timer thread under the hood, which sends events to the Reactor when the timer thread decides it is time to do so.
Protocols are sets of predefined rules provided by the Reactor system in order to ease understanding and also to maintain certain standards for successfully creating concurrent and distributed applications. The communication protocols are composed of basic abstractions and simpler protocols.
There are various protocols provided by the Reactor Model. The basic among them include the following.
Standard Server-Client Protocol
The Standard Server-Client Protocol relies solely on the basic primitives provided by the Reactors framework. This protocol is the basis for other protocols, and it can also be modified or customized easily according to your requirements, thus turning it into a Custom Server-Client Protocol.
With the Router Protocol, events coming to a specific channel are routed between a set of target channels according to some user-specified policy. (Round-Robin, Hash, Random)
It's useful for:
- Data replication and sharding
Two-Way Communication Protocol
In two-way communication, two parties obtain a link handle of the type TwoWay, which allows them to simultaneously send and receive an unlimited number of events — until they decide to close this link.
The TwoWay object contains an output channel (Output) and an input event stream (Input).
To close the link, the TwoWay object contains a subscription object called Subscription, which is used to close the link and free the associated resources.
Limitations: Not Everything Is Perfect
Reactors may overcome many problems of the traditional Actor system, but they still lack certain features. A couple of important things to note are:
Missing resilience: Here, resilience means the ability to overcome from a crash or problem. There is no supervision, as provided by Akka.
Reactors.io is still under development, and many features are still missing (and hopefully will be updated soon).
That's is all for Reactors for now. For more, you can refer to the official Reactors.io site: Reactors.io.
And for the code reference, you can find all the related code snippets on GitHub.
I hope this blog helps. Stay tuned for more!