RxJava from the trenches

35
mindloops RXJAVA: FROM THE TRENCHES PETER HENDRIKS @PETERHENDRIKS80 [email protected]

Transcript of RxJava from the trenches

Page 1: RxJava from the trenches

mindloops

RXJAVA: FROM THE TRENCHES

PETER HENDRIKS @PETERHENDRIKS80 [email protected]

Page 2: RxJava from the trenches

Agenda

• Intro

• Why we want RxJava

• RxJava in Practice

• Future / conclusion

Page 3: RxJava from the trenches

What is ReactiveX?

“A library for composing asynchronous and event-based programs by using observable

sequences” - Erik Meijer

Page 4: RxJava from the trenches

What is RxJava?

• Port of ReactiveX C# framework to Java

• Open source, lead by Netflix

• 1.0.0 release november 2014, 2.0.0 last week :)

• ReactiveX is currently available in 14 programming languages

Page 5: RxJava from the trenches

RxJava Survival Guide

// In your own code, convert results in a fluent way...Observable<String> descriptionObservable = eventObservable.map(event -> event.getDescription());

// e.g. Database, services, keyboard, messaging etc.MyEventProducer eventProducer = new MyEventProducer();

// use some custom adapter code to adapt to an RxJava "Observable"Observable<MyEvent> eventObservable = eventProducer.toObservable();

// ... And compose with other ObservablesObservable<Success> storeObservable = eventObservable.flatMap(event -> storeEvent(event));private Observable<Success> storeEvent(MyEvent event) { // stores the event asynchronously...}

// Start flowing by subscribing and do something with final results storeObservable.subscribe(myEvent -> {/* do something */}, error -> LOG.error("Oops", error));

Page 6: RxJava from the trenches

Case: Sanoma Learning

• Web e-learning platform

• Many users

• Co-development: multiple agile product teams

Page 7: RxJava from the trenches

Case: Sanoma Learning

• AngularJS, Java 8, Vert.x, MongoDB, AWS S3, Redis

• Suite of micro-services

Microservices met Vert.x- Tim van Eindhoven, Zaal 4, 11:30

Page 8: RxJava from the trenches

Why RxJava

WHY RXJAVA

Page 9: RxJava from the trenches

Why we want RxJava

• Avoid “callback hell”

• Out of the box support

• Streaming performance benefits

Page 10: RxJava from the trenches

Simplicity of the POJAM• A simple, blocking Plain Old JAva Method

• Predictable execution flow

• Fully built-in result and error handling

• Easy to composepublic List<QuestionView> listQuestions(String studentId, String segmentId) { List<Question> questions = contentService.findBySegmentId(segmentId); List<QuestionView> result = new ArrayList<>(); for(Question question : questions) { Progress progress = progressService.findByQuestionId(studentId, question.getId()); result.add(new QuestionView(question, progress)); } return result;}

Page 11: RxJava from the trenches

Vert.x callback exampleMessageConsumer<String> consumer = eventBus.consumer("news.uk.sport"); consumer.handler(message -> { try { System.out.println("I have received a message: " + message.body()); message.reply("how interesting!"); } catch (Exception e) { message.fail(500, e.getMessage()); }});sendNewsMessage("Yay! Someone kicked a ball across a patch of grass", ar -> { if (ar.succeeded()) { System.out.println("Received reply: " + ar.result().body()); } else { ar.cause().printStackTrace(); }});

private void sendNewsMessage(String message, Handler<AsyncResult<Message<Object>>> replyHandler) { eventBus.send("news.uk.sport", message, replyHandler);}

Page 12: RxJava from the trenches

Callback hellvoid listQuestions(String studentId, String segmentId, Handler<AsyncResult<List<QuestionView>>> resultHandler) { findBySegmentId(segmentId, ar -> { if (ar.succeeded()) { List<QuestionView> result = new ArrayList<>(); List<Question> questions = ar.result().body(); for(Question question : questions) { findByQuestionId(studentId, question.getId(), progressAr -> { if (ar.succeeded()) { Progress progress = progressAr.result().body(); result.add(new QuestionView(question, progress)); questions.remove(question); if (questions.isEmpty()) { resultHandler.handle(Future.<List<QuestionView>>succeededFuture(result)); } } else { resultHandler.handle(Future.<List<QuestionView>>failedFuture(ar.cause())); } }); } } else { resultHandler.handle(Future.<List<QuestionView>>failedFuture(progressAr.cause())); } } );} private void findBySegmentId(String segmentId, Handler<AsyncResult<Message<List<Question>>>> handler) {}private void findByQuestionId(String studentId, String questionId, Handler<AsyncResult<Message<Progress>>> handler) {}

Page 13: RxJava from the trenches

Solution: RxJava as “Promise API”

