RxJava applied [JavaDay Kyiv 2016]

81
RxJava Applied: Concise Examples where It Shines Igor Lozynskyi JavaDay Kyiv - Oct 14-15, 2016 Software Engineer at GlobalLogic

Transcript of RxJava applied [JavaDay Kyiv 2016]

Page 1: RxJava applied [JavaDay Kyiv 2016]

RxJava Applied: Concise Examples where It Shines

Igor Lozynskyi

JavaDay Kyiv - Oct 14-15, 2016

Software Engineer at GlobalLogic

Page 2: RxJava applied [JavaDay Kyiv 2016]

Presentation’s home

https://github.com/aigor/rx-presentation

Page 3: RxJava applied [JavaDay Kyiv 2016]

Outline

● RxJava vs Java 8 Streams

● RxJava usage example

Page 4: RxJava applied [JavaDay Kyiv 2016]

Pre Java 8 data processing

interface Iterator<T> {

T next();

boolean hasNext(); void remove();}

Page 5: RxJava applied [JavaDay Kyiv 2016]

Pre Java 8 data processing

List<Employee> employees = service.getEmployees();

Map<Integer, List<Employee>> ageDistribution = new HashMap<>();for (Employee employee : employees) { if (employee.getAge() > 25){ List<Employee> thisAge = ageDistribution.get(employee.getAge()); if (thisAge != null){ thisAge.add(employee); } else{ List<Employee> createThisAge = new ArrayList<>(); createThisAge.add(employee); ageDistribution.put(employee.getAge(), createThisAge); } }}

System.out.println(ageDistribution);

Page 6: RxJava applied [JavaDay Kyiv 2016]

Java 8 Stream ...

● Connects data source and client

● Do not hold any data

● Implements map / filter / reduce pattern

● Enforces functional style of data processing

Page 7: RxJava applied [JavaDay Kyiv 2016]

Stream collectors

List<Employee> employees = service.getEmployees();

Map<Integer, List<Employee>> ageDistribution = new HashMap<>();for (Employee employee : employees) { if (employee.getAge() > 25) { List<Employee> thisAge = ageDistribution.get(employee.getAge()); if (thisAge != null){ thisAge.add(employee); } else { List<Employee> createThisAge = new ArrayList<>(); createThisAge.add(employee); ageDistribution.put(employee.getAge(), createThisAge); } }}

System.out.println(ageDistribution);

Page 8: RxJava applied [JavaDay Kyiv 2016]

Stream collectors

List<Employee> employees = service.getEmployees();

Map<Integer, List<Employee>> ageDistribution =

employees.stream()

.filter(e -> e.getAge() > 25)

.collect(Collectors.groupingBy(Employee::getAge));

System.out.println(ageDistribution);

Page 9: RxJava applied [JavaDay Kyiv 2016]

Stream collectors

List<Employee> employees = service.getEmployees();

Map<Integer, Long> ageDistribution =

employees.stream()

.filter(e -> e.getAge() > 25)

.collect(Collectors.groupingBy(

Employee::getAge,

Collectors.counting()

));

Page 10: RxJava applied [JavaDay Kyiv 2016]

Stream sources

Stream<String> stream1 = Arrays.asList("A", "B").stream();

Stream<String> stream2 = Stream.of("Q", "P", "R");

IntStream chars = "some text".chars();

Stream<String> words

= Pattern.compile(" ").splitAsStream("some other text");

Page 11: RxJava applied [JavaDay Kyiv 2016]

Non-standard stream sources

Can we use non-standard stream sources?

?

Page 12: RxJava applied [JavaDay Kyiv 2016]

Stream generator

Stream .generate(() -> UUID.randomUUID().toString()) .limit(4) .forEach(System.out::println);

Page 13: RxJava applied [JavaDay Kyiv 2016]

Spliterator interface

public interface Spliterator<T> {

boolean tryAdvance(Consumer<? super T> action);

Spliterator<T> trySplit();

long estimateSize();

int characteristics(); }

Page 14: RxJava applied [JavaDay Kyiv 2016]

class RandomSpliterator implements Spliterator<String> { public boolean tryAdvance(Consumer<? super String> action) { action.accept(UUID.randomUUID().toString()); return true; }

// for parallel streams public Spliterator<String> trySplit() { return null; }

public long estimateSize() { return Long.MAX_VALUE; };

public int characteristics() { return NONNULL | DISTINCT; }}

Page 15: RxJava applied [JavaDay Kyiv 2016]

Generate sequence: 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, ...

IntStream sequence = IntStream.rangeClosed(1, 50) .flatMap(i -> IntStream.iterate(i, identity()).limit(i) );

