BenChristensen FunctionalReactiveProgrammingWithRxJava PDF
BenChristensen FunctionalReactiveProgrammingWithRxJava PDF
functional
GOTO Aarhus - October 2013
Ben Christensen
Software Engineer – Edge Platform at Netflix
@benjchristensen
http://www.linkedin.com/in/benjchristensen
http://techblog.netflix.com/
composablefunctions
function
reactivelyapplied
reactive
This presentation is about how the Netflix API application applies a functional programming style in an imperative Java application to apply functions reactively to asynchronously retrieved data ...
composablefunctions
function
reactivelyapplied
reactive
... and transform ...
composablefunctions
function
reactivelyapplied
reactive
... combine and output web service responses.
asynchronous
values
events
push
functional reactive
function
lambdas
closures
(mostly) pure
composable
We have been calling this approach “functional reactive” since we use functions (lambdas/closures) in a reactive (asynchronous/push) manner.
Clojure Scala
(-‐>
Observable("one",
"two",
"three")
(Observable/from
["one"
"two"
"three"])
.take(2)
(.take
2)
.subscribe((arg:
String)
=>
{
(.subscribe
(rx/action
[arg]
(println
arg))))
println(arg)
})
Groovy JRuby
Observable.from("one",
"two",
"three")
Observable.from("one",
"two",
"three")
.take(2)
.take(2)
.subscribe({arg
-‐>
println(arg)})
.subscribe(lambda
{
|arg|
puts
arg
})
Java8
Observable.from("one",
"two",
"three")
.take(2)
.subscribe((arg)
-‐>
{
System.out.println(arg);
});
Simple examples showing RxJava code in various languages supported by RxJava (https://github.com/Netflix/RxJava/tree/master/language-adaptors). Java8 works with rxjava-core and does not need a language-adaptor. It also works
with Java 6/7 but without lambdas/closures the code is more verbose.
Clojure Scala
(-‐>
Observable("one",
"two",
"three")
(Observable/from
["one"
"two"
"three"])
.take(2)
(.take
2)
.subscribe((arg:
String)
=>
{
(.subscribe
(rx/action
[arg]
(println
arg))))
println(arg)
})
Groovy JRuby
Observable.from("one",
"two",
"three")
Observable.from("one",
"two",
"three")
.take(2)
.take(2)
.subscribe({arg
-‐>
println(arg)})
.subscribe(lambda
{
|arg|
puts
arg
})
Java8
Observable.from("one",
"two",
"three")
.take(2)
.subscribe((arg)
-‐>
{
System.out.println(arg);
});
Most examples in the rest of this presentation will be in Groovy ...
Clojure Scala
(-‐>
Observable("one",
"two",
"three")
(Observable/from
["one"
"two"
"three"])
.take(2)
(.take
2)
.subscribe((arg:
String)
=>
{
(.subscribe
(rx/action
[arg]
(println
arg))))
println(arg)
})
Groovy JRuby
Observable.from("one",
"two",
"three")
Observable.from("one",
"two",
"three")
.take(2)
.take(2)
.subscribe({arg
-‐>
println(arg)})
.subscribe(lambda
{
|arg|
puts
arg
})
Java8
Observable.from("one",
"two",
"three")
.take(2)
.subscribe((arg)
-‐>
{
System.out.println(arg);
});
... with a handful in Java 8
RxJava
http://github.com/Netflix/RxJava
RxJava is a port of Microsoft’s Rx (Reactive Extensions) to Java that attempts to be polyglot by targeting the JVM rather than just Java the language.
Netflix is a subscription service for movies and TV shows for $7.99USD/month (about the same converted price in each countries local currency).
More than 37 million Subscribers
in 50+ Countries and Territories
Netflix has over 37 million video streaming customers in 50+ countries and territories across North & South America, United Kingdom, Ireland, Netherlands and the Nordics.
Netflix accounts for 33% of Peak Downstream
Internet Traffic in North America
1500
1000
500
0
2010 2011 2012 Today
Netflix API
The Netflix API serves all streaming devices and acts as the broker between backend Netflix systems and the user interfaces running on the 1000+ devices that support Netflix streaming.
This presentation is going to focus on why the Netflix API team chose the functional reactive programming model (Rx in particular), how it is used and what benefits have been achieved.
Other aspects of the Netflix API architecture can be found at http://techblog.netflix.com/search/label/api and https://speakerdeck.com/benjchristensen/.
Discovery of Rx began with a re-architecture ...
User interface client teams now build and deploy their own webservice endpoints on top of the API Platform instead of the “API Team” being the only ones who create endpoints.
We wanted to retain flexibility to use whatever JVM language we wanted as well as cater to the differing skills and backgrounds of engineers on different teams.
Groovy was the first alternate language we deployed in production on top of Java.
Concurrency without each engineer
reading and re-reading this →
T next() onNext(T)
throws Exception onError(Exception)
returns; onCompleted()
More information about the duality of Iterable and Observable can be found at http://csl.stanford.edu/~christos/pldi2010.fit/meijer.duality.pdf and http://codebetter.com/matthewpodwysocki/2009/11/03/introduction-to-the-reactive-
framework-part-ii/
Iterable Observable
pull push
T next() onNext(T)
throws Exception onError(Exception)
returns; onCompleted()
The same way higher-order functions can be applied to an Iterable they can be applied to an Observable.
Iterable Observable
pull push
T next() onNext(T)
throws Exception onError(Exception)
returns; onCompleted()
Grid of synchronous and asynchronous duals for single and multi-valued responses. The Rx Observable is the dual of the synchronous Iterable.
Single Multiple
String s = getData(args);
if (s.equals(x)) {
// do something
} else {
// do something else
}
Future<String> s = getData(args);
if (s.get().equals(x)) {
// do something
} else {
// do something else
}
As we move to async a normal Java Future is asynchronous but to apply conditional logic requires dereferencing the value via ‘get()’.
Single Multiple
Future<String> s = getData(args);
if (s.get().equals(x)) {
// do something
} else {
// do something else
}
And this leads to the typical issue in nested, conditional asynchronous code with Java Futures where asynchronous quickly becomes synchronous and blocking again.
Single Multiple
Future<String> s = getData(args);
Futures.addCallback(s,
new FutureCallback<String> {
public void onSuccess(String s) {
if (s.equals(x)) {
// do something
} else {
// do something else
}
}
}, executor);
There are better Futures though, one of them is from Guava ...
Single Multiple
Future<String> s = getData(args);
Futures.addCallback(s,
new FutureCallback<String> {
public void onSuccess(String s) {
if (s.equals(x)) {
// do something
} else {
// do something else
}
}
}, executor);
Future<String> s = getData(args);
Futures.addCallback(s,
new FutureCallback<String> {
public void onSuccess(String s) {
if (s.equals(x)) {
// do something
} else {
// do something else
}
}
}, executor);
... so the conditional logic can be put inside a callback and prevent us from blocking and we can chain calls together in these callbacks.
Single Multiple
CompletableFuture<String> s = getData(args);
s.thenApply((v) -> {
if (v.equals(x)) {
// do something
} else {
// do something else
}
});
CompletableFuture<String> s = getData(args);
s.thenApply((v) -> {
if (v.equals(x)) {
// do something
} else {
// do something else
}
});
Single Multiple
Future<String> s = getData(args);
s.map({ s ->
if (s.equals(x)) {
// do something
} else {
// do something else
}
});
Akka/Scala Futures are also composable and provide higher-order functions ...
Single Multiple
Future<String> s = getData(args);
s.map({ s ->
if (s.equals(x)) {
// do something
} else {
// do something else
}
});
... that get us to where we want to be so that we can now compose conditional, nested data flows while remaining asynchronous.
Single Multiple
Future<String> s = getData(args);
s.map({ s ->
if (s.equals(x)) {
// do something
} else {
// do something else
}
});
Observable<String> s = getData(args);
s.map({ s ->
if (s.equals(x)) {
// do something
} else {
// do something else
}
});
... is very similar to the Rx Observable except that an Rx Observable supports multiple values which means it can handle a single value, a sequence of values or an infinite stream.
Single Multiple
Observable<String> s = getData(args);
s.map({ s ->
if (s.equals(x)) {
// do something
} else {
// do something else
}
});
We wanted to be asynchronous to abstract away the underlying concurrency decisions and composable Futures or Rx Observables are good solutions.
Single Multiple
Observable<String> s = getData(args);
s.map({ s ->
if (s.equals(x)) {
// do something
} else {
// do something else
}
});
One reason we chose the Rx Observable is because it gives us a single abstraction that accommodates our needs for both single and multi-valued responses while giving us the higher-order functions to compose nested, conditional
logic in a reactive manner.
instead of a blocking api ...
class
VideoService
{
def
VideoList
getPersonalizedListOfMovies(userId);
def
VideoBookmark
getBookmark(userId,
videoId);
def
VideoRating
getRating(userId,
videoId);
def
VideoMetadata
getMetadata(videoId);
}
With Rx blocking APIs could be converted into Observable APIs and accomplish our architecture goals including abstracting away the control and implementation of concurrency and asynchronous execution.
One of the other positives of Rx Observable was that it is abstracted from the source of concurrency. It is not opinionated and allows the implementation to decide.
For example, an Observable API could just use the calling thread to synchronously execute and respond.
Or it could use a thread-pool to do the work asynchronously and callback with that thread.
Or it could use multiple threads, each thread calling back via onNext(T) when the value is ready.
Or it could use an actor pattern instead of a thread-pool.
Or NIO with an event-loop.
Or a thread-pool/actor that does the work but then performs the callback via an event-loop so the thread-pool/actor is tuned for IO and event-loop for CPU.
All of these different implementation choices are possible without changing the signature of the method and without the calling code changing their behavior or how they interact with or compose responses.
client code treats all interactions
with the api as asynchronous
Example Observable implementation that executes asynchronously on a thread-pool and emits a single value. This explicitly shows an ‘executor’ being used to run this on a separate thread to illustrate how it is up to the Observable
implementation to do as it wishes, but Rx always has Schedulers for typical scenarios of scheduling an Observable in a thread-pool or whatever a Scheduler implementation dictates.
Asynchronous Observable with Single Value
Example Observable implementation that executes asynchronously on a thread-pool and emits a single value. This explicitly shows an ‘executor’ being used to run this on a separate thread to illustrate how it is up to the Observable
implementation to do as it wishes, but Rx always has Schedulers for typical scenarios of scheduling an Observable in a thread-pool or whatever a Scheduler implementation dictates.
Synchronous Observable with Multiple Values
Caution: This example is eager and will always emit all values regardless of
subsequent operators such as take(10)
Example Observable implementation that executes synchronously and emits multiple values.
Note that the for-loop as implemented here will always complete so should not have any IO in it and be of limited length otherwise it should be done with a lazy iterator implementation or performed asynchronously so it can be
unsubscribed from.
Synchronous Observable with Multiple Values
Caution: This example is eager and will always emit all values regardless of
subsequent operators such as take(10)
Example Observable implementation that executes synchronously and emits multiple values.
Note that the for-loop as implemented here will always complete so should not have any IO in it and be of limited length otherwise it should be done with a lazy iterator implementation or performed asynchronously so it can be
unsubscribed from.
Asynchronous Observable with Multiple Values
def
Observable<Video>
getVideos()
{
return
Observable.create({
observer
-‐>
executor.execute(new
Runnable()
{
def
void
run()
{
try
{
for(id
in
videoIds)
{
Video
v
=
...
do
network
call
...
observer.onNext(v)
}
observer.onCompleted();
}
catch(Exception
e)
{
observer.onError(e);
}
}
})
})
}
Example Observable implementation that executes asynchronously on a thread-pool and emits multiple values.
Note that for brevity this code does not handle the subscription so will not unsubscribe even if asked.
See the ‘getListOfLists'
method
in the following for an implementation with unsubscribe handled: https://github.com/Netflix/RxJava/blob/master/language-adaptors/rxjava-groovy/src/examples/groovy/rx/lang/groovy/examples/
VideoExample.groovy#L125
Asynchronous Observable with Multiple Values
def
Observable<Video>
getVideos()
{
return
Observable.create({
observer
-‐>
executor.execute(new
Runnable()
{
def
void
run()
{
try
{
for(id
in
videoIds)
{
Video
v
=
...
do
network
call
...
observer.onNext(v)
}
observer.onCompleted();
}
catch(Exception
e)
{
observer.onError(e);
}
}
})
})
}
Example Observable implementation that executes asynchronously on a thread-pool and emits multiple values.
Note that for brevity this code does not handle the subscription so will not unsubscribe even if asked.
See the ‘getListOfLists'
method
in the following for an implementation with unsubscribe handled: https://github.com/Netflix/RxJava/blob/master/language-adaptors/rxjava-groovy/src/examples/groovy/rx/lang/groovy/examples/
VideoExample.groovy#L125
Asynchronous Observer
getVideos().subscribe(new
Observer<Video>()
{
def
void
onNext(Video
video)
{
println("Video:
"
+
video.videoId)
}
def
void
onError(Exception
e)
{
println("Error")
e.printStackTrace()
}
def
void
onCompleted()
{
println("Completed")
}
})
Moving to the subscriber side of the relationship we see how an Observer looks. This implements the full interface for clarity of what the types and members are ...
Asynchronous Observer
getVideos().subscribe(
{
video
-‐>
println("Video:
"
+
video.videoId)
},
{
exception
-‐>
println("Error")
e.printStackTrace()
},
{
println("Completed")
}
)
... but generally the on* method implementations are passed in as functions/lambdas/closures similar to this.
Asynchronous Observer
getVideos().subscribe(
{
video
-‐>
println("Video:
"
+
video.videoId)
},
{
exception
-‐>
println("Error")
e.printStackTrace()
}
)
This is a list of some of the higher-order functions that Rx supports. More can be found in the documentation (https://github.com/Netflix/RxJava/wiki) and many more from the original Rx.Net implementation have not yet been
implemented in RxJava (but are all listed on the RxJava Github issues page tracking the progress).
We will look at some of the important ones for combining and transforming data as well as handling errors asynchronously.
Combining via Merge
The ‘merge’ operator is used to combine multiple Observable sequences of the same type into a single Observable sequence with all data.
The X represents an onError call that would terminate the sequence so once it occurs the merged Observable also ends. The ‘mergeDelayError’ operator allows delaying the error until after all other values are successfully merged.
Observable<SomeData>
a
=
getDataA();
Observable<SomeData>
b
=
getDataB();
Observable.merge(a,
b)
.subscribe(
{
element
-‐>
println("data:
"
+
element)})
Observable<SomeData>
a
=
getDataA();
Observable<SomeData>
b
=
getDataB();
Observable.merge(a,
b)
.subscribe(
{
element
-‐>
println("data:
"
+
element)})
Observable.merge(a,
b)
.subscribe(
{
element
-‐>
println("data:
"
+
element)})
Observable.merge(a,
b)
.subscribe(
{
element
-‐>
println("data:
"
+
element)})
Observable.merge(a,
b)
.subscribe(
{
element
-‐>
println("data:
"
+
element)})
Observable.merge(a,
b)
.subscribe(
{
element
-‐>
println("data:
"
+
element)})
... and emits a single Observable containing all of the onNext events plus the first terminal event (onError/onCompleted) from the source Observables ...
Observable<SomeData>
a
=
getDataA();
Observable<SomeData>
b
=
getDataB();
Observable.merge(a,
b)
.subscribe(
{
element
-‐>
println("data:
"
+
element)})
... that we pass through the zip operator that contains a provided function to apply to each set of values received.
Observable<SomeData>
a
=
getDataA();
Observable<String>
b
=
getDataB();
... and in this case is simply taking x & y and combining them into a tuple or pair and then returning it.
Observable<SomeData>
a
=
getDataA();
Observable<String>
b
=
getDataB();
The output of the transformation function given to the zip operator is emitted in a single Observable sequence ...
Observable<SomeData>
a
=
getDataA();
Observable<String>
b
=
getDataB();
Observable<SomeData>
a
=
getDataA();
Observable<String>
b
=
getDataB();
If an error occurs then the ‘onError’ handler passed into the ‘subscribe’ will be invoked...
Error Handling
Observable<SomeData>
a
=
getDataA();
Observable<String>
b
=
getDataB();
onError(Exception)
onCompleted()
Error Handling
Observable<SomeData>
a
=
getDataA();
Observable<String>
b
=
getDataB();
onError(Exception)
onCompleted()
... but this is the final terminal state of the entire composition so we often want to move our error handling to more specific places. There are operators for that ...
Error Handling
The ‘onErrorResumeNext’ operator allows intercepting an ‘onError’ and providing a new Observable to continue with.
Observable<SomeData>
a
=
getDataA();
Observable<String>
b
=
getDataB()
Observable.zip(a,
b,
{x,
y
-‐>
[x,
y]})
.subscribe(
{
pair
-‐>
println("a:
"
+
pair[0]
+
"
b:
"
+
pair[1])},
{
exception
-‐>
println("error
occurred:
"
+
exception.getMessage())})
Observable<SomeData>
a
=
getDataA();
Observable<String>
b
=
getDataB()
.onErrorResumeNext(getFallbackForB());
Observable.zip(a,
b,
{x,
y
-‐>
[x,
y]})
.subscribe(
{
pair
-‐>
println("a:
"
+
pair[0]
+
"
b:
"
+
pair[1])},
{
exception
-‐>
println("error
occurred:
"
+
exception.getMessage())})
If we want to handle errors on Observable ‘b’ we can compose it with ‘onErrorResumeNext’ and pass in a function that when invoked returns another Observable that we will resume with if onError is
called.
Observable<SomeData>
a
=
getDataA();
Observable<String>
b
=
getDataB()
.onErrorResumeNext(getFallbackForB());
Observable.zip(a,
b,
{x,
y
-‐>
[x,
y]})
.subscribe(
{
pair
-‐>
println("a:
"
+
pair[0]
+
"
b:
"
+
pair[1])},
{
exception
-‐>
println("error
occurred:
"
+
exception.getMessage())})
... which provides a new Observable that is subscribed to in place of the original Observable ‘b’ ...
Observable<SomeData>
a
=
getDataA();
Observable<String>
b
=
getDataB()
.onErrorResumeNext(getFallbackForB());
Observable.zip(a,
b,
{x,
y
-‐>
[x,
y]})
.subscribe(
{
pair
-‐>
println("a:
"
+
pair[0]
+
"
b:
"
+
pair[1])},
{
exception
-‐>
println("error
occurred:
"
+
exception.getMessage())})
... so the returned Observable emits a single sequence of 5 onNext calls and a successful onCompleted without an onError.
The ‘onErrorReturn’ operator is similar ...
... except that it returns a specific value instead of an Observable.
The request is lazy and we turn it into an Observable that when subscribed to will execute the request and callback with the response.
HTTP Request Use Case
ObservableHttp.createRequest(
HttpAsyncMethods.createGet("http://www.wikipedia.com"),
client)
.toObservable()
//
Observable<ObservableHttpResponse>
.flatMap((ObservableHttpResponse
response)
-‐>
{
//
access
to
HTTP
status,
headers,
etc
//
response.getContent()
-‐>
Observable<byte[]>
return
response.getContent().map((bb)
-‐>
{
return
new
String(bb);
//
Observable<String>
});
})
Once we have the ObservableHttpResponse we can choose what to do with it, including fetching the content which returns an Observable<byte[]>.
HTTP Request Use Case
ObservableHttp.createRequest(
HttpAsyncMethods.createGet("http://www.wikipedia.com"),
client)
.toObservable()
//
Observable<ObservableHttpResponse>
.flatMap((ObservableHttpResponse
response)
-‐>
{
//
access
to
HTTP
status,
headers,
etc
//
response.getContent()
-‐>
Observable<byte[]>
return
response.getContent().map((bb)
-‐>
{
return
new
String(bb);
//
Observable<String>
});
})
We use flatMap as we want to perform nested logic that returns another Observable, ultimately an Observable<String> in this example.
HTTP Request Use Case
ObservableHttp.createRequest(
HttpAsyncMethods.createGet("http://www.wikipedia.com"),
client)
.toObservable()
//
Observable<ObservableHttpResponse>
.flatMap((ObservableHttpResponse
response)
-‐>
{
//
access
to
HTTP
status,
headers,
etc
//
response.getContent()
-‐>
Observable<byte[]>
return
response.getContent().map((bb)
-‐>
{
return
new
String(bb);
//
Observable<String>
});
})
... which will execute all of the lazily defined code above and receive String results.
HTTP Request Use Case
ObservableHttp.createRequest(
HttpAsyncMethods.createGet("http://www.wikipedia.com"),
client)
.toObservable()
//
Observable<ObservableHttpResponse>
.flatMap((ObservableHttpResponse
response)
-‐>
{
//
access
to
HTTP
status,
headers,
etc
//
response.getContent()
-‐>
Observable<byte[]>
return
response.getContent().map((bb)
-‐>
{
return
new
String(bb);
//
Observable<String>
});
})
.toBlockingObservable()
.forEach((resp)
-‐>
{
System.out.println(resp);
});
Or if we need to be blocking (useful for unit tests or simple demo apps) we can use toBlockingObservable().forEach() to iterate the responses in a blocking manner.
HTTP Request Use Case
ObservableHttp.createGet("http://www.wikipedia.com"),
client)
.toObservable()
//
Observable<ObservableHttpResponse>
.flatMap((ObservableHttpResponse
response)
-‐>
{
//
access
to
HTTP
status,
headers,
etc
//
response.getContent()
-‐>
Observable<byte[]>
return
response.getContent().map((bb)
-‐>
{
return
new
String(bb);
//
Observable<String>
});
})
... to something that streams results (mime-type text/event-stream) we can see a more interesting use of Observable.
HTTP Request Use Case
ObservableHttp.createGet("http://hostname/hystrix.stream"),
client)
.toObservable()
//
Observable<ObservableHttpResponse>
.flatMap((ObservableHttpResponse
response)
-‐>
{
//
access
to
HTTP
status,
headers,
etc
//
response.getContent()
-‐>
Observable<byte[]>
return
response.getContent().map((bb)
-‐>
{
return
new
String(bb);
//
Observable<String>
});
})
.filter((s)
-‐>
{
s.startsWith(":
ping");
})
.take(30);
We will receive a stream (potentially infinite) of events.
HTTP Request Use Case
ObservableHttp.createGet("http://hostname/hystrix.stream"),
client)
.toObservable()
//
Observable<ObservableHttpResponse>
.flatMap((ObservableHttpResponse
response)
-‐>
{
//
access
to
HTTP
status,
headers,
etc
//
response.getContent()
-‐>
Observable<byte[]>
return
response.getContent().map((bb)
-‐>
{
return
new
String(bb);
//
Observable<String>
});
})
.filter((s)
-‐>
{
s.startsWith(":
ping");
})
.take(30);
We can filter out all “: ping” events ...
HTTP Request Use Case
ObservableHttp.createGet("http://hostname/hystrix.stream"),
client)
.toObservable()
//
Observable<ObservableHttpResponse>
.flatMap((ObservableHttpResponse
response)
-‐>
{
//
access
to
HTTP
status,
headers,
etc
//
response.getContent()
-‐>
Observable<byte[]>
return
response.getContent().map((bb)
-‐>
{
return
new
String(bb);
//
Observable<String>
});
})
.filter((s)
-‐>
{
s.startsWith(":
ping");
})
.take(30);
... and take the first 30 and then unsubscribe. Or we can use operations like window/buffer/groupBy/scan to group and analyze the events.
Netflix API Use Case
Now we’ll move to a more involved example of how Rx is used in the Netflix API that demonstrates some of the power of Rx to handle nested asynchronous composition.
This marble diagram represents what the code in subsequent slides is doing when retrieving data and composing the functions.
Observable<Video> emits n videos to onNext()
First we start with a request to fetch videos asynchronously ...
def
Observable<Map>
getVideos(userId)
{
return
VideoService.getVideos(userId)
The ‘flatMap’/‘mapMany’ operator allows transforming from type T to type Observable<R>. If ‘map’ were being used this would result in an Observable<Observable<R>> which is rarely what is wanted, so ‘flatMap’/‘mapMany’ flattens
this via ‘merge’ back into Observable<R>.
This is generally used instead of ‘map’ anytime nested work is being done that involves fetching and returning other Observables.
flatMap
Observable<R>
b
=
Observable<T>.mapMany({
T
t
-‐>
Observable<R>
r
=
...
transform
t
...
return
r;
})
Observable<VideoMetadata>
Observable<VideoBookmark>
Observable<VideoRating>
3 separate types are being fetched asynchronously and each return an Observable.
def
Observable<Map>
getVideos(userId)
{
return
VideoService.getVideos(userId)
//
we
only
want
the
first
10
of
each
list
.take(10)
.flatMap({
Video
video
-‐>
//
for
each
video
we
want
to
fetch
metadata
def
m
=
video.getMetadata()
.map({
Map<String,
String>
md
-‐>
//
transform
to
the
data
and
format
we
want
return
[title:
md.get("title"),length:
md.get("duration")]
})
//
and
its
rating
and
bookmark
def
b
...
def
r
...
})
}
At this point we have 3 Observables defined but they are dangling - nothing combines or references them and we aren’t yet returning anything from the ‘flatMap’ function so we want to compose m, b, and r together and return a single
asynchronous Observable representing the composed work being done on those 3.
def
Observable<Map>
getVideos(userId)
{
return
VideoService.getVideos(userId)
//
we
only
want
the
first
10
of
each
list
.take(10)
.flatMap({
Video
video
-‐>
def
m
...
def
b
...
def
r
...
//
compose
these
together
return
Observable.zip(m,
b,
r,
{
metadata,
bookmark,
rating
-‐>
//
now
transform
to
complete
dictionary
//
of
data
we
want
for
each
Video
return
[id:
video.videoId]
<<
metadata
<<
bookmark
<<
rating
})
})
}
def
Observable<Map>
getVideos(userId)
{
return
VideoService.getVideos(userId)
//
we
only
want
the
first
10
of
each
list
.take(10)
.flatMap({
Video
video
-‐>
def
m
...
def
b
...
def
r
...
//
compose
these
together
return
Observable.zip(m,
b,
r,
{
metadata,
bookmark,
rating
-‐>
//
now
transform
to
complete
dictionary
//
of
data
we
want
for
each
Video
return
[id:
video.videoId]
<<
metadata
<<
bookmark
<<
rating
})
})
}
/android/home
Functional Reactive Dynamic Endpoints
We have found Rx to be a good fit for creating Observable APIs and composing asynchronous data together while building web services using this approach.
/tv/home
/ps3/home
/android/home
Functional Reactive Dynamic Endpoints
Hystrix
Asynchronous Java API
fault-isolation layer
With the success of Rx at the top layer of our stack we’re now finding other areas where we want this programming model applied.
+
Looking back, Rx has enabled us to achieve our goals that started us down this path.
lessons learned
As we implemented and adopted Rx and enabled dozens of developers (most of them of either Javascript or imperative Java backgrounds) we found that workshops, training sessions and well-written documentation was very helpful in
“onboarding” them to the new approach. We have found it generally takes a few weeks to get adjusted to the style.
lessons learned
Asynchronous code is challenging to debug. Improving our ability to debug, trace and visualize Rx “call graphs” is an area we are exploring.
lessons learned
Generally the model has been self-governing (get the code working and all is fine) but there has been one principle to teach since we are using this approach in mutable, imperative languages - don’t mutate state outside the lambda/
closure/function.
asynchronous
values
events
push
functional reactive
lambdas
closures
(mostly) pure
composable
The Rx “functional reactive” approach is a powerful and straight-forward abstraction for asynchronously composing values and events and has worked well for the Netflix API.
jobs.netflix.com
RxJava RxJS
https://github.com/Netflix/RxJava http://reactive-extensions.github.io/RxJS/
@RxJava @ReactiveX
Ben Christensen
@benjchristensen
http://www.linkedin.com/in/benjchristensen