Skip to content

Instantly share code, notes, and snippets.

@aaronoah
Last active October 6, 2019 17:36
Show Gist options
  • Save aaronoah/6522dcf5be1e7e99073a8f08ef6e5f30 to your computer and use it in GitHub Desktop.
Save aaronoah/6522dcf5be1e7e99073a8f08ef6e5f30 to your computer and use it in GitHub Desktop.
Demystifying Reactive Programming and When do you need it

Demystifying Reactive Programming and When do you need it

TL;DR You probably often hear about engineers talking about a term called reactive programming, so what is it actually? Why you should know it, not only to avoid awkwardness in front of your peers but also to secure your next high paid jobs? Let's find out.

Starting with a simple comparison so that you can have a fundamental understanding of it:

you have a Math project that takes several numbers and operators to compute a final value; for simple addition

int numberA = 1
int numberB = 2
int numberC = numberA + numberB // (the final value is then 3)

you can also make a function to compute it given two different numbers and whenever you need an addition value just use it again:

int add(int numberA, int numberB) {
  return numberA + numberB;
}

Easy, right? But what if you need to call this function again and again to get updated values? You say ok just call the function many times, not a big deal. But what if you have numerous occasions (e.g. in a stock ticker tracking system, you have to refresh trading volume, equity value on hold, investment return and current ticker price) that you need to get updated values, especially there are also dependencies among those values?

I admit, it's easy to think programming in an imperative way, it's our human nature like what we are taught for solving Math problem sets. reactive programming approaches the problem in a declarative way that instead of describing the actions we take, express only the dependencies between inputs and outputs. Same example for addition, now we use reactive programming:

ObservableOnSubscribe<Integer> handlerA = emitter -> {
  emitter.onNext(1);
  emitter.onComplete();
};

ObservableOnSubscribe<Integer> handlerB = emitter -> {
  emitter.onNext(2);
  emitter.onComplete();
};

Observable<Integer> observableA = Observable.create(handlerA);
Observable<Integer> observableB = Observable.create(handlerB);

observableA.mergeWith(observableB).subscribe(output -> System.out.println(output[0] + output[1]));

What the above code does is create one observable that emits a value of 1 and the other observable emits a value of 2, then merge them to form a new observable; an observer subscribes on the merged observable and output the addition of 1 and 2. If you need to update one of the values, just do emitter.onNext(value) and then every other place related to it will get updated accordingly. See the patterns? You can also chain the observables in a complex system and add observers in the middle, without worrying about single update that needs to propagate through the other places. This is the core concept of reactive programming, and it doesn't exclusively apply to a single programming language.

Distinguish Reactive Programming, Reactive Extensions and Reactive Systems

Remember to know the differences between these terms;

  • Reactive Programming is a paradigm of declarative programming that chains data streams and ease the propagation of changes (either synchronous or asynchronous changes, normally asynchronous).

  • Reactive Extensions (aka ReactiveX), is the API specifications and tools that provide guidelines for implementing reactive programming in multiple languages. When someone talks about Rx programming, they mean Reactive Extensions in real world programming languages.

  • Reactive systems conforming to Reactive Manifesto, on the other hand, concerns a type of system that are:

    • Responsive: responds in a timely manner if at all possible
    • Resilient: remains responsive even it comes across failures
    • Elastic: reacts under different workloads and adjust the resources allocated accordingly
    • Message Driven: uses async message passing between components

Deep Dive into Rx Programming

The Rx Programming extends the observer pattern and combine both the PULL and PUSH programming models, which is flexible; In a typical C/S architecture,

  • PULL: The client actively fetches the messages or events from the server.
  • PUSH: The server notifies the client of the messages or events, the client passively receive them.

For a Rx architecture,

  • The observer actively subscribe to the observable to be notified of messages or events, which is similar to PULL model.
  • The observable emits messages or events so that the observer which subscribes to receive them. It is similar to PUSH model.

There are already a group of implementations around Rx Programming, such as ReactiveX. The .NET framework firstly proposed the Rx implementations and was the guidance in other language platforms. There are three core building blocks of Reactive Extensions:

  1. Observable (express the data to be subscribed by Observers, either passively when Observers pull or actively when push to Observers)
  2. Operators (imperatively describe the relationships among Observables, emit new Observable(s) as output, remember the mergeWith call in the previous example)
  3. Scheduler (decides the total interleaving workflow of async message-driven observations)