sequence.forEach(System.out::println);

Page 16: RxJava applied [JavaDay Kyiv 2016]

Stream API is powerful!

Page 17: RxJava applied [JavaDay Kyiv 2016]

Stream API - async processing

getEmployeeIds().stream() .map(this::doHttpRequest) .collect(Collectors.toList())

Output:[main] processing request: c677c497[main] processing request: 3b5320a9[main] processing request: 9248b92e[main] processing request: 97a68a53

Page 18: RxJava applied [JavaDay Kyiv 2016]

Stream API - async processing

getEmployeeIds().stream() .parallel() .map(this::doHttpRequest) .collect(Collectors.toList())

Output:[main] processing request: 7674da72[ForkJoinPool.commonPool-worker-2] processing request: 747ae948[ForkJoinPool.commonPool-worker-1] processing request: 33fe0bac[ForkJoinPool.commonPool-worker-3] processing request: 812f69f3[main] processing request: 11dda466[ForkJoinPool.commonPool-worker-2] processing request: 12e22a10[ForkJoinPool.commonPool-worker-1] processing request: e2b324f9[ForkJoinPool.commonPool-worker-3] processing request: 8f9f8a97

Page 19: RxJava applied [JavaDay Kyiv 2016]

Stream API - async processing

ForkJoinPool forkJoinPool = new ForkJoinPool(80);

forkJoinPool.submit(() -> getEmployeeIds().stream() .parallel() .map(this::doHttpRequest) .collect(Collectors.toList())).get();

Page 20: RxJava applied [JavaDay Kyiv 2016]

Stream API has some limitations

Page 21: RxJava applied [JavaDay Kyiv 2016]

Reactive Streams: what the difference

Page 22: RxJava applied [JavaDay Kyiv 2016]

RxJavahttp://reactivex.iohttps://github.com/ReactiveX/RxJava

17,700 stars on GitHub

Page 23: RxJava applied [JavaDay Kyiv 2016]

Short history

● From 2009 for .NET

● From 2013 for JVM (latest: 1.2.1, Oct 5, 2016)

● Now a lot of other languages

Page 24: RxJava applied [JavaDay Kyiv 2016]

RxJava Observer

interface Observer<T> {

void onNext(T t);

void onCompleted();

void onError(Throwable e);}

Page 25: RxJava applied [JavaDay Kyiv 2016]

RxJava Subscription

interface Subscription {

void unsubscribe();

boolean isUnsubscribed();}

Page 26: RxJava applied [JavaDay Kyiv 2016]

Observable

● Central class in RxJava library

● It’s big: ~ 10k lines of code (with comments)

● It’s complex: ~ 100 static methods, ~ 150 non-static

Page 27: RxJava applied [JavaDay Kyiv 2016]

Subscription sub = Observable .create(s -> { s.onNext("A"); s.onNext("B"); s.onCompleted(); }) .subscribe(m -> log.info("Message received: " + m), e -> log.warning("Error: " + e.getMessage()), () -> log.info("Done!"));

Output:Message received: AMessage received: BDone!

Page 28: RxJava applied [JavaDay Kyiv 2016]

Observable<Integer> empty = Observable.empty();

Observable<Integer> never = Observable.never();

Observable<Integer> error = Observable.error(exception);

Observable useful for tests

never() - never emit anything (no data, no errors)

Page 29: RxJava applied [JavaDay Kyiv 2016]

public static <T, Resource> Observable<T> using(

final Func0<Resource> resourceFactory,

final Func1<Resource, Observable<T>> observableFactory,

final Action1<Resource> disposeAction){ }

Observable from resource

Page 30: RxJava applied [JavaDay Kyiv 2016]

Observable .from(Arrays.asList(1, 2, 5, 7, 8, 12, 3, 6, 7, 8)) .filter(i -> (i > 3 && i < 8)) .forEach(System.out::println);

Output:5767

Page 31: RxJava applied [JavaDay Kyiv 2016]

Marble diagram: filter

Page 32: RxJava applied [JavaDay Kyiv 2016]

Marble diagram: last

Page 33: RxJava applied [JavaDay Kyiv 2016]

Marble diagram: reduce

Page 34: RxJava applied [JavaDay Kyiv 2016]

Marble diagram: buffer

Page 35: RxJava applied [JavaDay Kyiv 2016]

Marble diagram: timer

Page 36: RxJava applied [JavaDay Kyiv 2016]

Marble diagram: interval

Page 37: RxJava applied [JavaDay Kyiv 2016]

Time series

