Book Image

Scala Reactive Programming

By : Rambabu Posa
Book Image

Scala Reactive Programming

By: Rambabu Posa

Overview of this book

Reactive programming is a scalable, fast way to build applications, and one that helps us write code that is concise, clear, and readable. It can be used for many purposes such as GUIs, robotics, music, and others, and is central to many concurrent systems. This book will be your guide to getting started with Reactive programming in Scala. You will begin with the fundamental concepts of Reactive programming and gradually move on to working with asynchronous data streams. You will then start building an application using Akka Actors and extend it using the Play framework. You will also learn about reactive stream specifications, event sourcing techniques, and different methods to integrate Akka Streams into the Play Framework. This book will also take you one step forward by showing you the advantages of the Lagom framework while working with reactive microservices. You will also learn to scale applications using multi-node clusters and test, secure, and deploy your microservices to the cloud. By the end of the book, you will have gained the knowledge to build robust and distributed systems with Scala and Akka.
Table of Contents (16 chapters)

Marble diagrams

In this section, you will learn what Marble diagrams are, why we really need them or what their benefits are, and the rules we need to follow to draw these Marble diagrams?

We will also discuss some of the important FP operators using Marble diagrams in the following sections.

What is a Marble diagram?

A Marble diagram is a diagram used to visualize an FRP Data Transformation in a very nice and easy-to-understand form. (Refer to the next section to understand what an FRP Data Transformation is.)

Using these diagrams, we can understand the following things very well about an FRP Data Transformation:

  • A data element or a set of data elements are emitted or produced from a source (Producer, Publisher, or a data stream)
  • A data element or a set of data elements flow in that data stream, that is, they are produced from the source and flow through that data stream
  • What data transformation is happening in that data stream?
  • How that data stream is picking each element and how it is performing that data transformation for each and every element or only a set of elements
  • The way it is preparing the final results after performing that data transformation
  • How that data stream is sending the final results to the destination (another Producer, Publisher or a data stream, or maybe a Consumer or Subscriber)

Before starting the discussion about some sample Marble diagrams, I feel it's good to know what an FRP Data Transformation is. Let's define it now.

Data transformation

A data transformation is an operation (or operator), which is applied on a set of source data elements or all data elements in a data stream and produces resultant data elements to send to another data stream:

As we use this data transformation to represent a functional operation, Reactive operation, or both, we also call this as an FP operator or RP operator, or FRP operator. It is also known as a Reactive Stream operator, Functional Reactive operator, or Data Flow operator.

Some of the FP operators are map, flatMap, reduce, fold, and filter. They are also known as combinators in the Scala World. Refer to Chapter 2, Functional Scala, to understand what a Scala combinator is.

We will pick up some of the important and useful FRP operators and discuss them with Marble diagrams in subsequent sections.

Benefits of Marble diagrams

The following are the benefits of Marble diagrams in the Functional and Reactive World:

  • They represent a simple or complex FRP operation in a simple and easy-to-understand way
  • Pictorial representation of an FRP operation explains better than a text description
  • They help us in understanding and solving complex problems in Functional and Reactive ways
  • Even FRP beginners can understand those FRP operations easily
  • It is easy to design source, data streams, and destinations
  • It is easy to understand how to compose and use multiple FRP operations

Rules of Marble diagrams

To draw a Marble diagram to represent an FRP operation in a pictorial form, we should follow these rules:

A horizontal line represents a data stream:

That horizontal line represents a duration or time from left to right to perform that data transformation operation.

Some symbols (such as circles, diamonds, and rectangles) on top of that horizontal line are used to represent the data elements coming from a source data stream or resulting data elements on the destination data stream:

We can use any symbol to represent the data elements in a Marble diagram:

The big rectangular box in the center of a Marble diagram represents the actual data transformation (or functional operation, Reactive operation, or FRP operation) logic:

The top horizontal line represents the Source Data Stream, and the bottom horizontal line represents the Destination Data Stream (or resulting data stream):

The vertical line (|) on top of the horizontal line represents the data stream completing successfully:

A cross mark (X) on top of the horizontal line represents that the data stream is completed with errors:

We can say that these are the properties of a Marble diagram. We will explore these rules with some useful examples in subsequent sections.

Important FRP operators

We will draw Marble diagrams for the following important and frequently used FPP operations:

  • The map() function
  • The flatMap() function
  • The merge() function
  • The filter() function
  • The reduce() function
  • The concat() and sorted() functions

As a Scala developer, I hope you are clear about how these functions (or operations or operators) work. If you are new to these functions, refer to Chapter 2, Functional Scala, which explains these functions in detail with some simple examples.

Let's start with the map() function first.

FRP – the map() function Marble diagram

In Scala, the map() function performs the following steps one by one:

  1. Take each element from the source container.
  2. Apply the given function.
  3. Create a new container of the same type as the destination container. Here, container means any data structure that can hold more than one element; for instance, a Collection, Option, Either, and so on.

In the Reactive World, we can call this container a data stream, as it emits or consumes the data (or data elements).

Here's the Scala sample code for the map() function:

scala> val numList = List(1,2,3,4,5) 
numList: List[Int] = List(1, 2, 3, 4, 5) 
 
scala> val squaredNumList = numList.map( x => x*x ) 
squaredNumList: List[Int] = List(1, 4, 9, 16, 25) 

Here, the map() function picks up each element from a number list, squares it, and creates a new list with the resultant numbers. Let's represent this map() functional operation in a pictorial form using a Marble diagram:

Here, the source data stream is a List (1,2,3,4,5) and the destination or resulting data stream is also a list with the squared value, that is, List (1, 4, 9, 16, 25).

