Asynchronous Data Streams with RxJava
ReactiveX
An API / library for composing asynchronous and event driven programs by using observable sequences
A new idea/paradigm in programming
Ported in more than 10 languages
RxJava ported by Netflix
- Data Stream: A sequence of values
- Micro-services consume & process a ton of Data Streams from apis, dbs, files and internal data structures with different response times
- Non-blocking processing is key for minimizing latency
Programming with
Asynchronous Data Streams
is essentially
Reactive programming
based on the principle of non-blocking push rather than pull
An easy and scalable way to propagate all data changes to different parts/layers of our app that may want to react on them in parallel
Other paradigms & apis
Event Bus
(Pub-sub pattern):
No clear connection between event producer and event consumer
Difficult to track your code logic
Difficult to test
Single data pipeline and a ton of XXXEvent Classes
Other paradigms & apis
CompletableFuture
(mixes blocking and non-blocking approach into one API ):
More oriented to asynchronously running of tasks with side effects or returning single result
Complex and non intuitive for asynchronous processing of streams
Limited api and composition capabilities
RxJava
(a combination of the Observer pattern, the Iterator pattern, and functional programming)
single item | multiple items | |
---|---|---|
synchronous | T getData() | Iterable<T> getData |
asynchronous | Future<T> getData() | Observable<T> getData() |
Observable(source of data stream, Observer(subscribes/listens/reacts)
event | iterable(pull) | Observable(push) |
---|---|---|
retrieve data | T next() | onNext(T) |
discover error | throws Exception | onErron(Exception) |
complete | !hasNext() | onCompleted() |
RxJava
Takes reactive programming to the next level
Functional & reactive programing - asynchronous data streams on steroids:
Easily create data streams of anything, i.e. variables, user inputs, properties, caches, data structures. Data streams are cheap and ubiquitous.
Apply functional composition to the data stream, i.e., have an amazing toolbox of functions to combine, map, filter, retry, throttle, any of those streams and an easy way to create new operators.
RxJava
Easy threading management (abstract away low-level threading issue)
Easy async error handling
More declarative, less side-effects and less mutable state
Higher manipulation, reusability, modularity of code and objects
create data streams of anything
Observable.just(1, 2, 3).toBlocking().subscribe(System.out::println);
Observable.range(100, 120).toBlocking().subscribe(System.out::println);
Observable.fromCallable(() -> 10).toBlocking().subscribe(System.out::println);
Observable.from(ImmutableList.of(1, 2, 3)).toBlocking().subscribe(System.out::println);
Observable.from(new Integer[]{ 1, 2, 3 }).toBlocking().subscribe(System.out::println);
Observable.form(future)...
Observable.create(subscriber -> {
try {
if (subscriber.isUnsubscribed()) {
return;
}
subscriber.onNext(1000);
subscriber.onCompleted();
} catch (Exception e) {
if (!subscriber.isUnsubscribed()) {
subscriber.onError(e);
}
}
}).toBlocking().subscribe(System.out::println);
Concurrently get docs from external api
Observable<Doc> getObservableDoc(String docName) {
return Observable.create(subscriber -> {
subscriber.onNext(esClient.getDoc(docName));
subscriber.onCompleted();
}
}
Observable
.from(ImmutableList.of(“doc1”, “doc2”, “doc3”))
.flatMap(docName -> getObservableDoc(docName))
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe(document -> System.out.println(document::getTitle));
Sum even, GroupBy
Observable
.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.filter(item -> item % 2 == 0)
.scan((sum, item) -> sum + item)
.last()
.subscribe(System.out::println);
Observable
.just("cat", "dog", "ant", "bee")
.groupBy(animal -> {
if (animal.equals("cat") || animal.equals("dog") {
return "mammal";
} else {
return "insect";
}
})
.subscribe(groupObservable -> {
groupObservable.toList()
.subscribe(itemInGroup -> System.out.println("Group " +
groupObservable.getKey() +
" items: " + itemsInGroup));
});
Event Bus with RxJava
public class RxBus {
private final Subject<Object, Object> rxBus =
PublishSubject.create().toSerialized();
public void send(Object event) {
rxBus.onNext(event);
}
public Observable<Object> toObservable() {
return rxBus;
}
}
rxBus.toObservable().subscribe(event -> processEvent(event))
Observable<T> to CompletableFuture<T>
public static <T> CompletableFuture<List<T>> fromObservable(Observable<T> observable) {
final CompletableFuture<List<T>> future = new CompletableFuture<>();
observable
.doOnError(future::completeExceptionally)
.toList()
.forEach(future::complete);
return future;
}
public static <T> Observable<T> toObservable(CompletableFuture<T> future) {
return Observable.create(subscriber ->
future.whenComplete((result, error) -> {
if (error != null) {
subscriber.onError(error);
} else {
subscriber.onNext(result);
subscriber.onCompleted();
}
})
);
}
rx.Completable
Completable task1 = Completable
.fromAction(() -> System.out.println("Task 1 completed"))
.subscribeOn(Schedulers.io());
Completable task2 = Completable
.fromAction(() -> System.out.println("Task 2 completed"))
.subscribeOn(Schedulers.io());
Completable.merge(task1, task2).subscribeOn(Schedulers.io()).await();
tranform, filter, math, boolean
distinct, elementAt, filter, find, first, last, pausable, skip, skipUntil, take, takeUntil
average, count, max, min, reduce, sum
every, some, includes, sequenceEqual
delay, findIndex, map, scan, debounce
Composing Observables
Observable.merge(o1, o2, ...)
1---2---3---4------|
------5--------6---|
1---2-5-3---4--6---|
Observable.compineLatest(List<Observable<T>>, (List<T>) -> {})
--1------2---------------3--4----5
------A-----B------C-D------------
--1A-----2A-2B----2C-2D--3D-4D---5D
Observable.concat(o1, o2)
---1------1--------------1---
--2---2--
---1------1--------------1---2---2--
Observable.just(1, 2, 3).withLatestFrom(ob2, (x, y) -> x + y);
Observable.zip(List<Observable<T>>, (List<T>) -> {})
Observable.amp(o1, o2); // emit all items from the first to emit
Concat Observables
Merge Observables
Handling Errors
Observable<String> numbers = Observable
.just("1", "2", "three", "4", "5")
.map(Integer::parseInt)
.onErrorReturn(e -> -1)
.subscribe(System.out::println);
Observable<Integer> defaultOnError =
Observable.just(5, 4, 3, 2, 1);
Observable<String> numbers = Observable
.just("1", "2", "three", "4", "5")
.map(Integer::parseInt)
.onExceptionResumeNext(defaultOnError);
Observable.just(1, 2, 3, 4, 5, 6)
.timeout(100, TimeUnit.MILLISECONDS)
.retryWhen(failedAttempts -> {
return failedAttempts
.zipWith(Observable.just(1, 5, 10), (failedAttempt, wait) -> wait)
.flatMap(wait -> Observable.timer(wait, TimeUnit.SECONDS));
})
.subscribe(System.out::println);
Buffering & Throttling
Observable
.sample(Observable
.interval(100L, TimeUnit.MILLISECONDS)
.take(10)
.concatWith(Observable
.interval(200L, TimeUnit.MILLISECONDS))
)
.debounce(150L, TimeUnit.MILLISECONDS)
.onBackpressureBuffer(10000)
.onBackpressureDrop()
.window(3L, 200L, TimeUnit.MILLISECONDS);
Orion Observable Deploys
Observable<DeployableState> observableDeploy = Observable.create(subscriber -> {
subscriber.onNext(deployer.deploy())
subscriber.onComplete()
}
.concatWith(
Observable.interval(5, TimeUnit.SECOMDS, Schedulers.io())
.map(tick -> {
if ((tick + 1L) * 5) > maxWaitTimeSeconds) {return TIMED_OUT}
return watcherFuction.apply(deployRequest, deployable)
}
.lift(new OperatorTakeWhileInclusive(watchedState -> !watchedState.isTerminalState)));
)
.onErrorReturn(throwable -> FAILED_TO_ACTIVATE)
.subcribeOn(Schedulers.io())
.observeOn(Schedulers.computation());
Observable<Deployable> getObservableOfCompinedDeployStates(
List<Observable<DeployableState>> childObservableDeploys) {
return Observable.combineLatest(childObservableDeploys, (latestStateList) -> {});
};
Cold vs Hot Observables
Observable<Integer> obj = Observable.just(1, 2, 3).publish()
// Observable<Integer> obj = Observable.just(1, 2, 3).replay()
// Observable<Integer> obj = Observable.just(1, 2, 3).publish().refCount()
Subscription sub1 = obj.subscribe(System.out::println);
Subscription sub2 = obj.subscribe(System.out::println);
obj.connect();
Resources
http://reactivex.io/tutorials.html
https://github.com/ReactiveX/RxJava/wiki
https://github.com/ReactiveX/RxNetty
https://github.com/davidmoten/rxjava-jdbc
Asynchronous Data Streams with RxJava
By Gregory Chomatas
Asynchronous Data Streams with RxJava
- 1,416