Observable<Long> timer = Observable.timer(2, TimeUnit.SECONDS);

Observable<Long> interval = Observable.interval(1, TimeUnit.SECONDS);

timer.forEach(System.out::println);

interval.forEach(System.out::println);

Output:Process finished with exit code 0

Page 38: RxJava applied [JavaDay Kyiv 2016]

Time series

Observable<Long> timer = Observable.timer(2, TimeUnit.SECONDS);

Observable<Long> interval = Observable.interval(1, TimeUnit.SECONDS);

timer.forEach(i -> System.out.println(currentThread().getName() + " - " + i));

interval.forEach(i -> System.out.println(currentThread().getName() + " - " + i));

Output:RxComputationThreadPool-2 - 0RxComputationThreadPool-1 - 0RxComputationThreadPool-2 - 1Process finished with exit code 0

Thread.sleep(2000);

Page 39: RxJava applied [JavaDay Kyiv 2016]

Schedulers

public final class Schedulers {

public static Scheduler immediate() {...}

public static Scheduler trampoline() {...}

public static Scheduler newThread() {...}

public static Scheduler computation() {...}

public static Scheduler io() {...}

public static TestScheduler test() {...}

public static Scheduler from(Executor executor) {...}

}

Page 40: RxJava applied [JavaDay Kyiv 2016]

Schedulers

Observable.from("one", "two", "three", "four", "five")

.subscribeOn(Schedulers.newThread())

.observeOn(AndroidSchedulers.mainThread())

.subscribe(/* an Observer */);

Page 41: RxJava applied [JavaDay Kyiv 2016]

Marble diagram: merge

Page 42: RxJava applied [JavaDay Kyiv 2016]

Merge

Observable<Integer> odds = Observable

.just(1, 3, 5).subscribeOn(Schedulers.io());

Observable<Integer> evens = Observable.just(2, 4, 6);

Observable.merge(odds, evens)

.subscribe(

System.out::println,

System.err::println,

() -> System.out.println("Finished"));

Page 43: RxJava applied [JavaDay Kyiv 2016]

Marble diagram: zip

Page 44: RxJava applied [JavaDay Kyiv 2016]

Zip

Observable<String> odds = Observable.just("A", "B", "C", "D");

Observable<Integer> evens = Observable.just(2, 4, 6);

Observable.zip(odds, evens, (a, b) -> a + "-" + b)

.subscribe(

System.out::println,

System.err::println,

() -> System.out.println("Finished"));

Output:A-2B-4C-6Finished

Page 45: RxJava applied [JavaDay Kyiv 2016]

Cold & Hot Observables

● Cold Observable emits events only having subscriber

● Hot Observable emits events even without subscribers

Page 46: RxJava applied [JavaDay Kyiv 2016]

Backpressure

Page 47: RxJava applied [JavaDay Kyiv 2016]

Backpressure

Backpreasure is a way to slow down emission of elements

It can act on observing side

Strategies:

● Buffering items (buffer, window)

● Skipping items (sampling, throttling, etc)

● Request for specific number of elements

Page 48: RxJava applied [JavaDay Kyiv 2016]

Backpressure: request for elements

public interface Producer {

void request(long n);

}

Page 49: RxJava applied [JavaDay Kyiv 2016]

Stream API vs RxJava

RxJava:

● allows to process data in chosen thread, this is useful for IO,

computations, specialized threads (GUI threads),

● allows synchronization on clocks and application events,

● works in push mode, producer initiates data transfer, but consumer may

control data flow via backpressure.

Stream API:

● tuned for hot data processing,

● good for parallel computation,

● has rich set of collectors for data.

Page 50: RxJava applied [JavaDay Kyiv 2016]

Shakespeare Plays Scrabble Benchmark (throughput)

Non-Parallel Streams 44.995 ± 1.718 ms/op

Parallel Streams 14.095 ± 0.616 ms/op

RxJava 310.378 ± 9.688 ms/op

RxJava (2.0.0-RC4) 156.334 ± 8.423 ms/op

Performance

Based on JMH benchmark by Jose Paumard

Page 51: RxJava applied [JavaDay Kyiv 2016]

Scenarios where RxJava shines

● Observables are better callbacks (easily wrap callbacks)

● Observables are highly composable (on contrast with callbacks)

● Provide async stream of data (on contrast with CompletableFuture)

● Observables can handle errors (have retry / backup strategies)

● Give complete control over running threads

● Good for managing IO rich application workflows

● Perfect for Android development (no Java 8 required, retrolambda compatible)

● Netflix uses RxJava for most their internal APIs