The data transformation or functional operator is map( x => x*x ).

FRP – the flatMap() function Marble diagram

We use the flatMap() function when we want to map a data stream of data stream elements into a plain data stream element.

For instance, List[List[Int]] to List[Int], as illustrated here:

scala> val numList = List(List(1,2,3),List(4,5),List(6)) 
numList: List[List[Int]] = List(List(1, 2, 3), List(4, 5), List(6)) 
 
scala> numList.map(x => x) 
res9: List[List[Int]] = List(List(1, 2, 3), List(4, 5), List(6)) 
 
scala> numList.flatMap(x => x) 
res10: List[Int] = List(1, 2, 3, 4, 5, 6) 

In this case, the map() function does not give us the expected results, so we only use flatMap(). We can represent this flatMap() function in a Marble diagram as follows:

FRP – the merge() function Marble diagram

Suppose we have a code like this to merge two data streams of the same type:

scala> val list1 = List(1,24) 
list1: List[Int] = List(1, 24) 
 
scala> val list1 = List(1,2,4) 
list1: List[Int] = List(1, 2, 4) 
 
scala> val list2 = List(3,5) 
list2: List[Int] = List(3, 5) 
 
scala> list1 ++ list2 
res0: List[Int] = List(1, 2, 4, 3, 5) 

Alternatively, we have a user-defined function, merge(), as shown here:

scala> def merge[A](list1:List[A], list2:List[A]): List[A] = list1 ++ list2 
merge: [A](list1: List[A], list2: List[A])List[A] 
scala> merge(list1,list2) 
res1: List[Int] = List(1, 2, 4, 3, 5) 

If we want to represent this merge() function's Marble diagram, we can do so as follows:

Here, Input Data Stream1 is list1 and Input Data Stream2 is list2. When we make a call to the merge() function with them, we will get the resulting (or output) data stream.

FRP – the filter() function Marble diagram

In FP, a filter() function is used to filter a data stream (or a set of elements—containers) with a condition (this condition is known as a predicate). For instance, we have a list of numbers and want to filter and return only even numbers, as demonstrated here:

scala> val numList = List(1,2,3,4,5,6) 
numList: List[Int] = List(1, 2, 3, 4, 5, 6) 
scala> numList.filter(x => x%2 == 0)  
res8: List[Int] = List(2, 4, 6)  

We can represent this filter() function as shown here:

FRP – the reduce() function Marble diagram

Now we will see another important and frequently used functional HOF, that is, reduce().

It takes each element from the data stream (or container) and applies the given function. Let's do it with List (almost all containers have this function):

scala> val numList = List(1,2,3,4,5,6) 
numList: List[Int] = List(1, 2, 3, 4, 5, 6) 
 
scala> numList.reduce((x,y) => x +y ) 
res22: Int = 21 
 
scala> numList.reduce(_ + _) 
res21: Int = 21 

The following diagram shows the Marble diagram for the reduce() function:

If you are new to HOF, refer to Chapter 2, Functional Scala, for more information.

FRP – the concat() and sorted() functions Marble diagram

So far, we did simple Marble diagrams, which apply only one function at a time. However, we can compose functions one by one in a sequential order to solve some complex problems easily and in an elegant way.

Now, we will draw Marble diagram for the concat() and sorted() functions. Let's consider some Scala code using these two functions:

scala> val hello = "Hello" 
hello: String = Hello 
 
scala> val world = "World" 
world: String = World 
 
scala> hello.concat(world) 
res17: String = HelloWorld 
 
scala> hello.concat(world).sorted 
res19: String = Hwdellloor 

We can represent them as a Marble diagram, as follows:

In the same way, we can represent any Functional or Reactive or FRP function as a Marble diagram to understand it well.

Observer pattern versus Reactive pattern

In this section, we will try to differentiate between the Observer pattern and Reactive pattern. As we have already discussed, the Reactive pattern gives a lot of benefits to our systems or applications.

The Observer pattern is a widely used OOP design pattern to solve some of the problems, and it mainly has two kinds of components—Subject and Object (where Subject is Observable and Object is Observer).

It gives us the following benefits:

  • Separation of concerns into two distinct components—Subject and Object
  • Clear abstraction and encapsulation between these two components
  • Loose coupling between the Subject and Object components
  • We can change a component without affecting others
  • We can add more Objects or Observers at any time

Even though the Observer pattern solves most of the problems, it still has the following drawbacks or issues:

  • It is not thread-safe
  • It may cause leaking if we forget to unregister any Observers
  • It does not support the backpressure technique
  • It does not support composability, which means we cannot compose multiple small components to solve large or complex problems
  • It does not support asynchronous non-blocking communication with backpressure

To solve all these problems, we should go for the Reactive Streams Specification.

The Reactive pattern is more than the Observer pattern. It is the combination of positive points from the Observer pattern, Iterator pattern, and FP.

Reactive pattern = Observer pattern + Iterator pattern + FP

The Reactive pattern or programming is not a single pattern; it is an architecture and gives us a new set of design patterns to develop new kinds of systems or applications (that is, Reactive systems or Reactive applications). We will discuss it in Chapter 12, Reactive Design Patterns and Best Practices.

By design, the Reactive pattern supports asynchronous non-blocking communication with backpressure. If we use Akka Reactive Streams or Play Framework with FP, we will get composability, thread-safety, concurrency, and parallelism for free.

Check out the Benefits of Reactive programming section for more details, which are also the same for the Reactive pattern.

Take a look at Chapter 7, Working with Reactive Streams, to understand backpressure.
Going forward, I hope my readers start thinking functional reactively.