public Observable<QuestionView> listQuestions(String studentId, String segmentId) { return contentService.findBySegmentId(segmentId) .concatMap(question -> progressService.findByQuestionId(studentId, question.getId()) .map(progress -> new QuestionView(question, progress)));}

Page 14: RxJava from the trenches

RxJava: one Promise API to rule them all

• We want to use one promise API everywhere to fix callback hell

• RxJava is directly supported in Vert.x and the MongoDB Java driver

Page 15: RxJava from the trenches

Typical request processing overhead

Thread

Request

Response (List)

Domain data (List)

public List<QuestionView> listQuestions(String studentId, String segmentId) { List<Question> questions = contentService.findBySegmentId(segmentId); List<Question> result = new ArrayList<>(); for(Question question : questions) { Progress progress = progressService.findByQuestionId(studentId, question.getId()); result.add(new QuestionView(question, progress)); } return result;}

Page 16: RxJava from the trenches

Streaming == less intermediate data needed

public Observable<QuestionView> listQuestions(String studentId, String segmentId) { return contentService.findBySegmentId(segmentId) .flatMap(question -> progressService.findByQuestionId(studentId, question.getId()) .map(progress -> new QuestionView(question, progress)));}

Thread

Request

Response (List)

Domain data (element)

Page 17: RxJava from the trenches

RXJAVA IN PRACTICE

Page 18: RxJava from the trenches

Expect a learning curve

Page 19: RxJava from the trenches

“Simple” promises vs RxJava

CompletableFuture Observable

What emits 1 event or 1 error emit 0 or more events and/or 1 error

When Immediately scheduled for execution

already executing, or only after subscription

Where Event can be read at any time after completion Event is available once

Page 20: RxJava from the trenches

Learn to stream first!• RxJava will force you to do it

• It helps to be comfortable with Java 8 Stream API first

• Java 8 Stream API still very useful for POJAM code

public Observable<QuestionView> listQuestions(String studentId, String segmentId) { return contentService.findBySegmentId(segmentId) .flatMap(question -> progressService.findByQuestionId(studentId, question.getId()) .map(progress -> new QuestionView(question, progress)));}

Page 21: RxJava from the trenches

Hot vs Cold Observables// HOTMyEventProducer eventProducer = new MyEventProducer();// some other code...Observable<MyEvent> hotObservable = Observable.create(subscriber -> { // Listen to producer here.});hotObservable.subscribe(myEvent -> {/* do something with event */});

// COLDObservable<MyEvent> coldObservable = Observable.create(subscriber -> { MyEventProducer eventProducer = new MyEventProducer(); // Listen to producer here.});// some other code...coldObservable.subscribe(myEvent -> {/* do something with event */});

Page 22: RxJava from the trenches

Examples of hot and cold// HOT exampleHttpServer server = vertx.createHttpServer();Observable<HttpServerRequest> requests = server.requestStream().toObservable();// some other coderequests.subscribe(request -> System.out.println(request.absoluteURI()));

//COLD exampleMongoClient mongoClient = MongoClients.create();MongoCollection<Document> questionCollection = mongoClient.getDatabase("contents").getCollection("questions");Observable<Document> allQuestions = questionCollection.find().toObservable();// some other codeallQuestions.subscribe(question -> System.out.println(question));

Page 23: RxJava from the trenches

Always end with subscribe somewhere// wrong way to check value @Testpublic void storeAnswerStoresAnswer() { Observable<QuestionView> questionView = answerController.storeAnswer("1", "test"); questionView.map(view -> assertThat(view, is(testView)));}

// use test subscriber to test observables in isolation @Testpublic void storeAnswerStoresAnswer() { Observable<QuestionView> questionView = answerController.storeAnswer("1", "test"); TestSubscriber<QuestionView> ts = new TestSubscriber<>(); questionView.subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); ts.assertValue(testValue);}

Page 24: RxJava from the trenches

0 or more events dilemma// POJAMpublic QuestionView storeAnswer(String questionId, String textValue) { Question question = contentService.findByQuestionId(questionId); if (question == null) { throw new IllegalStateException("question not found"); } Answer answer = validateAnswer(question, textValue); progressService.store(questionId, answer); return new QuestionView(question, answer);}

// RxJava wrongpublic Observable<QuestionView> storeAnswer(String questionId, String textValue) { return contentService.findByQuestionId(questionId) .flatMap(question -> { Answer answer = validateAnswer(question, textValue); return progressService.store(questionId, answer) .map(x -> new QuestionView(question, answer)); });}

// RxJava with switchpublic Observable<QuestionView> storeAnswer(String questionId, String textValue) { return contentService.findByQuestionId(questionId) .switchIfEmpty(Observable.defer(() -> Observable.error(new IllegalStateException("question not found")))) .flatMap(question -> { Answer answer = validateAnswer(question, textValue); return progressService.store(questionId, answer) .map(x -> new QuestionView(question, answer)); });}