Page 52: RxJava applied [JavaDay Kyiv 2016]

Request flow

Created with draw.io

Page 53: RxJava applied [JavaDay Kyiv 2016]

Stream API and RxJava are friends!

You can easily build Observable from Stream

Iterator<T> iterator = ...;

Observable<T> observable = Observable.from(() -> iterator);

You can map Observable to Stream by implementing adapter

public static<T> Iterator<T> of(Observable<? extends T> ob){

class Adapter implements Iterator<T>, Consumer<T> {

...

}

return new Adapter();

}

Page 54: RxJava applied [JavaDay Kyiv 2016]

External libraries that work with RxJava

● hystrix - latency and fault tolerance bulkheading library

● camel RX - to reuse Apache Camel components

● rxjava-http-tail - allows you to follow logs over HTTP

● mod-rxvertx - extension for VertX that provides support Rx

● rxjava-jdbc - use RxJava with JDBC to stream ResultSets

● rtree - immutable in-memory R-tree and R*-tree with RxJava

● and many more ...

Page 55: RxJava applied [JavaDay Kyiv 2016]

RxJava is not new

JDeferred

CompletableFuture<T>

Scala.Rx

Scala Actors

Spring Integration

Page 56: RxJava applied [JavaDay Kyiv 2016]

What’s around?

● Google Agera - reactive streams for Android by Google

● Project Reactor - flow API implementation by Pivotal & Spring

● Akka-Streams - Akka based streams

● Monix - Scala based implementation of Reactive Streams

● Vert.x, Lagom - if you want more than streams

Page 57: RxJava applied [JavaDay Kyiv 2016]

Let’s build something with RxJava

Page 58: RxJava applied [JavaDay Kyiv 2016]

Use case: Stream of tweets

Created with Balsamiq Mockups

Page 59: RxJava applied [JavaDay Kyiv 2016]

Twitter API

Twitter Stream API (WebSocket alike):● Doc: https://dev.twitter.com/streaming/overview

● Library: com.twitter:hbc-core:2.2.0

Twitter REST API:● GET https://api.twitter.com/1.1/users/show.json?screen_name=jeeconf

● GET https://api.twitter.com/1.1/search/tweets.json?q=from:jeeconf

Page 60: RxJava applied [JavaDay Kyiv 2016]

Let’s look at entities

class Tweet { String text; int favorite_count; String author; int author_followers;}

class Profile { String screen_name; String name; String location; int statuses_count; int followers_count;}

class UserWithTweet { Profile profile; Tweet tweet;}

Page 61: RxJava applied [JavaDay Kyiv 2016]

Marble diagram

Page 62: RxJava applied [JavaDay Kyiv 2016]

Profile getUserProfile(String screenName) { ObjectMapper om = new ObjectMapper(); return (Profile) om.readValue(om.readTree( Unirest.get(API_BASE_URL + "users/show.json") .queryString("screen_name", screenName) .header("Authorization", bearerAuth(authToken.get())) .asString() .getBody()), Profile.class);}

Get user profile synchronously

Page 63: RxJava applied [JavaDay Kyiv 2016]

Get user profile asynchronously

Observable<Profile> getUserProfile(String screenName) { return Observable.fromCallable(() -> { ObjectMapper om = new ObjectMapper(); return (Profile) om.readValue(om.readTree( Unirest.get(API_BASE_URL + "users/show.json") .queryString("screen_name", screenName) .header("Authorization", bearerAuth(authToken.get())) .asString()

.getBody()), Profile.class); });}

Page 64: RxJava applied [JavaDay Kyiv 2016]

Add some errors handling

Observable<Profile> getUserProfile(String screenName) { if (authToken.isPresent()) { return Observable.fromCallable(() -> { ObjectMapper om = new ObjectMapper(); return (Profile) om.readValue(om.readTree( Unirest.get(API_BASE_URL + "users/show.json") .queryString("screen_name", screenName) .header("Authorization", bearerAuth(authToken.get())) .asString() .getBody()), Profile.class); }).doOnCompleted(() -> log("getUserProfile completed for: " + screenName)); } else { return Observable.error(new RuntimeException("Can not connect to twitter")); }}

Page 65: RxJava applied [JavaDay Kyiv 2016]

Let’s do something concurrently

Illustration: Arsenal Firearms S.r.l.

Page 66: RxJava applied [JavaDay Kyiv 2016]
Page 67: RxJava applied [JavaDay Kyiv 2016]

Get data concurrently

