RxJava is a library for composing asynchronous and event-based programs using observable sequences. It avoids "callback hell" and supports streaming for performance benefits. While RxJava has a learning curve, it is important to start with streams, learn hot vs cold observables, always subscribe, and address issues like empty results. RxJava works best when subscribe code is separated and POJAM code is still used where possible. Its future looks promising with adoption in Android, JavaScript and Spring 5.
3. What is ReactiveX?
“A library for composing asynchronous
and event-based programs by using observable
sequences” - Erik Meijer
4. 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
5. 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 Observables
Observable<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));
6. Case: Sanoma Learning
• Web e-learning platform
• Many users
• Co-development: multiple
agile product teams
7. 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
13. 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)));
}
14. 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
19. “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
20. 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)));
}
21. Hot vs Cold Observables
// HOT
MyEventProducer eventProducer = new MyEventProducer();
// some other code...
Observable<MyEvent> hotObservable = Observable.create(subscriber -> {
// Listen to producer here.
});
hotObservable.subscribe(myEvent -> {/* do something with event */});
// COLD
Observable<MyEvent> coldObservable = Observable.create(subscriber -> {
MyEventProducer eventProducer = new MyEventProducer();
// Listen to producer here.
});
// some other code...
coldObservable.subscribe(myEvent -> {/* do something with event */});
22. Examples of hot and cold
// HOT example
HttpServer server = vertx.createHttpServer();
Observable<HttpServerRequest> requests = server.requestStream().toObservable();
// some other code
requests.subscribe(request -> System.out.println(request.absoluteURI()));
//COLD example
MongoClient mongoClient = MongoClients.create();
MongoCollection<Document> questionCollection =
mongoClient.getDatabase("contents").getCollection("questions");
Observable<Document> allQuestions = questionCollection.find().toObservable();
// some other code
allQuestions.subscribe(question -> System.out.println(question));
23. Always end with subscribe somewhere
// wrong way to check value
@Test
public void storeAnswerStoresAnswer() {
Observable<QuestionView> questionView = answerController.storeAnswer("1", "test");
questionView.map(view -> assertThat(view, is(testView)));
}
// use test subscriber to test observables in isolation
@Test
public void storeAnswerStoresAnswer() {
Observable<QuestionView> questionView = answerController.storeAnswer("1", "test");
TestSubscriber<QuestionView> ts = new TestSubscriber<>();
questionView.subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
ts.assertValue(testValue);
}
24. 0 or more events dilemma
// POJAM
public 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 wrong
public 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 switch
public 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));
});
}
25. Mapping “always empty” results
public 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.x
public enum Success {
SUCCESS
}
Will change in RxJava 2.x
Better distinction between 0, 1,
optional and multiple results
27. 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!");
}
28. Separate subscribe code
• Separate and reuse subscribe code from observable chains
// BAD: subscription mixed with observable code
public 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 subscribe
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!");
}
29. Async code and crappy stack traces
public static void main(String[] args) {
Observable.empty()
.observeOn(Schedulers.io())
.toBlocking()
.first();
}
Exception in thread "main" java.util.NoSuchElementException: Sequence contains no elements
at 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)
32. 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
33. Spring example
@RestController
public 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);
}
}
35. 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
PETER@MINDLOOPS.NL