Page 25: RxJava from the trenches

Mapping “always empty” resultspublic Observable<Void> storeResult(String answer) { System.out.println("Do some async operation without result."); return Observable.empty();} public Observable<String> processRequest(String answer) { // This does not work right return storeResult(answer) .map(x -> "success!");}

public interface MongoCollection<TDocument> { /** * @return an Observable with a single element indicating when the operation has completed or with either a com.mongodb.DuplicateKeyException or com.mongodb.MongoException */ Observable<Success> insertOne(TDocument document);} // Return non-null event to be compatible with RxJava 2.xpublic enum Success { SUCCESS}

Will change in RxJava 2.xBetter distinction between 0, 1,

optional and multiple results

Page 26: RxJava from the trenches

Make it easy to do the right thing

Page 27: RxJava from the trenches

Only use RxJava when needed• POJAM code still the best for a lot of your code

• Use RxJava only for async composition/streaming

public boolean isAnswerCorrect(Question question, String answer) { // some complicated synchronised checking logic here return true; } public Observable<String> processRequest(String questionId, String answer) { return contentService.findByQuestionId(questionId) .map(question -> isAnswerCorrect(question, answer)) .map(result -> result ? "Good job!" : "Better luck next time!");}

Page 28: RxJava from the trenches

Separate subscribe code• Separate and reuse subscribe code from observable chains

// BAD: subscription mixed with observable codepublic void processRequest(HttpServerRequest request, String questionId, String answer) { contentService.findByQuestionId(questionId) .map(question -> isAnswerCorrect(question, answer)) .map(result -> result ? "Good job!" : "Better luck next time!") .subscribe(result -> request.response().end(result));}

// GOOD: simple observable code, easy to test and cannot forget to subscribepublic Observable<String> processRequest(String questionId, String answer) { return contentService.findByQuestionId(questionId) .map(question -> isAnswerCorrect(question, answer)) .map(result -> result ? "Good job!" : "Better luck next time!");}

Page 29: RxJava from the trenches

Async code and crappy stack tracespublic static void main(String[] args) { Observable.empty() .observeOn(Schedulers.io()) .toBlocking() .first();}

Exception in thread "main" java.util.NoSuchElementException: Sequence contains no elementsat rx.internal.operators.OperatorSingle$ParentSubscriber.onCompleted(OperatorSingle.java:131) at rx.internal.operators.OperatorTake$1.onCompleted(OperatorTake.java:53) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber.pollQueue(OperatorObserveOn.java:195) at rx.internal.operators.OperatorObserveOn$ObserveOnSubscriber$1.call(OperatorObserveOn.java:162) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(<…>.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(<…>.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)

Page 30: RxJava from the trenches

Band-aid: add context to logging/exceptions

// generic wrapping codepublic void processRequest(HttpServerRequest request, Function<HttpServerRequest, Observable<String>> processor) { Observable<String> observable = processor.apply(request); observable.subscribe( result -> processResult(request, result), error -> processError(request, error));} private void processError(HttpServerRequest request, Throwable error) { LOG.error("An error occurred processing: " + request.uri(), error); request.response().setStatusCode(500).setStatusMessage(error.getMessage()).end();}

Page 31: RxJava from the trenches

FUTURE OF RXJAVA

Page 32: RxJava from the trenches

The Future of RxJava looks promising

• Increased adoption for “Reactive Streams”

• RxJava 2 solves some API weirdness and simplifies simple event handling

• Strong adoption in Android and JavaScript

• Spring 5 goes big on reactive, including RxJava support

Page 33: RxJava from the trenches

Spring example@RestControllerpublic class RxJavaController { private final RxJavaService aService; @Autowired public RxJavaController(RxJavaService aService) { this.aService = aService; } @RequestMapping(path = "/handleMessageRxJava", method = RequestMethod.POST) public Observable<MessageAcknowledgement> handleMessage(@RequestBody Message message) { System.out.println("Got Message.."); return this.aService.handleMessage(message); }}

Page 34: RxJava from the trenches

RxJava 2.xReactive Streams(Backpressure) No Backpressure

0..n items, complete, error Flowable Observable

0..1 item, complete, error Maybe

1 item, error Single

complete, error Completable

Page 35: RxJava from the trenches

CONCLUSION

RxJava in practice- Learning to stream - Hot vs Cold observables - Always subscribe - 0 or more events dilemma - Dealing with always empty results Make it easy

- Still use POJAM code - Separate subscribe code - Make up for stack trace

Why we want RxJava- Avoid Callback Hell - Streaming helps performance - Out of the box support

PETER HENDRIKS @PETERHENDRIKS80 [email protected]