Observable<UserWithTweet> getUserAndPopularTweet(String author){ return Observable.just(author) .flatMap(u -> { Observable<Profile> profile = client.getUserProfile(u) .subscribeOn(Schedulers.io()); Observable<Tweet> tweet = client.getUserRecentTweets(u) .defaultIfEmpty(null) .reduce((t1, t2) -> t1.retweet_count > t2.retweet_count ? t1 : t2) .subscribeOn(Schedulers.io()); return Observable.zip(profile, tweet, UserWithTweet::new); });}

Page 68: RxJava applied [JavaDay Kyiv 2016]
Page 69: RxJava applied [JavaDay Kyiv 2016]

Let’s subscribe on stream of tweets!

Page 70: RxJava applied [JavaDay Kyiv 2016]

streamClient.getStream("RxJava", "JavaDay", "Java")

Page 71: RxJava applied [JavaDay Kyiv 2016]

streamClient.getStream("RxJava", "JavaDay", "Java", "Trump", "Hillary")

Page 72: RxJava applied [JavaDay Kyiv 2016]

streamClient.getStream("RxJava", "JavaDay", "Java", "Trump", "Hillary")

.distinctUntilChanged()

.map(p -> p.author)

.flatMap(name -> getUserAndPopularTweet(name))

.subscribeOn(Schedulers.io())

.observeOn(Schedulers.immediate())

.subscribe(p -> log.info("The most popular tweet of user " + p.profile.name + ": " + p.tweet));

.scan((u1, u2) -> u1.author_followers > u2.author_followers ? u1 : u2)

Page 73: RxJava applied [JavaDay Kyiv 2016]

streamClient.getStream("RxJava", "JavaDay", "Java", "Trump") .scan((u1, u2) -> u1.author_followers > u2.author_followers ? u1 : u2) .distinctUntilChanged() .map(p -> p.author) .flatMap(name -> { Observable<Profile> profile = client.getUserProfile(name) .subscribeOn(Schedulers.io()); Observable<Tweet> tweet = client.getUserRecentTweets(name) .defaultIfEmpty(null) .reduce((t1, t2) -> t1.retweet_count > t2.retweet_count ? t1 : t2) .subscribeOn(Schedulers.io()); return Observable.zip(profile, tweet, UserWithTweet::new); }) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.immediate()) .subscribe(p -> log.info("The most popular tweet of user " + p.profile.name + ": " + p.tweet));

Page 74: RxJava applied [JavaDay Kyiv 2016]

Marble diagram

Page 75: RxJava applied [JavaDay Kyiv 2016]

@Test public void correctlyJoinsHttpResults() throws Exception {

String testUser = "testUser";

Profile profile = new Profile("u1", "Name", "USA", 10, 20, 30);

Tweet tweet1 = new Tweet("text-1", 10, 20, testUser, 30);

Tweet tweet2 = new Tweet("text-2", 40, 50, testUser, 30);

TwitterClient client = mock(TwitterClient.class);

when(client.getUserProfile(testUser)).thenReturn(Observable.just(profile));

when(client.getUserRecentTweets(testUser)).thenReturn(Observable.just(tweet1, tweet2));

TestSubscriber<UserWithTweet> testSubscriber = new TestSubscriber<>();

new Solutions().getUserAndPopularTweet(client, testUser).subscribe(testSubscriber);

testSubscriber.awaitTerminalEvent();

assertEquals(singletonList(new UserWithTweet(profile, tweet2)),

testSubscriber.getOnNextEvents());

}

Page 76: RxJava applied [JavaDay Kyiv 2016]

Don’t believe it works?

Page 77: RxJava applied [JavaDay Kyiv 2016]

● API is big (150+ methods to remember)

● Requires to understand underlying magic

● Hard to debug

● Don’t forget about back pressure

Conclusions: pitfalls

Page 78: RxJava applied [JavaDay Kyiv 2016]

● It is functional, it is reactive*

● Good for integration scenarios

● Allows to control execution threads

● Easy to compose workflows

● Easy to integrate into existing solutions

● Easy Ok to test

* RxJava is inspired by FRP (Functional Reactive Programming), but doesn’t implement it

Conclusions: strength

Page 79: RxJava applied [JavaDay Kyiv 2016]

● RxJava 2.0 in few weeks!

● RxJava 2.0 is better and faster

● RxJava 2.0 is Java 9 Flow API compatible

● We will see more and more reactive streams

Conclusions: future

Page 80: RxJava applied [JavaDay Kyiv 2016]

Q&A https://github.com/aigor/rx-presentation

Page 81: RxJava applied [JavaDay Kyiv 2016]

Thanks

Presentation template by SlidesCarnival

@[email protected]