SlideShare a Scribd company logo
1 of 70
Download to read offline
Being FunctionalBeing Functional
on
Reactive StreamsReactive Streams
with
Spring ReactorSpring Reactor
Source code is available on
https://github.com/maxxhuang/functional-reactive-stream-with-spring-reactor
GitHub
More Functional with Java 8More Functional with Java 8
Optional
Java 8 Stream
CompletableFuture
java.util.Optional<T>java.util.Optional<T>
Assume we are building a service
on the imaginary banking model
public class User {
private String id;
private String name;
...
}
public class Account {
private String accountNumber;
private String userId;
private double balance;
...
}
public class AccountInfo {
private String userId;
private String userName;
private String accountNumber;
private double balance;
...
}
and with Java 8 Optional, we have
the repositories
public class UserRepository {
public Optional<User> get(String userId) {
return Optional.ofNullable(FakeData.users.get(userId));
}
}
public class AccountRepository {
public Optional<Account> get(String accountNumber) {
return FakeData.accounts.get(accountNumber).stream().findFirst();
}
}
Now we are building a service for
querying account details combining
user information
public class AccountService {
private UserRepository userRepository;
private AccountRepository accountRepository;
...
public Optional<AccountInfo> getAccountInfo(String accountNumber) {
Optional<Account> optAccount = this.accountRepository.get(accountNumber);
if (!optAccount.isPresent()) {
return Optional.empty();
}
Account account = optAccount.get();
Optional<User> optUser = this.userRepository.get(account.getUserId());
if (!optUser.isPresent()) {
return Optional.empty();
}
User user = optUser.get();
return Optional.of(AccountInfo.create(user, account));
}
}
We are treating Optional as a data structure
containing a possible existing value
That's nothing diļ¬€erent from using null as
an indication of "absence of value"
Optional<Account> optAccount =
this.accountRepository.get(accountNumber);
if (!optAccount.isPresent()) {
return Optional.empty();
}
Account account = optAccount.get();
Optional<User> optUser =
this.userRepository.get(account.getUserId());
if (!optUser.isPresent()) {
return Optional.empty();
}
User user = optUser.get();
return Optional.of(AccountInfo.create(user, account));
Account account =
this.accountRepository.get(accountNumber);
if (account == null) {
return null;
}
User user =
this.userRepository.get(account.getUserId());
if (user == null) {
return null;
}
return AccountInfo.create(user, account);
Can we combine 2 Optional values
in a more functional way?
Optional<Account> optAccount =
this.accountRepository.get(accountNumber);
if (!optAccount.isPresent()) {
return Optional.empty();
}
Account account = optAccount.get();
Optional<User> optUser =
this.userRepository.get(account.getUserId());
if (!optUser.isPresent()) {
return Optional.empty();
}
User user = optUser.get();
return Optional.of(AccountInfo.create(user, account));
public<U> Optional<U> flatMap(
Function<? super T, Optional<U>> mapper) {
requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return requireNonNull(mapper.apply(value));
}
}
public<U> Optional<U> map(
Function<? super T, ? extends U> mapper) {
requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
Let's check out
Optional.ļ¬‚atMap and Optional.map
public class Optional<T> {
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
}
Optional.map takes a function of (T => U) to
transform the value of type "T" into something
else of type "U"
Optional.map creates an Optional<U> out
of Ā an Optional<T>
public class Optional<T> {
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return requireNonNull(mapper.apply(value));
}
}
}
Optional.ļ¬‚atMap takes a mapper function of (T => Optional<U>)
Ā 
This mapper function, with the access to the actual value of
Optional<T>, creates another Optional<U>
Optional.ļ¬‚atMap is said to be more powerful than
Optional.map
ļ¬‚atMap is able to compose other Optional objects and come
out with a new Optional object
Ā 
ļ¬‚atMap has the ability of ļ¬‚ow control. It can decide whether
or not to create a new Optional object or simply an empty
Optional object depending on the given access to previous
Optional value.
Something about ļ¬‚atMap
Attempt to compose Optional using map
Optional<Account> optAccount = accountRepository.get(accountNumber);
Optional<Optional<AccountInfo>> result = optAccount.map(account -> {
Optional<User> optUser = userRepository.get(account.getUserIdI());
Optional<AccountInfo> optAccountInfo =
optUser.map(user -> AccountInfo.create(user, account);
return optAccountInfo
});
Optional.map is unable to compose 2 Optional objects and peel
oļ¬€ or ļ¬‚atten the outer Optional.
Ā 
Optional.map just accumulates the layers of nested Optional.
It is not easy to access the value of multi-layer Optonal, e.g.
Optinoal<Optional<AccountInfo>>Ā 
Attempt to compose Optional using ļ¬‚atMap
Optional<Account> optAccount = accountRepository.get(accountNumber);
Optional<AccountInfo> result = optAccount.flatMap(account -> {
Optional<User> optUser = userRepository.get(account.getUserIdI());
Optional<AccountInfo> optAccountInfo =
optUser.flatMap(user -> Optional.of(AccountInfo.create(user, account));
return optAccountInfo
});
With ļ¬‚atMap, we are capable of composing
Optional<Account> and Optional<User> to
obtain a "ļ¬‚attened" Optional<AccountInfo>
Use ļ¬‚atMap for Flow Control
Optional<Account> optAccount = accountRepository.get(accountNumber);
Optional<AccountInfo> result = optAccount.flatMap(account -> {
if (account.isCredential()) {
return Optional.empty();
}
Optional<User> optUser = userRepository.get(account.getUserIdI());
Optional<AccountInfo> optAccountInfo =
optUser.flatMap(user -> Optional.of(AccountInfo.create(user, account));
return optAccountInfo
});
ļ¬‚atMap and map work together
Optional<Account> optAccount = accountRepository.get(accountNumber);
Optional<AccountInfo> result = optAccount.flatMap(account -> {
Optional<User> optUser = userRepository.get(account.getUserIdI());
Optional<AccountInfo> optAccountInfo =
optUser.flatMap(user -> Optional.of(AccountInfo.create(user, account));
return optAccountInfo
});
Don't bother to wrap AccountInfo with an Optional in the last ļ¬‚atMap composition.
We can use "map" to achieve the same result
Optional<Account> optAccount = accountRepository.get(accountNumber);
Optional<AccountInfo> result = optAccount.flatMap(account -> {
Optional<User> optUser = userRepository.get(account.getUserIdI());
Optional<AccountInfo> optAccountInfo =
optUser.map(user -> AccountInfo.create(user, account);
return optAccountInfo
});
Composing 2 or more Optionals
Optional<TransactionInfo> result = accountRepository.get(accountNumber).flatMap(account ->
userRepository.get(account.getUserId()).flatMap(user ->
transactionRepository.get(account.getAccountNumber()).map(transaction -> {
// use "account", "user", "transaction" to create TransactionInfo
...
return TransactionInfo.create(user, account, transaction);
})
)
);
Optional<SomeType> result = optional1.flatMap(v1 ->
optional2.flatMap(v2 ->
optional3.flatMap(v3 ->
...
optionaln.map(vn ->
// do something with v1, v2, v3,..., vn
//return some value of some type
return SomeType.create(...);
)
)
)
);
java.util.stream.Stream<T>java.util.stream.Stream<T>
Banking Repositories with Java 8 Stream
public class UserRepository {
public List<User> get(String userId) {
return FakeData.users.containsKey(userId) ?
Collections.singletonList(FakeData.users.get(userId)) :
Collections.emptyList();
}
}
public class AccountRepository {
public List<Account> get(String accountNumber) {
return FakeData.accounts.get(accountNumber);
}
}
AccountService
public class AccountService {
private UserRepository userRepository;
private AccountRepository accountRepository;
...
public List<AccountInfo> getAccountInfo(String accountNumber) {
List<AccountInfo> infos = new ArrayList<>();
for (Account account : this.accountRepository.get(accountNumber)) {
for (User user : this.userRepository.get(account.getUserId())) {
infos.add(AccountInfo.create(user, account));
}
}
return infos;
}
}
AccountService being functional
public class FunctionalAccountService {
private UserRepository userRepository;
private AccountRepository accountRepository;
...
public List<AccountInfo> getAccountInfo(String accountNumber) {
return this.accountRepository.get(accountNumber).stream().flatMap(account ->
this.userRepository.get(account.getUserId()).stream().map(user ->
AccountInfo.create(user, account)))
.collect(Collectors.toList());
}
}
java.util.concurrent.CompletableFuture<T>java.util.concurrent.CompletableFuture<T>
Banking Repositories with CompletableFuture
public class UserRepository extends BaseRepository {
public UserRepository() {
super(defaultExecutorService());
}
public CompletableFuture<User> get(String userId) {
return CompletableFuture.supplyAsync(
() -> FakeData.users.get(userId),
this.executorService
);
}
}
public class AccountRepository extends BaseRepository {
public AccountRepository() {
super(defaultExecutorService());
}
public CompletableFuture<Account> get(String accountNumber) {
return CompletableFuture.supplyAsync(
() -> {
List<Account> accounts = FakeData.accounts.get(accountNumber);
return accounts.isEmpty() ? null : accounts.get(0);
},
this.executorService);
}
}
AccountService
public class AccountService {
private UserRepository userRepository;
private AccountRepository accountRepository;
...
public CompletableFuture<AccountInfo> getAccountInfo(String accountNumber) {
CompletableFuture<AccountInfo> result = new CompletableFuture<>();
try {
CompletableFuture<Account> accountFuture =
this.accountRepository.get(accountNumber);
Account account = accountFuture.get();
if (account == null) result.complete(null);
CompletableFuture<User> userFuture = this.userRepository.get(account.getUserId());
User user = userFuture.get();
if (user == null) result.complete(null);
else result.complete(AccountInfo.create(user, account));
} catch (Exception e) {
result.completeExceptionally(e);
}
return result;
}
}
map and ļ¬‚atMap in disguise
CompletableFuture does not have map and ļ¬‚atMap
Ā 
CompletableFuture deļ¬nes thenApply and thenCompose
with similar sematics:
thenApply -> map
thenCompose -> ļ¬‚atMap
public CompletableFuture<T> implements Future<T>, CompletionStage<
// map
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn) {
...
}
// flatMap
public <U> CompletableFuture<U> thenCompose(
Function<? super T, ? extends CompletionStage<U>> fn) {
...
}
}
Threading Control for
CompletableFuture.map/ļ¬‚atMap
There are variant thenApply and thenCompose to control
the threading policy for the execution of mapping functions
public CompletableFuture<T> implements Future<T>, CompletionStage<T> {
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn) {...}
public <U> CompletableFuture<U> thenApplyAsync(
Function<? super T,? extends U> fn) {...}
public <U> CompletableFuture<U> thenApplyAsync(
Function<? super T,? extends U> fn, Executor executor) {...}
public <U> CompletableFuture<U> thenCompose(
Function<? super T, ? extends CompletionStage<U>> fn) {...}
public <U> CompletableFuture<U> thenComposeAsync(
Function<? super T, ? extends CompletionStage<U>> fn) {...}
public <U> CompletableFuture<U> thenComposeAsync(
Function<? super T, ? extends CompletionStage<U>> fn,
Executor executor) {...}
}
AccountService with brighter future
public class FunctionalAccountService {
private UserRepository userRepository;
private AccountRepository accountRepository;
...
public CompletableFuture<AccountInfo> getAccountInfo(String accountNumber) {
return this.accountRepository.get(accountNumber).thenCompose(account ->
this.userRepository.get(account.getUserId()).thenApply(user ->
AccountInfo.create(user, account)));
}
}
Why are Optional, Stream, CompletableFuture
able to chain the computations?
Ā 
What's the pattern?
public final class Optional<T> {
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
}
public interface Stream<T> extends BaseStream<T, Stream<T>> {
public <U> Stream<U> flatMap(Function<? super T, ? extends Stream<? extends U>> mapper)
public <U> Stream<U> map(Function<? super T, ? extends U> mapper)
}
public CompletableFuture<T> implements Future<T>, CompletionStage<T> {
// flatMap
public <U> CompletableFuture<U> thenCompose(
Function<? super T, ? extends CompletionStage<U>> fn)
// map
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
}
Monad
// This is psudo code illustrating Monad structure
M[T] {
flatMap(mapper: T => M[U]): M[U]
// unit wraps a value into monad context
//
// in Optional, it is Optional.of(...), Optional.ofNullable(...)
//
// in Stream, it is Stream.of(...)
//
// in CompletableFuture, it is CompletableFuture.supplyAsync(...)
// or {new CompletableFuture().complete(...)}
unit(value: T): M[T]
}
What about map?
map can always be implemented with ļ¬‚atMap and unit. This
proves again ļ¬‚atMap is more powerful than map.
map(mapper: T => U) {
return flatMap(v -> unit(mapper(v)))
}
Monad
A great article explaining Monad in plain English
http://blog.leichunfeng.com/blog/2015/11/08/functor-applicative-and-monad
A monad is a computational context for some value with a
"unit" method and a "ļ¬‚atMap" method
Ā 
Context value of M[T] Ā is passes as parameter of (T => M[U]).
In the mapper function, you get to access the context value
and decide the new monad value to return.
accountRepository.get(accountNumber).flatMap(account -> {
if (account.isCredential()) {
return Optional.empty();
}
Optional<User> optUser = userRepository.get(account.getUserIdI());
Optional<AccountInfo> optAccountInfo =
optUser.flatMap(user -> Optional.of(AccountInfo.create(user, account));
return optAccountInfo
});
Use ļ¬‚atMap to implement other combinators
JDK implementation for Optional.ļ¬lter
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
another implementation with ļ¬‚atMap
public Optional<T> filter(Predicate<? super T> predicate) {
return flatMap(v -> predicate.test(v) ? this : Optional.empty();
}
the emptiness test (isPresent()) has been done in ļ¬‚atMap
Monad Comparision
Type Context Value Composition Eļ¬€ect (ļ¬‚atMap)
Optional a value may or may not exist composition happens if Optional
contains value
composition stops if Optional is
empty and subsequent
compositions are ignored
CompletableFuture a value available in the future composition happens if
CompletableFuture obtains a
value without error ???
composition stops if error
occurred in CompletableFuture
and subsequent compositions
are ignored ???
Stream each value in the stream composition happens if the
stream is not empty
composition stops if Stream is
empty and subsequent
compositions are ignored
The resulting sub-streams are
merged to a joint stream
Reactive StreamsReactive Streams
Streams are a series of elements emitted over time. The
series potentially has no beginning and no end.
Kevin Webber, A Journey into Reactive Streams
Reactive Stream goes a step further by being able to
signal demand from downstream thus controlling the
overall speed of data elements ļ¬‚owing through it
Walter Chang, Reactive Streams in Scala
What is Reactive Streams
Who deļ¬nes Reactive Streams?
What is it?
How is it related to Java 9 Flow
https://en.wikipedia.org/wiki/Reactive_Streams
The scope of Reactive Streams is to ļ¬nd a minimal set of interfaces, methods and
protocols that will describe the necessary operations and entities to achieve the goalā€”
asynchronous streams of data with non-blocking back pressure.
www.reactive-streams.org
So there are only 4 interfaces and and 7 methods in total inside reactive-streams-1.0.2.jar
package org.reactivestreams;
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
public interface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}
public interface Subscription {
public void request(long n);
public void cancel();
}
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {}
End-user DSLs or protocol binding APIs have purposefully been left out of the scope to encourage and
enable diļ¬€erent implementations that potentially use diļ¬€erent programming languages to stay as true as
possible to the idioms of their platform.
www.reactive-streams.org
Reactive Streams is a speciļ¬cation for library developers.
Ā 
Reactive-stream libraries complying with Reactive Steams speciļ¬cation are
capable of
1. back-pressure control
2. interoperate with other libraries.
Ā 
For example, a Reactor Processor can subscribe to RxJava Producer given that
Spring Reactor and RxJava are both Reactive Streams compliant.
How it works
Dynamic Push / Pull
request n elements
push at most n elements
Subscriber PublisherSubscription
Requests can be made asynchronously.
Multiple requests are accumulated on Publisher and will be served later
Fast subscriber demands more elements; publisher don't need to wait for
requests => push mode
Slow subscriber request less; publish waits for requests => pull mode
The dynamic push/pull makes back-pressure possible and ensure all
participating components are resilient to massive load although this may
degrade the performance
Let's fake it till we make it
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
abstract public class Flowie<T> implements Publisher<T> {
public static <T> Flowie<T> fromIterable(Iterable<T> iterable) {
...
}
}
"Flowie", a homemade implementation for Reactive Streams
Spring Reactor has "Flux"; RxJava comes with "Flowable".
Why don't we create one of our own?
public class FlowieIterable<T> extends Flowie<T> {
private Iterable<T> iterable;
public FlowieIterable(Iterable<T> iterable) {
this.iterable = iterable;
}
@Override
public void subscribe(Subscriber<? super T> subscriber) {
try {
Iterator<T> iterator = this.iterable.iterator();
subscriber.onSubscribe(new IterableSubscription<>(subscriber, iterator));
} catch (Exception e) {
subscriber.onError(e);
}
}
...
static class IterableSubscription<T> implements Subscription {
private Subscriber<? super T> subscriber;
private Iterator<? extends T> iterator;
private boolean cancelled = false;
private AtomicLong requested = new AtomicLong(0L);
@Override
public void request(long n) {
...
}
}
}
We take a similar approach to that for Spring Reactor
Put major logic in Subscription object.
This subscription object gets delivered to users via
onSubscribe()
static class IterableSubscription<T> implements Subscription {
private boolean cancelled = false;
/**
* pending request
*
* The updates to the pending request may come from other threads via {@link Subscription#request(lo
*
* Use AtomicLong to make sure the update and comparison of pending request counter is conducted ato
*
*/
private AtomicLong requested = new AtomicLong(0L);
@Override
public void request(long n) {
long updatedRequested = addRequestAndReturnCurrent(n);
if (updatedRequested == 0L) {
doEmit(n);
}
}
private void doEmit(long n) {
long emitted = 0L;
while (true) {
Great care is taken to make sure multiple asynchronous Subscription.request(n) are
accumulated and served atomically
often leverage java.util.concurrent.atomic components
use CAS (compare and set) to ensure counter is updated atomically at the right
time
Sometimes the price to maintain the correctness of request counter is messing up
the code making it hard to read and understand.
public class FlowieIterable<T> extends Flowie<T> {
private Iterable<T> iterable;
public FlowieIterable(Iterable<T> iterable) {
this.iterable = iterable;
}
@Override
public void subscribe(Subscriber<? super T> subscriber) {
try {
Iterator<T> iterator = this.iterable.iterator();
subscriber.onSubscribe(new IterableSubscription<>(subscriber, iterator));
} catch (Exception e) {
subscriber.onError(e);
}
}
///////////////////////////////////////////////////////////////////////////
static class IterableSubscription<T> implements Subscription {
private Subscriber<? super T> subscriber;
private Iterator<? extends T> iterator;
private boolean cancelled = false;
/**
* pending request
*
* The updates to the pending request may come from other threads via {@link Subscription#request(lo
*
* Use AtomicLong to make sure the update and comparison of pending request counter is conducted ato
*
*/
Ya! We have the ļ¬rst Flowie implementation
Flowie.fromIterable(Arrays.asList("a", "b", "c")).subscribe(new Subscriber<String>() {
@Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(String s) {
System.out.println(s);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
After subscribing, Subscription.request() needs to be
invoked to trigger element emission
a request of Long.MAX_VALUE elements, by the rule of
3.17 (https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.2/README.md#speciļ¬cation), is
treated as requesting ALL elements from the publisher
Let's pursue the banking service and make it
more "Flowie"Ā 
public class UserRepository {
public Flowie<User> get(String userId) {
List<User> user = FakeData.users.containsKey(userId) ?
Collections.singletonList(FakeData.users.get(userId)) :
Collections.emptyList();
return Flowie.fromIterable(user);
}
}
public class AccountRepository {
public Flowie<Account> get(String accountNumber) {
return Flowie.fromIterable(
FakeData.accounts.get(accountNumber)
);
}
}
AccountService in callback hell
public class AccountServiceInCallbackHell {
private UserRepository userRepository;
private AccountRepository accountRepository;
...
/**
* NOTE: This only works in a single-thread execution context.
* The implementation does not catch the timing of "completion" in onComplete() or onError().
* In a muti-thread environment, the elements (User, Account) might not be ready
* when this method returns.
*/
public Flowie<AccountInfo> getAccountInfo(String accountNumber) {
final List<AccountInfo> result = new ArrayList<>();
this.accountRepository.get(accountNumber).subscribe(new Subscriber<Account>() {
@Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(Account account) {
userRepository.get(account.getUserId()).subscribe(new Subscriber<User>() {
@Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(User user) {
result.add(AccountInfo.create(user, account));
}
...
});
}
...
We need a way out of this callback hell.Ā 
Ā 
Can we make Flowie a monad?
This way, we can do monadic composition
of 2 Flowie values like this.
flowieAccuont.flatMap(account ->
flowieUser.map(user ->
// create AccountInfo out of account, user
accountInfo))
Start with the simple one: Flowie.map
abstract public class Flowie<T> implements Publisher<T> {
public <R> Flowie<R> map(Function<? super T, ? extends R> mapper) {
return new FlowieMap<>(this, mapper);
}
}
public class FlowieMap<T, R> extends Flowie<R> {
private Publisher<T> source;
private Function<? super T, ? extends R> mapper;
...
@Override
public void subscribe(Subscriber<? super R> s) {
this.source.subscribe(new MapSubscriber<>(s, this.mapper));
}
static class MapSubscriber<T, R> implements Subscriber<T>, Subscription {
private Subscriber<? super R> actualSubscriber;
private Function<? super T, ? extends R> mapper;
private Subscription upstreamSubscription;
2 levels of decorator pattern adoption
1st level is FlowMap decorating the actual Publisher
2nd level is MapSubscriber decorating the actual
Subscriber
MapSubscriber applies the mapper function in "onNext"
Flowie.ļ¬‚atMap
Compliant ļ¬‚atMap implementation for Reactive Streams
is complicated and needs to tackle the concurrent sub-
streams emissions and elements queuing.
For quick demonstration of functional programming
beneļ¬t, we rush a non-compliant implementation that
only works in a single-thread execution context.
Flowie.ļ¬‚atMap Implementation FYI
/**
* This implementation DOES NOT comply with Reactive Streams. It does not take care of
* the situation where elements from sub-streams emitted asynchronously.
*
* The compliant implementation is complicated and usually needs one or more queues
* to store un-consumed elements emitted from sub-streams.
*
* This non-compliant implementation only serves the purpose of demonstrating the
* advantage of making reactive streams functional.
*/
public class FlowieNonCompliantSynchronousFlatMap<T, R> extends Flowie<R> {
private Publisher<T> source;
private Function<? super T, ? extends Publisher<? extends R>> mapper;
public FlowieNonCompliantSynchronousFlatMap(Publisher<T> source, Function<? super T, ? extend
this.source = source;
this.mapper = mapper;
}
@Override
public void subscribe(Subscriber<? super R> s) {
this.source.subscribe(new SynchronousNonThreadSafeFlapMapSubscriber<>(s, this.mapper));
}
///////////////////////////////////////////////////////////////////////////
static class SynchronousNonThreadSafeFlapMapSubscriber<T, R> implements Subscriber<T>, Subscr
With map/ļ¬‚atMap in place, here is the
upgraded Flowie AccountService
public class FunctionalAccountService {
private UserRepository userRepository;
private AccountRepository accountRepository;
...
public Flowie<AccountInfo> getAccountInfo(String accountNumber) {
return this.accountRepository.get(accountNumber).flatMap(account ->
this.userRepository.get(account.getUserId()).map(user ->
new AccountInfo(
user.getId(), user.getName(),
account.getAccountNumber(), account.getBalance())));
}
}
Project ReactorProject Reactor
(Spring Reactor)
Project Reactor implements Reactive Streams, inherently with non-
blocking streaming natureĀ 
Ā 
On top of Reactive Streams, Project Reactor provides its own APIs for
functional streaming handling and adapting other data source.
Reactor oļ¬€ers 2 Publisher implementations
Flux: a reactive stream of 0-N elements
Mono: a reactive stream of 0-1 elements
Ā 
In addition to Reactive Streams, Reactor extensively
implements APIs deļ¬ned by Reactive Extensions (Rx)
(http://reactivex.io)
Spring WebFlux runs on top of Reactor non-blocking IO, if
Netty is chosen as the underlying web server among Tomcat,
Jetty, Undertow.
Ā 
Spring WebClient is built on top of Reactor non-blocking IO
AccountService with Reactor
public class UserRepository {
public Mono<User> get(String userId) {
return Mono.justOrEmpty(FakeData.users.get(userId));
}
}
public class AccountRepository {
public Flux<Account> get(String accountNumber) {
return Flux.fromIterable(
FakeData.accounts.get(accountNumber)
);
}
}
public class FunctionalAccountService {
private UserRepository userRepository;
private AccountRepository accountRepository;
...
public FunctionalAccountService(UserRepository userRepository, AccountRepository accountRepository) {
this.userRepository = userRepository;
this.accountRepository = accountRepository;
}
public Flux<AccountInfo> getAccountInfo(String accountNumber) {
return this.accountRepository.get(accountNumber).flatMap(account ->
this.userRepository.get(account.getUserId()).map(user ->
new AccountInfo(
user.getId(), user.getName(),
account.getAccountNumber(), account.getBalance())));
}
}
Mono/Flux.ļ¬‚atMap
3 ļ¬‚avours of ļ¬‚atMap
ļ¬‚atMap
ļ¬‚atMapSequential
concatMap
Generation of inners
and subscription
Ordering of the
ļ¬‚attened values
Interleaving
ļ¬‚atMap Eagerly subscribing to
inners
No Yes
ļ¬‚atMapSequential Eagerly subscribing to
inners
Yes (elements from
late inners are
queued)
No
concatMap subscribing to inners
one by one
Yes No
Threading Control
In Reactor, the execution model and where the execution happens is
determined by the Scheduler that is used. A Scheduler has scheduling
responsibilities similar to an ExecutorService
Ā 
Ā 
Default Schedulers
Ā 
Schedulers.elastic()
An elastic thread pool (Schedulers.elastic()). It creates new worker pools as needed, and reuse idle
ones.
Ā 
Schedulers.parallel()
a ļ¬xed pool of workers that is tuned for parallel work (Schedulers.parallel()). It creates as many
workers as you have CPU cores.
Ā 
Schedulers.immediate()
the current thread
Create new instances
Schedulers.newElastic
Schedulers.newParallel
Schedulers.newSingle
Schedulers.fromExecutor
Flux/Mono.publishOn
publishOn takes signals from upstream and replays them
downstream while executing the callback on a worker from
the associated Scheduler.
https://projectreactor.io/docs/core/release/reference/#schedulers
Flux/Mono.subscribeOn
https://projectreactor.io/docs/core/release/reference/#schedulers
subscribeOn applies to the subscription process, when that
backward chain is constructed.
As a consequence, no matter where you place the
subscribeOn in the chain, it always aļ¬€ects the context of the
source emission.
Getting closer to Project Reactor
Reactor 3 Reference Guide
https://projectreactor.io/docs/core/release/reference/
Reactor 3 Javadoc
https://projectreactor.io/docs/core/release/api/
methods are illustrated with diagrams
Put it all together
A device simulator that simulator speciļ¬ed
number of devices.
Ā 
Each device periodically reportsĀ 
heartbeat
stats
to a device controller
First create a stream of device MACs
List<String> deviceMacs = new MacGenerator("AA", "BB").generate(deviceCount);
Flux<String> deviceMacFlux = Flux.fromIterable();
Flux<DeviceRequest> createDeviceStatsStream(String deviceMac) {
...
}
Assume we have a method that creates a stream of reporting
requests for a single device.
Ā 
The stream is a sum of heartbeat/stats request stream
Flux<DeviceRequest> requestFlux = deviceMacFlux.flatMap(mac -> createDeviceRequestStream(mac));
How do we turn a stream of device MAC into a stream of device request stream and
combine each single device request stream into a single massive one?
Ā 
Yes... ļ¬‚atMap
Dive deeper to implement (deviceMac => Flux<DeviceRequest>)
A device request stream is composed ofĀ 
heatbeat request stream
stats request stream
private Flux<DeviceRequest> createDeviceRequestStream(String deviceMac) {
return Flux.merge(
createDeviceHeartbeatStream(deviceMac),
createDeviceStatsStream(deviceMac)
);
}
Now what's left is the terminal streams,
heartbeat/stats request streams
heartbeat/stats request streams emit elements in a periodical manner
We need a source stream that generates elements in a ļ¬xed time interval.
Then each element is transformed to a DeviceRequest.
Ā 
Flux.interval(Duration) is what we need
private Flux<DeviceRequest> createDeviceHeartbeatStream(String deviceMac) {
return Flux.interval(Duration.ofSeconds(this.heartbeatIntervalInSeconds))
.map(n -> // create a DeviceRequest out of the given deviceMac);
}
private Flux<DeviceRequest> createDeviceStatsStream(String deviceMac) {
return Flux.interval(Duration.ofSeconds(this.statsIntervalInSeconds))
.map(n -> // create a DeviceRequest out of the given deviceMac);,
List<String> deviceMacs = this.macGenerator.generate(this.deviceCount);
Flux.fromIterable(deviceMacs).flatMap(mac -> createDeviceRequestStream(mac))
.subscribe(request -> /* sending reporting request to device controller via http */ );
Flux<DeviceRequest> createDeviceRequestStream(String deviceMac) {
return Flux.merge(
createDeviceHeartbeatStream(deviceMac),
createDeviceStatsStream(deviceMac)
);
}
Flux<DeviceRequest> createDeviceHeartbeatStream(String deviceMac) {
return Flux.interval(Duration.ofSeconds(this.heartbeatIntervalInSeconds))
.map(n -> new DeviceRequest(
deviceMac,
System.currentTimeMillis(),
ReportType.heartbeat,
this.urlBuilder.url(ReportType.heartbeat, deviceMac)));
}
Flux<DeviceRequest> createDeviceStatsStream(String deviceMac) {
return Flux.interval(Duration.ofSeconds(this.statsIntervalInSeconds))
.map(n -> new DeviceRequest(
deviceMac,
System.currentTimeMillis(),
ReportType.stats,
this.urlBuilder.url(ReportType.stats, deviceMac)));
}
The problem with Flux.ļ¬‚atMapĀ 
ļ¬‚atMap eagerly subscribes to inners with conļ¬gurable value with the default as 256
public static final int SMALL_BUFFER_SIZE = Math.max(16,
Integer.parseInt(System.getProperty("reactor.bufferSize.small", "256")));
We observed that only 256 devices reported heartbeat/stats
even we speciļ¬ed more than 256 devices.
Why?
Each device request stream is an inļ¬nite stream, and ļ¬‚atMap
eagerly subscribes ļ¬rst 256 inner streams.
Ā 
Each device request steam is an inļ¬nite stream
(Flux.interval()). That means only ļ¬rst 256 device streams get
to be created and emit data.
More on ļ¬‚atMap
ļ¬‚atMap exposes 2 conļ¬gurable parameters for eager inner
stream subscription
maxConcurrency
prefetch
ļ¬‚atMap eagerly subscribes "maxConcurrentcy" inner streams when
subscribed (onSubscribe)
Ā 
When subscribing inner stream, it will also pre-fetch "prefetch" elements by
invoking Subscription.request(prefetch) of inner stream.
To ļ¬x this problem, we explicitly conļ¬gure "maxConcurrency" when ļ¬‚atMapping
Flux.fromIterable(deviceMacs).flatMap(mac -> createDeviceRequestStream(mac), deviceMacs.size())
DemoDemo
Q & AQ & A

More Related Content

What's hot

Spring Boot
Spring BootSpring Boot
Spring BootJiayun Zhou
Ā 
Understanding Reactive Programming
Understanding Reactive ProgrammingUnderstanding Reactive Programming
Understanding Reactive ProgrammingAndres Almiray
Ā 
Building RESTful applications using Spring MVC
Building RESTful applications using Spring MVCBuilding RESTful applications using Spring MVC
Building RESTful applications using Spring MVCIndicThreads
Ā 
Practical non blocking microservices in java 8
Practical non blocking microservices in java 8Practical non blocking microservices in java 8
Practical non blocking microservices in java 8Michal Balinski
Ā 
REST APIs with Spring
REST APIs with SpringREST APIs with Spring
REST APIs with SpringJoshua Long
Ā 
Unlocking the Power of Lakehouse Architectures with Apache Pulsar and Apache ...
Unlocking the Power of Lakehouse Architectures with Apache Pulsar and Apache ...Unlocking the Power of Lakehouse Architectures with Apache Pulsar and Apache ...
Unlocking the Power of Lakehouse Architectures with Apache Pulsar and Apache ...StreamNative
Ā 
Reactive programming by spring webflux - DN Scrum Breakfast - Nov 2018
Reactive programming by spring webflux - DN Scrum Breakfast - Nov 2018Reactive programming by spring webflux - DN Scrum Breakfast - Nov 2018
Reactive programming by spring webflux - DN Scrum Breakfast - Nov 2018Scrum Breakfast Vietnam
Ā 
Let's keep it simple and streaming.pdf
Let's keep it simple and streaming.pdfLet's keep it simple and streaming.pdf
Let's keep it simple and streaming.pdfVMware Tanzu
Ā 
Best Practices for Middleware and Integration Architecture Modernization with...
Best Practices for Middleware and Integration Architecture Modernization with...Best Practices for Middleware and Integration Architecture Modernization with...
Best Practices for Middleware and Integration Architecture Modernization with...Claus Ibsen
Ā 
Spring Data JPA
Spring Data JPASpring Data JPA
Spring Data JPACheng Ta Yeh
Ā 
Java 8 Lambda Expressions & Streams
Java 8 Lambda Expressions & StreamsJava 8 Lambda Expressions & Streams
Java 8 Lambda Expressions & StreamsNewCircle Training
Ā 
Easy data-with-spring-data-jpa
Easy data-with-spring-data-jpaEasy data-with-spring-data-jpa
Easy data-with-spring-data-jpaStaples
Ā 
Spring Framework - AOP
Spring Framework - AOPSpring Framework - AOP
Spring Framework - AOPDzmitry Naskou
Ā 
Extending Flink SQL for stream processing use cases
Extending Flink SQL for stream processing use casesExtending Flink SQL for stream processing use cases
Extending Flink SQL for stream processing use casesFlink Forward
Ā 
Exactly-once Stream Processing with Kafka Streams
Exactly-once Stream Processing with Kafka StreamsExactly-once Stream Processing with Kafka Streams
Exactly-once Stream Processing with Kafka StreamsGuozhang Wang
Ā 
Spring Framework - Data Access
Spring Framework - Data AccessSpring Framework - Data Access
Spring Framework - Data AccessDzmitry Naskou
Ā 

What's hot (20)

Spring Boot
Spring BootSpring Boot
Spring Boot
Ā 
Understanding Reactive Programming
Understanding Reactive ProgrammingUnderstanding Reactive Programming
Understanding Reactive Programming
Ā 
Building RESTful applications using Spring MVC
Building RESTful applications using Spring MVCBuilding RESTful applications using Spring MVC
Building RESTful applications using Spring MVC
Ā 
Practical non blocking microservices in java 8
Practical non blocking microservices in java 8Practical non blocking microservices in java 8
Practical non blocking microservices in java 8
Ā 
REST APIs with Spring
REST APIs with SpringREST APIs with Spring
REST APIs with Spring
Ā 
Unlocking the Power of Lakehouse Architectures with Apache Pulsar and Apache ...
Unlocking the Power of Lakehouse Architectures with Apache Pulsar and Apache ...Unlocking the Power of Lakehouse Architectures with Apache Pulsar and Apache ...
Unlocking the Power of Lakehouse Architectures with Apache Pulsar and Apache ...
Ā 
Reactive programming by spring webflux - DN Scrum Breakfast - Nov 2018
Reactive programming by spring webflux - DN Scrum Breakfast - Nov 2018Reactive programming by spring webflux - DN Scrum Breakfast - Nov 2018
Reactive programming by spring webflux - DN Scrum Breakfast - Nov 2018
Ā 
Let's keep it simple and streaming.pdf
Let's keep it simple and streaming.pdfLet's keep it simple and streaming.pdf
Let's keep it simple and streaming.pdf
Ā 
Spring Webflux
Spring WebfluxSpring Webflux
Spring Webflux
Ā 
Spring Cloud Gateway
Spring Cloud GatewaySpring Cloud Gateway
Spring Cloud Gateway
Ā 
Best Practices for Middleware and Integration Architecture Modernization with...
Best Practices for Middleware and Integration Architecture Modernization with...Best Practices for Middleware and Integration Architecture Modernization with...
Best Practices for Middleware and Integration Architecture Modernization with...
Ā 
Spring Data JPA
Spring Data JPASpring Data JPA
Spring Data JPA
Ā 
Spring data jpa
Spring data jpaSpring data jpa
Spring data jpa
Ā 
Java 8 Lambda Expressions & Streams
Java 8 Lambda Expressions & StreamsJava 8 Lambda Expressions & Streams
Java 8 Lambda Expressions & Streams
Ā 
Reactive programming intro
Reactive programming introReactive programming intro
Reactive programming intro
Ā 
Easy data-with-spring-data-jpa
Easy data-with-spring-data-jpaEasy data-with-spring-data-jpa
Easy data-with-spring-data-jpa
Ā 
Spring Framework - AOP
Spring Framework - AOPSpring Framework - AOP
Spring Framework - AOP
Ā 
Extending Flink SQL for stream processing use cases
Extending Flink SQL for stream processing use casesExtending Flink SQL for stream processing use cases
Extending Flink SQL for stream processing use cases
Ā 
Exactly-once Stream Processing with Kafka Streams
Exactly-once Stream Processing with Kafka StreamsExactly-once Stream Processing with Kafka Streams
Exactly-once Stream Processing with Kafka Streams
Ā 
Spring Framework - Data Access
Spring Framework - Data AccessSpring Framework - Data Access
Spring Framework - Data Access
Ā 

Similar to Being Functional on Reactive Streams with Spring Reactor

I am getting these errors for getcountgroupbymedia Cannot infer type a (1).docx
I am getting these errors for getcountgroupbymedia Cannot infer type a (1).docxI am getting these errors for getcountgroupbymedia Cannot infer type a (1).docx
I am getting these errors for getcountgroupbymedia Cannot infer type a (1).docxPaulntmMilleri
Ā 
Pragmatic functional refactoring with java 8 (1)
Pragmatic functional refactoring with java 8 (1)Pragmatic functional refactoring with java 8 (1)
Pragmatic functional refactoring with java 8 (1)RichardWarburton
Ā 
Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8RichardWarburton
Ā 
This is a C# project . I am expected to create as this image shows. .pdf
This is a C# project . I am expected to create as this image shows. .pdfThis is a C# project . I am expected to create as this image shows. .pdf
This is a C# project . I am expected to create as this image shows. .pdfindiaartz
Ā 
Dti2143 chapter 5
Dti2143 chapter 5Dti2143 chapter 5
Dti2143 chapter 5alish sha
Ā 
Java 8 Lambda Built-in Functional Interfaces
Java 8 Lambda Built-in Functional InterfacesJava 8 Lambda Built-in Functional Interfaces
Java 8 Lambda Built-in Functional InterfacesGanesh Samarthyam
Ā 
Pragmatic Functional Refactoring with Java 8
Pragmatic Functional Refactoring with Java 8Pragmatic Functional Refactoring with Java 8
Pragmatic Functional Refactoring with Java 8Codemotion
Ā 
Functions & Procedures [7]
Functions & Procedures [7]Functions & Procedures [7]
Functions & Procedures [7]ecko_disasterz
Ā 
Apache Flink Training: DataStream API Part 2 Advanced
Apache Flink Training: DataStream API Part 2 Advanced Apache Flink Training: DataStream API Part 2 Advanced
Apache Flink Training: DataStream API Part 2 Advanced Flink Forward
Ā 
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdfJAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdfcalderoncasto9163
Ā 
SeneJug java_8_prez_122015
SeneJug java_8_prez_122015SeneJug java_8_prez_122015
SeneJug java_8_prez_122015senejug
Ā 
Functional Java 8 in everyday life
Functional Java 8 in everyday lifeFunctional Java 8 in everyday life
Functional Java 8 in everyday lifeAndrea Iacono
Ā 
Creat Shape classes from scratch DETAILS You will create 3 shape cla.pdf
Creat Shape classes from scratch DETAILS You will create 3 shape cla.pdfCreat Shape classes from scratch DETAILS You will create 3 shape cla.pdf
Creat Shape classes from scratch DETAILS You will create 3 shape cla.pdfaromanets
Ā 
Java programI made this Account.java below. Using the attached cod.pdf
Java programI made this Account.java below. Using the attached cod.pdfJava programI made this Account.java below. Using the attached cod.pdf
Java programI made this Account.java below. Using the attached cod.pdffathimafancy
Ā 
C++ Function
C++ FunctionC++ Function
C++ FunctionHajar
Ā 
documents.pub_new-features-in-java-8-it-jpoialjavanaitedwien15java8pdf-java-8...
documents.pub_new-features-in-java-8-it-jpoialjavanaitedwien15java8pdf-java-8...documents.pub_new-features-in-java-8-it-jpoialjavanaitedwien15java8pdf-java-8...
documents.pub_new-features-in-java-8-it-jpoialjavanaitedwien15java8pdf-java-8...Akaks
Ā 
2.overview of c++ ________lecture2
2.overview of c++  ________lecture22.overview of c++  ________lecture2
2.overview of c++ ________lecture2Warui Maina
Ā 
ā€œSOLID principles in PHP ā€“ how to apply them in PHP and why should we careā€œ b...
ā€œSOLID principles in PHP ā€“ how to apply them in PHP and why should we careā€œ b...ā€œSOLID principles in PHP ā€“ how to apply them in PHP and why should we careā€œ b...
ā€œSOLID principles in PHP ā€“ how to apply them in PHP and why should we careā€œ b...DevClub_lv
Ā 

Similar to Being Functional on Reactive Streams with Spring Reactor (20)

I am getting these errors for getcountgroupbymedia Cannot infer type a (1).docx
I am getting these errors for getcountgroupbymedia Cannot infer type a (1).docxI am getting these errors for getcountgroupbymedia Cannot infer type a (1).docx
I am getting these errors for getcountgroupbymedia Cannot infer type a (1).docx
Ā 
Pragmatic functional refactoring with java 8 (1)
Pragmatic functional refactoring with java 8 (1)Pragmatic functional refactoring with java 8 (1)
Pragmatic functional refactoring with java 8 (1)
Ā 
Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8
Ā 
This is a C# project . I am expected to create as this image shows. .pdf
This is a C# project . I am expected to create as this image shows. .pdfThis is a C# project . I am expected to create as this image shows. .pdf
This is a C# project . I am expected to create as this image shows. .pdf
Ā 
Dti2143 chapter 5
Dti2143 chapter 5Dti2143 chapter 5
Dti2143 chapter 5
Ā 
Java 8 Lambda Built-in Functional Interfaces
Java 8 Lambda Built-in Functional InterfacesJava 8 Lambda Built-in Functional Interfaces
Java 8 Lambda Built-in Functional Interfaces
Ā 
Pragmatic Functional Refactoring with Java 8
Pragmatic Functional Refactoring with Java 8Pragmatic Functional Refactoring with Java 8
Pragmatic Functional Refactoring with Java 8
Ā 
Functions & Procedures [7]
Functions & Procedures [7]Functions & Procedures [7]
Functions & Procedures [7]
Ā 
Apache Flink Training: DataStream API Part 2 Advanced
Apache Flink Training: DataStream API Part 2 Advanced Apache Flink Training: DataStream API Part 2 Advanced
Apache Flink Training: DataStream API Part 2 Advanced
Ā 
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdfJAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
JAVA...With N.E.T_B.E.A.N.S___________________________________.pdf
Ā 
SeneJug java_8_prez_122015
SeneJug java_8_prez_122015SeneJug java_8_prez_122015
SeneJug java_8_prez_122015
Ā 
Functional Java 8 in everyday life
Functional Java 8 in everyday lifeFunctional Java 8 in everyday life
Functional Java 8 in everyday life
Ā 
Creat Shape classes from scratch DETAILS You will create 3 shape cla.pdf
Creat Shape classes from scratch DETAILS You will create 3 shape cla.pdfCreat Shape classes from scratch DETAILS You will create 3 shape cla.pdf
Creat Shape classes from scratch DETAILS You will create 3 shape cla.pdf
Ā 
Java programI made this Account.java below. Using the attached cod.pdf
Java programI made this Account.java below. Using the attached cod.pdfJava programI made this Account.java below. Using the attached cod.pdf
Java programI made this Account.java below. Using the attached cod.pdf
Ā 
C++ Function
C++ FunctionC++ Function
C++ Function
Ā 
documents.pub_new-features-in-java-8-it-jpoialjavanaitedwien15java8pdf-java-8...
documents.pub_new-features-in-java-8-it-jpoialjavanaitedwien15java8pdf-java-8...documents.pub_new-features-in-java-8-it-jpoialjavanaitedwien15java8pdf-java-8...
documents.pub_new-features-in-java-8-it-jpoialjavanaitedwien15java8pdf-java-8...
Ā 
Pro.docx
Pro.docxPro.docx
Pro.docx
Ā 
Wien15 java8
Wien15 java8Wien15 java8
Wien15 java8
Ā 
2.overview of c++ ________lecture2
2.overview of c++  ________lecture22.overview of c++  ________lecture2
2.overview of c++ ________lecture2
Ā 
ā€œSOLID principles in PHP ā€“ how to apply them in PHP and why should we careā€œ b...
ā€œSOLID principles in PHP ā€“ how to apply them in PHP and why should we careā€œ b...ā€œSOLID principles in PHP ā€“ how to apply them in PHP and why should we careā€œ b...
ā€œSOLID principles in PHP ā€“ how to apply them in PHP and why should we careā€œ b...
Ā 

Recently uploaded

Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
Ā 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmSujith Sukumaran
Ā 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesPhilip Schwarz
Ā 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
Ā 
software engineering Chapter 5 System modeling.pptx
software engineering Chapter 5 System modeling.pptxsoftware engineering Chapter 5 System modeling.pptx
software engineering Chapter 5 System modeling.pptxnada99848
Ā 
办ē†å­¦ä½čƁ(UQę–‡å‡­čƁ书)ę˜†å£«å…°å¤§å­¦ęƕäøščÆęˆē»©å•åŽŸē‰ˆäø€ęØ”äø€ę ·
办ē†å­¦ä½čƁ(UQę–‡å‡­čƁ书)ę˜†å£«å…°å¤§å­¦ęƕäøščÆęˆē»©å•åŽŸē‰ˆäø€ęØ”äø€ę ·åŠžē†å­¦ä½čƁ(UQę–‡å‡­čƁ书)ę˜†å£«å…°å¤§å­¦ęƕäøščÆęˆē»©å•åŽŸē‰ˆäø€ęØ”äø€ę ·
办ē†å­¦ä½čƁ(UQę–‡å‡­čƁ书)ę˜†å£«å…°å¤§å­¦ęƕäøščÆęˆē»©å•åŽŸē‰ˆäø€ęØ”äø€ę ·umasea
Ā 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
Ā 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio, Inc.
Ā 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
Ā 
GOING AOT WITH GRAALVM ā€“ DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM ā€“ DEVOXX GREECE.pdfGOING AOT WITH GRAALVM ā€“ DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM ā€“ DEVOXX GREECE.pdfAlina Yurenko
Ā 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...Christina Lin
Ā 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
Ā 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWave PLM
Ā 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
Ā 
The Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfThe Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfPower Karaoke
Ā 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
Ā 
React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaHanief Utama
Ā 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureDinusha Kumarasiri
Ā 

Recently uploaded (20)

Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Ā 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalm
Ā 
Hot Sexy call girls in Patel NagaršŸ” 9953056974 šŸ” escort Service
Hot Sexy call girls in Patel NagaršŸ” 9953056974 šŸ” escort ServiceHot Sexy call girls in Patel NagaršŸ” 9953056974 šŸ” escort Service
Hot Sexy call girls in Patel NagaršŸ” 9953056974 šŸ” escort Service
Ā 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a series
Ā 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
Ā 
software engineering Chapter 5 System modeling.pptx
software engineering Chapter 5 System modeling.pptxsoftware engineering Chapter 5 System modeling.pptx
software engineering Chapter 5 System modeling.pptx
Ā 
办ē†å­¦ä½čƁ(UQę–‡å‡­čƁ书)ę˜†å£«å…°å¤§å­¦ęƕäøščÆęˆē»©å•åŽŸē‰ˆäø€ęØ”äø€ę ·
办ē†å­¦ä½čƁ(UQę–‡å‡­čƁ书)ę˜†å£«å…°å¤§å­¦ęƕäøščÆęˆē»©å•åŽŸē‰ˆäø€ęØ”äø€ę ·åŠžē†å­¦ä½čƁ(UQę–‡å‡­čƁ书)ę˜†å£«å…°å¤§å­¦ęƕäøščÆęˆē»©å•åŽŸē‰ˆäø€ęØ”äø€ę ·
办ē†å­¦ä½čƁ(UQę–‡å‡­čƁ书)ę˜†å£«å…°å¤§å­¦ęƕäøščÆęˆē»©å•åŽŸē‰ˆäø€ęØ”äø€ę ·
Ā 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
Ā 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Ā 
Call Girls In Mukherjee Nagar šŸ“± 9999965857 šŸ¤© Delhi šŸ«¦ HOT AND SEXY VVIP šŸŽ SE...
Call Girls In Mukherjee Nagar šŸ“±  9999965857  šŸ¤© Delhi šŸ«¦ HOT AND SEXY VVIP šŸŽ SE...Call Girls In Mukherjee Nagar šŸ“±  9999965857  šŸ¤© Delhi šŸ«¦ HOT AND SEXY VVIP šŸŽ SE...
Call Girls In Mukherjee Nagar šŸ“± 9999965857 šŸ¤© Delhi šŸ«¦ HOT AND SEXY VVIP šŸŽ SE...
Ā 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
Ā 
GOING AOT WITH GRAALVM ā€“ DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM ā€“ DEVOXX GREECE.pdfGOING AOT WITH GRAALVM ā€“ DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM ā€“ DEVOXX GREECE.pdf
Ā 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
Ā 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Ā 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need It
Ā 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
Ā 
The Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfThe Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdf
Ā 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Ā 
React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief Utama
Ā 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with Azure
Ā 

Being Functional on Reactive Streams with Spring Reactor

  • 1. Being FunctionalBeing Functional on Reactive StreamsReactive Streams with Spring ReactorSpring Reactor
  • 2. Source code is available on https://github.com/maxxhuang/functional-reactive-stream-with-spring-reactor GitHub
  • 3. More Functional with Java 8More Functional with Java 8 Optional Java 8 Stream CompletableFuture
  • 5. Assume we are building a service on the imaginary banking model public class User { private String id; private String name; ... } public class Account { private String accountNumber; private String userId; private double balance; ... } public class AccountInfo { private String userId; private String userName; private String accountNumber; private double balance; ... }
  • 6. and with Java 8 Optional, we have the repositories public class UserRepository { public Optional<User> get(String userId) { return Optional.ofNullable(FakeData.users.get(userId)); } } public class AccountRepository { public Optional<Account> get(String accountNumber) { return FakeData.accounts.get(accountNumber).stream().findFirst(); } }
  • 7. Now we are building a service for querying account details combining user information public class AccountService { private UserRepository userRepository; private AccountRepository accountRepository; ... public Optional<AccountInfo> getAccountInfo(String accountNumber) { Optional<Account> optAccount = this.accountRepository.get(accountNumber); if (!optAccount.isPresent()) { return Optional.empty(); } Account account = optAccount.get(); Optional<User> optUser = this.userRepository.get(account.getUserId()); if (!optUser.isPresent()) { return Optional.empty(); } User user = optUser.get(); return Optional.of(AccountInfo.create(user, account)); } }
  • 8. We are treating Optional as a data structure containing a possible existing value That's nothing diļ¬€erent from using null as an indication of "absence of value" Optional<Account> optAccount = this.accountRepository.get(accountNumber); if (!optAccount.isPresent()) { return Optional.empty(); } Account account = optAccount.get(); Optional<User> optUser = this.userRepository.get(account.getUserId()); if (!optUser.isPresent()) { return Optional.empty(); } User user = optUser.get(); return Optional.of(AccountInfo.create(user, account)); Account account = this.accountRepository.get(accountNumber); if (account == null) { return null; } User user = this.userRepository.get(account.getUserId()); if (user == null) { return null; } return AccountInfo.create(user, account);
  • 9. Can we combine 2 Optional values in a more functional way? Optional<Account> optAccount = this.accountRepository.get(accountNumber); if (!optAccount.isPresent()) { return Optional.empty(); } Account account = optAccount.get(); Optional<User> optUser = this.userRepository.get(account.getUserId()); if (!optUser.isPresent()) { return Optional.empty(); } User user = optUser.get(); return Optional.of(AccountInfo.create(user, account)); public<U> Optional<U> flatMap( Function<? super T, Optional<U>> mapper) { requireNonNull(mapper); if (!isPresent()) return empty(); else { return requireNonNull(mapper.apply(value)); } } public<U> Optional<U> map( Function<? super T, ? extends U> mapper) { requireNonNull(mapper); if (!isPresent()) return empty(); else { return Optional.ofNullable(mapper.apply(value)); } } Let's check out Optional.ļ¬‚atMap and Optional.map
  • 10. public class Optional<T> { public<U> Optional<U> map(Function<? super T, ? extends U> mapper) { requireNonNull(mapper); if (!isPresent()) return empty(); else { return Optional.ofNullable(mapper.apply(value)); } } } Optional.map takes a function of (T => U) to transform the value of type "T" into something else of type "U" Optional.map creates an Optional<U> out of Ā an Optional<T>
  • 11. public class Optional<T> { public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) { requireNonNull(mapper); if (!isPresent()) return empty(); else { return requireNonNull(mapper.apply(value)); } } } Optional.ļ¬‚atMap takes a mapper function of (T => Optional<U>) Ā  This mapper function, with the access to the actual value of Optional<T>, creates another Optional<U>
  • 12. Optional.ļ¬‚atMap is said to be more powerful than Optional.map ļ¬‚atMap is able to compose other Optional objects and come out with a new Optional object Ā  ļ¬‚atMap has the ability of ļ¬‚ow control. It can decide whether or not to create a new Optional object or simply an empty Optional object depending on the given access to previous Optional value. Something about ļ¬‚atMap
  • 13. Attempt to compose Optional using map Optional<Account> optAccount = accountRepository.get(accountNumber); Optional<Optional<AccountInfo>> result = optAccount.map(account -> { Optional<User> optUser = userRepository.get(account.getUserIdI()); Optional<AccountInfo> optAccountInfo = optUser.map(user -> AccountInfo.create(user, account); return optAccountInfo }); Optional.map is unable to compose 2 Optional objects and peel oļ¬€ or ļ¬‚atten the outer Optional. Ā  Optional.map just accumulates the layers of nested Optional. It is not easy to access the value of multi-layer Optonal, e.g. Optinoal<Optional<AccountInfo>>Ā 
  • 14. Attempt to compose Optional using ļ¬‚atMap Optional<Account> optAccount = accountRepository.get(accountNumber); Optional<AccountInfo> result = optAccount.flatMap(account -> { Optional<User> optUser = userRepository.get(account.getUserIdI()); Optional<AccountInfo> optAccountInfo = optUser.flatMap(user -> Optional.of(AccountInfo.create(user, account)); return optAccountInfo }); With ļ¬‚atMap, we are capable of composing Optional<Account> and Optional<User> to obtain a "ļ¬‚attened" Optional<AccountInfo>
  • 15. Use ļ¬‚atMap for Flow Control Optional<Account> optAccount = accountRepository.get(accountNumber); Optional<AccountInfo> result = optAccount.flatMap(account -> { if (account.isCredential()) { return Optional.empty(); } Optional<User> optUser = userRepository.get(account.getUserIdI()); Optional<AccountInfo> optAccountInfo = optUser.flatMap(user -> Optional.of(AccountInfo.create(user, account)); return optAccountInfo });
  • 16. ļ¬‚atMap and map work together Optional<Account> optAccount = accountRepository.get(accountNumber); Optional<AccountInfo> result = optAccount.flatMap(account -> { Optional<User> optUser = userRepository.get(account.getUserIdI()); Optional<AccountInfo> optAccountInfo = optUser.flatMap(user -> Optional.of(AccountInfo.create(user, account)); return optAccountInfo }); Don't bother to wrap AccountInfo with an Optional in the last ļ¬‚atMap composition. We can use "map" to achieve the same result Optional<Account> optAccount = accountRepository.get(accountNumber); Optional<AccountInfo> result = optAccount.flatMap(account -> { Optional<User> optUser = userRepository.get(account.getUserIdI()); Optional<AccountInfo> optAccountInfo = optUser.map(user -> AccountInfo.create(user, account); return optAccountInfo });
  • 17. Composing 2 or more Optionals Optional<TransactionInfo> result = accountRepository.get(accountNumber).flatMap(account -> userRepository.get(account.getUserId()).flatMap(user -> transactionRepository.get(account.getAccountNumber()).map(transaction -> { // use "account", "user", "transaction" to create TransactionInfo ... return TransactionInfo.create(user, account, transaction); }) ) ); Optional<SomeType> result = optional1.flatMap(v1 -> optional2.flatMap(v2 -> optional3.flatMap(v3 -> ... optionaln.map(vn -> // do something with v1, v2, v3,..., vn //return some value of some type return SomeType.create(...); ) ) ) );
  • 19. Banking Repositories with Java 8 Stream public class UserRepository { public List<User> get(String userId) { return FakeData.users.containsKey(userId) ? Collections.singletonList(FakeData.users.get(userId)) : Collections.emptyList(); } } public class AccountRepository { public List<Account> get(String accountNumber) { return FakeData.accounts.get(accountNumber); } }
  • 20. AccountService public class AccountService { private UserRepository userRepository; private AccountRepository accountRepository; ... public List<AccountInfo> getAccountInfo(String accountNumber) { List<AccountInfo> infos = new ArrayList<>(); for (Account account : this.accountRepository.get(accountNumber)) { for (User user : this.userRepository.get(account.getUserId())) { infos.add(AccountInfo.create(user, account)); } } return infos; } }
  • 21. AccountService being functional public class FunctionalAccountService { private UserRepository userRepository; private AccountRepository accountRepository; ... public List<AccountInfo> getAccountInfo(String accountNumber) { return this.accountRepository.get(accountNumber).stream().flatMap(account -> this.userRepository.get(account.getUserId()).stream().map(user -> AccountInfo.create(user, account))) .collect(Collectors.toList()); } }
  • 23. Banking Repositories with CompletableFuture public class UserRepository extends BaseRepository { public UserRepository() { super(defaultExecutorService()); } public CompletableFuture<User> get(String userId) { return CompletableFuture.supplyAsync( () -> FakeData.users.get(userId), this.executorService ); } } public class AccountRepository extends BaseRepository { public AccountRepository() { super(defaultExecutorService()); } public CompletableFuture<Account> get(String accountNumber) { return CompletableFuture.supplyAsync( () -> { List<Account> accounts = FakeData.accounts.get(accountNumber); return accounts.isEmpty() ? null : accounts.get(0); }, this.executorService); } }
  • 24. AccountService public class AccountService { private UserRepository userRepository; private AccountRepository accountRepository; ... public CompletableFuture<AccountInfo> getAccountInfo(String accountNumber) { CompletableFuture<AccountInfo> result = new CompletableFuture<>(); try { CompletableFuture<Account> accountFuture = this.accountRepository.get(accountNumber); Account account = accountFuture.get(); if (account == null) result.complete(null); CompletableFuture<User> userFuture = this.userRepository.get(account.getUserId()); User user = userFuture.get(); if (user == null) result.complete(null); else result.complete(AccountInfo.create(user, account)); } catch (Exception e) { result.completeExceptionally(e); } return result; } }
  • 25. map and ļ¬‚atMap in disguise CompletableFuture does not have map and ļ¬‚atMap Ā  CompletableFuture deļ¬nes thenApply and thenCompose with similar sematics: thenApply -> map thenCompose -> ļ¬‚atMap public CompletableFuture<T> implements Future<T>, CompletionStage< // map public <U> CompletableFuture<U> thenApply( Function<? super T,? extends U> fn) { ... } // flatMap public <U> CompletableFuture<U> thenCompose( Function<? super T, ? extends CompletionStage<U>> fn) { ... } }
  • 26. Threading Control for CompletableFuture.map/ļ¬‚atMap There are variant thenApply and thenCompose to control the threading policy for the execution of mapping functions public CompletableFuture<T> implements Future<T>, CompletionStage<T> { public <U> CompletableFuture<U> thenApply( Function<? super T,? extends U> fn) {...} public <U> CompletableFuture<U> thenApplyAsync( Function<? super T,? extends U> fn) {...} public <U> CompletableFuture<U> thenApplyAsync( Function<? super T,? extends U> fn, Executor executor) {...} public <U> CompletableFuture<U> thenCompose( Function<? super T, ? extends CompletionStage<U>> fn) {...} public <U> CompletableFuture<U> thenComposeAsync( Function<? super T, ? extends CompletionStage<U>> fn) {...} public <U> CompletableFuture<U> thenComposeAsync( Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) {...} }
  • 27. AccountService with brighter future public class FunctionalAccountService { private UserRepository userRepository; private AccountRepository accountRepository; ... public CompletableFuture<AccountInfo> getAccountInfo(String accountNumber) { return this.accountRepository.get(accountNumber).thenCompose(account -> this.userRepository.get(account.getUserId()).thenApply(user -> AccountInfo.create(user, account))); } }
  • 28. Why are Optional, Stream, CompletableFuture able to chain the computations? Ā  What's the pattern? public final class Optional<T> { public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) public<U> Optional<U> map(Function<? super T, ? extends U> mapper) } public interface Stream<T> extends BaseStream<T, Stream<T>> { public <U> Stream<U> flatMap(Function<? super T, ? extends Stream<? extends U>> mapper) public <U> Stream<U> map(Function<? super T, ? extends U> mapper) } public CompletableFuture<T> implements Future<T>, CompletionStage<T> { // flatMap public <U> CompletableFuture<U> thenCompose( Function<? super T, ? extends CompletionStage<U>> fn) // map public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) }
  • 29. Monad // This is psudo code illustrating Monad structure M[T] { flatMap(mapper: T => M[U]): M[U] // unit wraps a value into monad context // // in Optional, it is Optional.of(...), Optional.ofNullable(...) // // in Stream, it is Stream.of(...) // // in CompletableFuture, it is CompletableFuture.supplyAsync(...) // or {new CompletableFuture().complete(...)} unit(value: T): M[T] } What about map? map can always be implemented with ļ¬‚atMap and unit. This proves again ļ¬‚atMap is more powerful than map. map(mapper: T => U) { return flatMap(v -> unit(mapper(v))) }
  • 30. Monad A great article explaining Monad in plain English http://blog.leichunfeng.com/blog/2015/11/08/functor-applicative-and-monad A monad is a computational context for some value with a "unit" method and a "ļ¬‚atMap" method Ā  Context value of M[T] Ā is passes as parameter of (T => M[U]). In the mapper function, you get to access the context value and decide the new monad value to return. accountRepository.get(accountNumber).flatMap(account -> { if (account.isCredential()) { return Optional.empty(); } Optional<User> optUser = userRepository.get(account.getUserIdI()); Optional<AccountInfo> optAccountInfo = optUser.flatMap(user -> Optional.of(AccountInfo.create(user, account)); return optAccountInfo });
  • 31. Use ļ¬‚atMap to implement other combinators JDK implementation for Optional.ļ¬lter public Optional<T> filter(Predicate<? super T> predicate) { Objects.requireNonNull(predicate); if (!isPresent()) return this; else return predicate.test(value) ? this : empty(); } another implementation with ļ¬‚atMap public Optional<T> filter(Predicate<? super T> predicate) { return flatMap(v -> predicate.test(v) ? this : Optional.empty(); } the emptiness test (isPresent()) has been done in ļ¬‚atMap
  • 32. Monad Comparision Type Context Value Composition Eļ¬€ect (ļ¬‚atMap) Optional a value may or may not exist composition happens if Optional contains value composition stops if Optional is empty and subsequent compositions are ignored CompletableFuture a value available in the future composition happens if CompletableFuture obtains a value without error ??? composition stops if error occurred in CompletableFuture and subsequent compositions are ignored ??? Stream each value in the stream composition happens if the stream is not empty composition stops if Stream is empty and subsequent compositions are ignored The resulting sub-streams are merged to a joint stream
  • 34. Streams are a series of elements emitted over time. The series potentially has no beginning and no end. Kevin Webber, A Journey into Reactive Streams Reactive Stream goes a step further by being able to signal demand from downstream thus controlling the overall speed of data elements ļ¬‚owing through it Walter Chang, Reactive Streams in Scala What is Reactive Streams
  • 35. Who deļ¬nes Reactive Streams? What is it? How is it related to Java 9 Flow https://en.wikipedia.org/wiki/Reactive_Streams
  • 36. The scope of Reactive Streams is to ļ¬nd a minimal set of interfaces, methods and protocols that will describe the necessary operations and entities to achieve the goalā€” asynchronous streams of data with non-blocking back pressure. www.reactive-streams.org So there are only 4 interfaces and and 7 methods in total inside reactive-streams-1.0.2.jar package org.reactivestreams; public interface Publisher<T> { public void subscribe(Subscriber<? super T> s); } public interface Subscriber<T> { public void onSubscribe(Subscription s); public void onNext(T t); public void onError(Throwable t); public void onComplete(); } public interface Subscription { public void request(long n); public void cancel(); } public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {} End-user DSLs or protocol binding APIs have purposefully been left out of the scope to encourage and enable diļ¬€erent implementations that potentially use diļ¬€erent programming languages to stay as true as possible to the idioms of their platform. www.reactive-streams.org
  • 37. Reactive Streams is a speciļ¬cation for library developers. Ā  Reactive-stream libraries complying with Reactive Steams speciļ¬cation are capable of 1. back-pressure control 2. interoperate with other libraries. Ā  For example, a Reactor Processor can subscribe to RxJava Producer given that Spring Reactor and RxJava are both Reactive Streams compliant.
  • 39. Dynamic Push / Pull request n elements push at most n elements Subscriber PublisherSubscription Requests can be made asynchronously. Multiple requests are accumulated on Publisher and will be served later Fast subscriber demands more elements; publisher don't need to wait for requests => push mode Slow subscriber request less; publish waits for requests => pull mode The dynamic push/pull makes back-pressure possible and ensure all participating components are resilient to massive load although this may degrade the performance
  • 40. Let's fake it till we make it public interface Publisher<T> { public void subscribe(Subscriber<? super T> s); } abstract public class Flowie<T> implements Publisher<T> { public static <T> Flowie<T> fromIterable(Iterable<T> iterable) { ... } } "Flowie", a homemade implementation for Reactive Streams Spring Reactor has "Flux"; RxJava comes with "Flowable". Why don't we create one of our own?
  • 41. public class FlowieIterable<T> extends Flowie<T> { private Iterable<T> iterable; public FlowieIterable(Iterable<T> iterable) { this.iterable = iterable; } @Override public void subscribe(Subscriber<? super T> subscriber) { try { Iterator<T> iterator = this.iterable.iterator(); subscriber.onSubscribe(new IterableSubscription<>(subscriber, iterator)); } catch (Exception e) { subscriber.onError(e); } } ... static class IterableSubscription<T> implements Subscription { private Subscriber<? super T> subscriber; private Iterator<? extends T> iterator; private boolean cancelled = false; private AtomicLong requested = new AtomicLong(0L); @Override public void request(long n) { ... } } } We take a similar approach to that for Spring Reactor Put major logic in Subscription object. This subscription object gets delivered to users via onSubscribe()
  • 42. static class IterableSubscription<T> implements Subscription { private boolean cancelled = false; /** * pending request * * The updates to the pending request may come from other threads via {@link Subscription#request(lo * * Use AtomicLong to make sure the update and comparison of pending request counter is conducted ato * */ private AtomicLong requested = new AtomicLong(0L); @Override public void request(long n) { long updatedRequested = addRequestAndReturnCurrent(n); if (updatedRequested == 0L) { doEmit(n); } } private void doEmit(long n) { long emitted = 0L; while (true) { Great care is taken to make sure multiple asynchronous Subscription.request(n) are accumulated and served atomically often leverage java.util.concurrent.atomic components use CAS (compare and set) to ensure counter is updated atomically at the right time Sometimes the price to maintain the correctness of request counter is messing up the code making it hard to read and understand.
  • 43. public class FlowieIterable<T> extends Flowie<T> { private Iterable<T> iterable; public FlowieIterable(Iterable<T> iterable) { this.iterable = iterable; } @Override public void subscribe(Subscriber<? super T> subscriber) { try { Iterator<T> iterator = this.iterable.iterator(); subscriber.onSubscribe(new IterableSubscription<>(subscriber, iterator)); } catch (Exception e) { subscriber.onError(e); } } /////////////////////////////////////////////////////////////////////////// static class IterableSubscription<T> implements Subscription { private Subscriber<? super T> subscriber; private Iterator<? extends T> iterator; private boolean cancelled = false; /** * pending request * * The updates to the pending request may come from other threads via {@link Subscription#request(lo * * Use AtomicLong to make sure the update and comparison of pending request counter is conducted ato * */
  • 44. Ya! We have the ļ¬rst Flowie implementation Flowie.fromIterable(Arrays.asList("a", "b", "c")).subscribe(new Subscriber<String>() { @Override public void onSubscribe(Subscription s) { s.request(Long.MAX_VALUE); } @Override public void onNext(String s) { System.out.println(s); } @Override public void onError(Throwable t) { } @Override public void onComplete() { } }); After subscribing, Subscription.request() needs to be invoked to trigger element emission a request of Long.MAX_VALUE elements, by the rule of 3.17 (https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.2/README.md#speciļ¬cation), is treated as requesting ALL elements from the publisher
  • 45. Let's pursue the banking service and make it more "Flowie"Ā  public class UserRepository { public Flowie<User> get(String userId) { List<User> user = FakeData.users.containsKey(userId) ? Collections.singletonList(FakeData.users.get(userId)) : Collections.emptyList(); return Flowie.fromIterable(user); } } public class AccountRepository { public Flowie<Account> get(String accountNumber) { return Flowie.fromIterable( FakeData.accounts.get(accountNumber) ); } }
  • 46. AccountService in callback hell public class AccountServiceInCallbackHell { private UserRepository userRepository; private AccountRepository accountRepository; ... /** * NOTE: This only works in a single-thread execution context. * The implementation does not catch the timing of "completion" in onComplete() or onError(). * In a muti-thread environment, the elements (User, Account) might not be ready * when this method returns. */ public Flowie<AccountInfo> getAccountInfo(String accountNumber) { final List<AccountInfo> result = new ArrayList<>(); this.accountRepository.get(accountNumber).subscribe(new Subscriber<Account>() { @Override public void onSubscribe(Subscription s) { s.request(Long.MAX_VALUE); } @Override public void onNext(Account account) { userRepository.get(account.getUserId()).subscribe(new Subscriber<User>() { @Override public void onSubscribe(Subscription s) { s.request(Long.MAX_VALUE); } @Override public void onNext(User user) { result.add(AccountInfo.create(user, account)); } ... }); } ...
  • 47. We need a way out of this callback hell.Ā  Ā  Can we make Flowie a monad? This way, we can do monadic composition of 2 Flowie values like this. flowieAccuont.flatMap(account -> flowieUser.map(user -> // create AccountInfo out of account, user accountInfo))
  • 48. Start with the simple one: Flowie.map abstract public class Flowie<T> implements Publisher<T> { public <R> Flowie<R> map(Function<? super T, ? extends R> mapper) { return new FlowieMap<>(this, mapper); } } public class FlowieMap<T, R> extends Flowie<R> { private Publisher<T> source; private Function<? super T, ? extends R> mapper; ... @Override public void subscribe(Subscriber<? super R> s) { this.source.subscribe(new MapSubscriber<>(s, this.mapper)); } static class MapSubscriber<T, R> implements Subscriber<T>, Subscription { private Subscriber<? super R> actualSubscriber; private Function<? super T, ? extends R> mapper; private Subscription upstreamSubscription; 2 levels of decorator pattern adoption 1st level is FlowMap decorating the actual Publisher 2nd level is MapSubscriber decorating the actual Subscriber MapSubscriber applies the mapper function in "onNext"
  • 49. Flowie.ļ¬‚atMap Compliant ļ¬‚atMap implementation for Reactive Streams is complicated and needs to tackle the concurrent sub- streams emissions and elements queuing. For quick demonstration of functional programming beneļ¬t, we rush a non-compliant implementation that only works in a single-thread execution context.
  • 50. Flowie.ļ¬‚atMap Implementation FYI /** * This implementation DOES NOT comply with Reactive Streams. It does not take care of * the situation where elements from sub-streams emitted asynchronously. * * The compliant implementation is complicated and usually needs one or more queues * to store un-consumed elements emitted from sub-streams. * * This non-compliant implementation only serves the purpose of demonstrating the * advantage of making reactive streams functional. */ public class FlowieNonCompliantSynchronousFlatMap<T, R> extends Flowie<R> { private Publisher<T> source; private Function<? super T, ? extends Publisher<? extends R>> mapper; public FlowieNonCompliantSynchronousFlatMap(Publisher<T> source, Function<? super T, ? extend this.source = source; this.mapper = mapper; } @Override public void subscribe(Subscriber<? super R> s) { this.source.subscribe(new SynchronousNonThreadSafeFlapMapSubscriber<>(s, this.mapper)); } /////////////////////////////////////////////////////////////////////////// static class SynchronousNonThreadSafeFlapMapSubscriber<T, R> implements Subscriber<T>, Subscr
  • 51. With map/ļ¬‚atMap in place, here is the upgraded Flowie AccountService public class FunctionalAccountService { private UserRepository userRepository; private AccountRepository accountRepository; ... public Flowie<AccountInfo> getAccountInfo(String accountNumber) { return this.accountRepository.get(accountNumber).flatMap(account -> this.userRepository.get(account.getUserId()).map(user -> new AccountInfo( user.getId(), user.getName(), account.getAccountNumber(), account.getBalance()))); } }
  • 53. Project Reactor implements Reactive Streams, inherently with non- blocking streaming natureĀ  Ā  On top of Reactive Streams, Project Reactor provides its own APIs for functional streaming handling and adapting other data source.
  • 54. Reactor oļ¬€ers 2 Publisher implementations Flux: a reactive stream of 0-N elements Mono: a reactive stream of 0-1 elements Ā  In addition to Reactive Streams, Reactor extensively implements APIs deļ¬ned by Reactive Extensions (Rx) (http://reactivex.io)
  • 55. Spring WebFlux runs on top of Reactor non-blocking IO, if Netty is chosen as the underlying web server among Tomcat, Jetty, Undertow. Ā  Spring WebClient is built on top of Reactor non-blocking IO
  • 56. AccountService with Reactor public class UserRepository { public Mono<User> get(String userId) { return Mono.justOrEmpty(FakeData.users.get(userId)); } } public class AccountRepository { public Flux<Account> get(String accountNumber) { return Flux.fromIterable( FakeData.accounts.get(accountNumber) ); } } public class FunctionalAccountService { private UserRepository userRepository; private AccountRepository accountRepository; ... public FunctionalAccountService(UserRepository userRepository, AccountRepository accountRepository) { this.userRepository = userRepository; this.accountRepository = accountRepository; } public Flux<AccountInfo> getAccountInfo(String accountNumber) { return this.accountRepository.get(accountNumber).flatMap(account -> this.userRepository.get(account.getUserId()).map(user -> new AccountInfo( user.getId(), user.getName(), account.getAccountNumber(), account.getBalance()))); } }
  • 57. Mono/Flux.ļ¬‚atMap 3 ļ¬‚avours of ļ¬‚atMap ļ¬‚atMap ļ¬‚atMapSequential concatMap Generation of inners and subscription Ordering of the ļ¬‚attened values Interleaving ļ¬‚atMap Eagerly subscribing to inners No Yes ļ¬‚atMapSequential Eagerly subscribing to inners Yes (elements from late inners are queued) No concatMap subscribing to inners one by one Yes No
  • 58. Threading Control In Reactor, the execution model and where the execution happens is determined by the Scheduler that is used. A Scheduler has scheduling responsibilities similar to an ExecutorService Ā  Ā  Default Schedulers Ā  Schedulers.elastic() An elastic thread pool (Schedulers.elastic()). It creates new worker pools as needed, and reuse idle ones. Ā  Schedulers.parallel() a ļ¬xed pool of workers that is tuned for parallel work (Schedulers.parallel()). It creates as many workers as you have CPU cores. Ā  Schedulers.immediate() the current thread Create new instances Schedulers.newElastic Schedulers.newParallel Schedulers.newSingle Schedulers.fromExecutor
  • 59. Flux/Mono.publishOn publishOn takes signals from upstream and replays them downstream while executing the callback on a worker from the associated Scheduler. https://projectreactor.io/docs/core/release/reference/#schedulers
  • 60. Flux/Mono.subscribeOn https://projectreactor.io/docs/core/release/reference/#schedulers subscribeOn applies to the subscription process, when that backward chain is constructed. As a consequence, no matter where you place the subscribeOn in the chain, it always aļ¬€ects the context of the source emission.
  • 61. Getting closer to Project Reactor Reactor 3 Reference Guide https://projectreactor.io/docs/core/release/reference/ Reactor 3 Javadoc https://projectreactor.io/docs/core/release/api/ methods are illustrated with diagrams
  • 62. Put it all together A device simulator that simulator speciļ¬ed number of devices. Ā  Each device periodically reportsĀ  heartbeat stats to a device controller
  • 63. First create a stream of device MACs List<String> deviceMacs = new MacGenerator("AA", "BB").generate(deviceCount); Flux<String> deviceMacFlux = Flux.fromIterable(); Flux<DeviceRequest> createDeviceStatsStream(String deviceMac) { ... } Assume we have a method that creates a stream of reporting requests for a single device. Ā  The stream is a sum of heartbeat/stats request stream Flux<DeviceRequest> requestFlux = deviceMacFlux.flatMap(mac -> createDeviceRequestStream(mac)); How do we turn a stream of device MAC into a stream of device request stream and combine each single device request stream into a single massive one? Ā  Yes... ļ¬‚atMap
  • 64. Dive deeper to implement (deviceMac => Flux<DeviceRequest>) A device request stream is composed ofĀ  heatbeat request stream stats request stream private Flux<DeviceRequest> createDeviceRequestStream(String deviceMac) { return Flux.merge( createDeviceHeartbeatStream(deviceMac), createDeviceStatsStream(deviceMac) ); } Now what's left is the terminal streams, heartbeat/stats request streams
  • 65. heartbeat/stats request streams emit elements in a periodical manner We need a source stream that generates elements in a ļ¬xed time interval. Then each element is transformed to a DeviceRequest. Ā  Flux.interval(Duration) is what we need private Flux<DeviceRequest> createDeviceHeartbeatStream(String deviceMac) { return Flux.interval(Duration.ofSeconds(this.heartbeatIntervalInSeconds)) .map(n -> // create a DeviceRequest out of the given deviceMac); } private Flux<DeviceRequest> createDeviceStatsStream(String deviceMac) { return Flux.interval(Duration.ofSeconds(this.statsIntervalInSeconds)) .map(n -> // create a DeviceRequest out of the given deviceMac);,
  • 66. List<String> deviceMacs = this.macGenerator.generate(this.deviceCount); Flux.fromIterable(deviceMacs).flatMap(mac -> createDeviceRequestStream(mac)) .subscribe(request -> /* sending reporting request to device controller via http */ ); Flux<DeviceRequest> createDeviceRequestStream(String deviceMac) { return Flux.merge( createDeviceHeartbeatStream(deviceMac), createDeviceStatsStream(deviceMac) ); } Flux<DeviceRequest> createDeviceHeartbeatStream(String deviceMac) { return Flux.interval(Duration.ofSeconds(this.heartbeatIntervalInSeconds)) .map(n -> new DeviceRequest( deviceMac, System.currentTimeMillis(), ReportType.heartbeat, this.urlBuilder.url(ReportType.heartbeat, deviceMac))); } Flux<DeviceRequest> createDeviceStatsStream(String deviceMac) { return Flux.interval(Duration.ofSeconds(this.statsIntervalInSeconds)) .map(n -> new DeviceRequest( deviceMac, System.currentTimeMillis(), ReportType.stats, this.urlBuilder.url(ReportType.stats, deviceMac))); }
  • 67. The problem with Flux.ļ¬‚atMapĀ  ļ¬‚atMap eagerly subscribes to inners with conļ¬gurable value with the default as 256 public static final int SMALL_BUFFER_SIZE = Math.max(16, Integer.parseInt(System.getProperty("reactor.bufferSize.small", "256"))); We observed that only 256 devices reported heartbeat/stats even we speciļ¬ed more than 256 devices. Why? Each device request stream is an inļ¬nite stream, and ļ¬‚atMap eagerly subscribes ļ¬rst 256 inner streams. Ā  Each device request steam is an inļ¬nite stream (Flux.interval()). That means only ļ¬rst 256 device streams get to be created and emit data.
  • 68. More on ļ¬‚atMap ļ¬‚atMap exposes 2 conļ¬gurable parameters for eager inner stream subscription maxConcurrency prefetch ļ¬‚atMap eagerly subscribes "maxConcurrentcy" inner streams when subscribed (onSubscribe) Ā  When subscribing inner stream, it will also pre-fetch "prefetch" elements by invoking Subscription.request(prefetch) of inner stream. To ļ¬x this problem, we explicitly conļ¬gure "maxConcurrency" when ļ¬‚atMapping Flux.fromIterable(deviceMacs).flatMap(mac -> createDeviceRequestStream(mac), deviceMacs.size())
  • 70. Q & AQ & A