Observable

The essense of Observable is to describe rather than execute; For example, the imperative way of calling a method and make use of the return value is intuitive. But for reactive programming, it looks something like:

  1. Define a method that does something useful with the return value from the async call; this method is part of the Observer.
  2. Define the async call itself as an Observable.
  3. Attach the Observer to that Observable by subscribing it.
  4. Whenever the async call returns, the Observer’s method will begin to operate on its return value or values — the items emitted by the Observable.

When you subscribe your Observers to Observables, it is by standard for the Observers to implement the following methods:

  • onNext: An Observable calls this method whenever new items are emitted by Observable itself.
  • onComplete: An Observable calls this method after finalizing all calls of onNext and does not encounter any errors.
  • onError: An Observable calls this method to indicate an error occurred in generating function returns. It will stop further calls to onNext or onComplete

Ok, when does the Observable starts emitting values so that Observers can know? It depends on the types of Observables. There are two types of Observables in general:

  • Cold Observable: The Observable does not start emitting values until an Observer begins to subscribe to the Observable itself.
  • Hot Observable: The Observable starts emitting values right after it's created. If Observers don't subscribe to it, the values will be lost.

Operators

Operators are like functional transformers on values emitted by the Observables, which means the Observables emit immutable data. Operators operate on Observables and produce new Observables, so you can chain operators to form the final Observables. What worth noted is that the order of chaining matters and reorder the Operators applying on an Observable would produce different emitted values.

In general, there are several types of Operators: Create, Transform, Filtering, Combining, Error Handling, Conditional and Mathmatical. see more

A special type of Operator called Backpressure is introduced to prevent the scenario that the Observable is emitting values faster than the rate they are consumed by the Observers. There are several strategies for a Backpressure operator to handle this situation. For example, Buffer can maintain values emitted by the faster Observable and eventually combine with values emitted by the slower Observable.

Scheduler

Normally it's up to the programmer who decides what kinds of scheduling algorithms the Observables run on (multi-threading environment in particular, and the design of Scheduler decides the efficiency of running on multiprocessor architectures), but you can actually pick the strategies that meet your performance requirements. see more

Use Cases that need it

The most important part now, when do you need the fancy reactive programming? There are certain general use cases:

  • Extensive number of dependent service calls:

Many backend systems use HTTP-based protocol to process request such as RESTful and SOAP, and normally they are synchronous, blocking calls. If there are multiple calls depend on the return results of their previous ones, they might wait for a long time before a reply is assembled and returned. In a time-bounded service the client-side would also assume false positive failures happen from the server-side. With the power of observer pattern, the service calls are handled in a async, non-blocking fashion and the Rx Programming offers composability of the business logic, which drastically improve the productivity of developers.

  • Highly concurrent messaging system:

There are many kinds of highly concurrent systems. The reactive programming natrually fit into messaging system that demands great concurrency. It makes it simple for programmers to reason about multi-threaded development, which is always a pain point. It is up to the schedulers that decide how the resources are allocated for doing the jobs and programmers are freed from dealing with complex locks and synchronizations.

  • Service that needs flexible combination of both pull and push models:

In a classic social media platform, it is very common to require both pull and push models so that the system is running efficiently. For example, if your connections don't update their status so often, you would waste a tons of active requests to fetch their updates so it makes sense to use push model whenever your connections make updates. If you have a connection who is popular and has tens of thousands of followers, it's only efficient to fetch their updates with pull model rather than waiting for their updates to fanout. As how the combination is implemented, the Rx Programming uses the flavor of observer pattern. The client can subscribe Observers to Observables to initiate the pull and the Observables can update themselves to notify Observers, which is a push.

  • Better abstraction of sync and async operations:

It still matters for today that if programmers takes an extra layer of abstraction over the underlying infrastructure, they can forget about if the method calls are synchronous or asynchronous as long as they accomplish the same thing. The design of Rx frameworks is to free the programmers and to make use of their precious time, energy and of course brain cells in more important matters.

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment