Refactor To Reactive
by Oleh Dokuka
1
2
https://goo.gl/yzG2HC
Say Hello Here
3
• JSE at Levi9
• JEEConf/JavaDay/JUG Speaker
• Reactor 3 committer
• Reactivity preacher
/oleh.dokuka /OlegDokuka/OlehDokuka
3
About me
4
Agenda
• App Overview
• Refactoring
5
Primary Goal
• Expand Reactive Approaches with Spring 5
• Demonstrate Refactoring Steps by Examples
6
What is Reactivity?
7
8
9
Chat Application
10
Functionality
• Gitter Chat Communication
• Messaging Statistics
11
https://goo.gl/yzG2HC
12
Gitter Room:
Reactive EatDog
Demo
13
Toolkit
• Spring Framework 4.x
• Spring MVC
• Spring Data Mongo
• Spring Boot 1.5.x
14
Architecture
15
User Client
16
Gitter Service
17
Whole ApplicationControllers LayerServices LayerPersistence Layer
18
Demo
19
Existing Flow
20
21
22
23
24
Problems
25
Problems
• Pulling model
26
27
Problems
• Pulling model
• Blocking I/O
28
29
Step 0.
Or how to motivate the business
30
31
t2.medium
1-CPU 2g-RAM
32
java -Xmx2g
-Xms1g
-Dserver.tomcat.accept-count=20000
-jar blocking.jar
Blocking Tomcat
33
Throughput Average Latency (millisec.)
100 1271
1000 4600
10000 44565
Blocking Tomcat
34
java -Xmx2g
-Xms1g
-Dserver.tomcat.max-threads=10000
-Dserver.tomcat.max-connections=10000
-Dserver.tomcat.accept-count=20000
-jar blocking.jar
Blocking Tomcat
35
Throughput Average Latency (millisec.)
100 1211
1000 1429
10000 OutOfMemoryError
Blocking Tomcat
36
java -Xmx2g
-Xms1g
-Dserver.tomcat.accept-count=20000
-jar non-blocking.jar
Non-blocking Tomcat
37
Throughput Average Latency (millisec.)
100 1203
1000 1407
10000 9661
Non-blocking Tomcat
38
Throughput Average Latency (millisec.)
1000 1370
10000 2699
20000 6310
Non-blocking Netty
39
40
Reactive is Solution!
41
42
Step 1.
Refactor Design
43
Push Model
44
Gitter
Streaming API
45
46
47
Non-blocking
48
Reactive Mongo Driver
49
50
51
Push Model
52
WebSocket
53
54
New
Reactive Architecture
55
Non-Blocking Connection Pipe
Gitter Connection Pipe
56
Client Connection Pipe
57
Step 2.
Choose Reactive Toolset
58
Project Reactor
Reactive Types
•Mono<T>
59
Reactive Types
•Mono<T>
•Flux<T>
60
Reactive Types
•Mono<T>
•Flux<T>
61
Spring 5 :)
62
Painless Replacement
63
Reactive Stack (WebFlux)
@Controller, @RequestMapping,…
Spring MVC
Servlet API
Servlet Container
64
Spring WebFlux
HTTP/Reactive-Streams
Servlet 3.1, Netty, Undertow
Default Stack (MVC)
@RestController
@RequestMapping("/api/v1/statistics")
public class StatisticResource {
private final StatisticService statisticService;
@Autowired
public StatisticResource(
StatisticService statisticService) {
this.statisticService = statisticService;
}
@GetMapping("/users")
public List<UsersStatisticVM> getUsersStatistic() {
return statisticService.getUsersStatistic();
}
} 65
@RestController
@RequestMapping("/api/v1/statistics")
public class StatisticResource {
private final StatisticService statisticService;
@Autowired
public StatisticResource(
StatisticService statisticService) {
this.statisticService = statisticService;
}
@GetMapping("/users")
public Flux<UsersStatisticVM> getUsersStatistic() {
return statisticService.getUsersStatistic();
}
} 66
@RestController
@RequestMapping("/api/v1/statistics")
public class StatisticResource {
private final StatisticService statisticService;
@Autowired
public StatisticResource(
StatisticService statisticService) {
this.statisticService = statisticService;
}
@GetMapping("/users")
public List<UsersStatisticVM> getUsersStatistic() {
return statisticService.getUsersStatistic();
}
} 67
@RestController
@RequestMapping("/api/v1/statistics")
public class StatisticResource {
private final StatisticService statisticService;
@Autowired
public StatisticResource(
StatisticService statisticService) {
this.statisticService = statisticService;
}
@GetMapping("/users")
public Flux<UsersStatisticVM> getUsersStatistic() {
return statisticService.getUsersStatistic();
}
} 68
69
70
71
Asynchronous
event-driven framework
72
73
Step 3.
Refactor Code
https://goo.gl/yxAJGI
74
Join GitHub
75
Step 3.1.
Update Dependencies
76
Tip
77
• Use start.spring.io to generate build
file
SpringBoot 2.X
78
Toolset Tips
SpringBoot 1.X
Code Session
79
80
Step 3.2.
Refactor Contracts (interfaces)
Tips
81
Object Mono<Object>
Iterable<T> Flux<T>
However
82
Object Flux<Object>
ReactiveCrudRepository<T, ID>
83
Toolset Tips
CrudRepository<T, ID>
interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> Mono<S> save(S entity);
<S extends T> Flux<S> saveAll(Iterable<S> entities);
<S extends T> Flux<S> saveAll(Publisher<S> entityStream);
Mono<T> findById(ID id);
Mono<T> findById(Mono<ID> id);
Mono<Boolean> existsById(ID id);
Mono<Boolean> existsById(Mono<ID> id);
Flux<T> findAll();
Flux<T> findAllById(Iterable<ID> ids);
Flux<T> findAllById(Publisher<ID> idStream);
Mono<Long> count();
Mono<Void> deleteById(ID id);
Mono<Void> delete(T entity);
Mono<Void> deleteAll(Iterable<? extends T> entities);
Mono<Void> deleteAll(Publisher<? extends T> entityStream);
Mono<Void> deleteAll();
}
84
interface ReactiveCrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> Mono<S> save(S entity);
<S extends T> Flux<S> saveAll(Iterable<S> entities);
<S extends T> Flux<S> saveAll(Publisher<S> entityStream);
Mono<T> findById(ID id);
Mono<T> findById(Mono<ID> id);
Mono<Boolean> existsById(ID id);
Mono<Boolean> existsById(Mono<ID> id);
Flux<T> findAll();
Flux<T> findAllById(Iterable<ID> ids);
Flux<T> findAllById(Publisher<ID> idStream);
Mono<Long> count();
Mono<Void> deleteById(ID id);
Mono<Void> delete(T entity);
Mono<Void> deleteAll(Iterable<? extends T> entities);
Mono<Void> deleteAll(Publisher<? extends T> entityStream);
Mono<Void> deleteAll();
}
85
Code Session
86
87
Step 3.3.
Refactor Implementation (services)
Notice
88
• Everything is a Stream
• Only Non-blocking ops.
• Less side effects
• No custom operators
Gitter Service
89
Toolset Tips
90
RestTemplate WebClient
public interface WebClient {
UriSpec<RequestHeadersSpec<?>> get();
//omitted...
static WebClient create() {
return new DefaultWebClientBuilder().build();
}
interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> {
//omitted...
Mono<ClientResponse> exchange();
//omitted...
}
interface ResponseSpec {
<T> Mono<T> bodyToMono(Class<T> bodyType);
<T> Flux<T> bodyToFlux(Class<T> elementType);
<T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType);
<T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> elementType);
}
}
91
public interface WebClient {
UriSpec<RequestHeadersSpec<?>> get();
//omitted...
static WebClient create() {
return new DefaultWebClientBuilder().build();
}
interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> {
//omitted...
Mono<ClientResponse> exchange();
//omitted...
}
interface ResponseSpec {
<T> Mono<T> bodyToMono(Class<T> bodyType);
<T> Flux<T> bodyToFlux(Class<T> elementType);
<T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType);
<T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> elementType);
}
}
92
Code Session
93
94
WebClient
.create()
.get()
.uri(...)
.header(...)
.exchange()
.retryWhen(...)
.flatMapMany(...);
WebClient
.create()
.get()
.uri(...)
.header(...)
.exchange()
.retryWhen(...)
.flatMapMany(...);
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
95
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
96
.getMS().getLM()
.retry()
cache
97
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
.getMS().getLM()
.retry()
cache
.getMS().getLM()
.retry()
cache
98
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
.getLM() .getMS()
.retry()
cache
99
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
.getMS().getLM()
.retry()
cache
100
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
.getLM()
.getMS().getLM()
.retry()
cache
101
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
.getMS().getLM()
.retry()
cache
102
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
.getMS().getLM()
.retry()
cache
103
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
.getMS().getLM()
.retry()
cache
104
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
…subscribe();
105
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
.getMS().getLM()
.retry()
cache
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
…subscribe();
106
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
.getMS().getLM()
.retry()
cache
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
…subscribe();
107
.getMS().getLM()
.retry()
cache .getMS()
.retry()
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
…subscribe();
108
.getMS().getLM()
.retry()
cache
…subscribe();
…subscribe();
109
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
.getMS().getLM()
.retry()
cache
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
…subscribe();
…subscribe();
110
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
.getMS().getLM()
.retry()
cache
@Autowired
public GitterService(
GitterClient gitterClient
) {
gas = Flux.merge(
getLatestMessages(),
getMessagesStream(null)
)
.replay(30)
.autoConnect(0);
}
…subscribe();
.getMS()
.retry()
Message Service
111
Code Session
112
@Autowired
public DefaultMessageService(
MessageRepository messageRepository,
ChatService<MessageResponse> chatClient,
ApplicationContext context
) {
this.chatClient = chatClient;
chatClient.stream()
.transform(MessageMapper::toDomainUnits)
.transform(messageRepository::saveAll)
.retryWhen(…)
.subscribe()
}
113
@Autowired
public DefaultMessageService(
MessageRepository messageRepository,
ChatService<MessageResponse> chatClient,
ApplicationContext context
) {
this.chatClient = chatClient;
chatClient.stream()
.transform(MessageMapper::toDomainUnits)
.transform(messageRepository::saveAll)
.retryWhen(…)
.subscribe()
}
@Autowired
public DefaultMessageService(
MessageRepository messageRepository,
ChatService<MessageResponse> chatClient,
ApplicationContext context
) {
this.chatClient = chatClient;
chatClient.stream()
.transform(MessageMapper::toDomainUnits)
.transform(messageRepository::saveAll)
.retryWhen(…)
.subscribe()
}
114
@Autowired
public DefaultMessageService(
MessageRepository messageRepository,
ChatService<MessageResponse> chatClient,
MessageBroker messageBroker
) {
this.chatClient = chatClient;
chatClient.stream()
.transform(MessageMapper::toDomainUnits)
.transform(messageRepository::saveAll)
.retryWhen(…)
.subscribe()
}
.transform()
115
@Autowired
public DefaultMessageService(
MessageRepository messageRepository,
ChatService<MessageResponse> chatClient,
MessageBroker messageBroker
) {
this.chatClient = chatClient;
chatClient.stream()
.transform(MessageMapper::toDomainUnits)
.transform(messageRepository::saveAll)
.retryWhen(…)
.subscribe()
}
.transform()
116
@Autowired
public DefaultMessageService(
MessageRepository messageRepository,
ChatService<MessageResponse> chatClient,
MessageBroker messageBroker
) {
this.chatClient = chatClient;
chatClient.stream()
.transform(MessageMapper::toDomainUnits)
.transform(messageRepository::saveAll)
.retryWhen(…)
.subscribe()
}
.transform()
117
@Autowired
public DefaultMessageService(
MessageRepository messageRepository,
ChatService<MessageResponse> chatClient,
MessageBroker messageBroker
) {
this.chatClient = chatClient;
chatClient.stream()
.transform(MessageMapper::toDomainUnits)
.transform(messageRepository::saveAll)
.retryWhen(…)
.subscribe()
}
.transform()
118
@Autowired
public DefaultMessageService(
MessageRepository messageRepository,
ChatService<MessageResponse> chatClient,
MessageBroker messageBroker
) {
this.chatClient = chatClient;
chatClient.stream()
.transform(MessageMapper::toDomainUnits)
.transform(messageRepository::saveAll)
.retryWhen(…)
.subscribe()
}
.transform()
119
@Autowired
public DefaultMessageService(
MessageRepository messageRepository,
ChatService<MessageResponse> chatClient,
MessageBroker messageBroker
) {
this.chatClient = chatClient;
chatClient.stream()
.transform(MessageMapper::toDomainUnits)
.transform(messageRepository::saveAll)
.retryWhen(…)
.subscribe()
}
.transform()
120
@Autowired
public DefaultMessageService(
MessageRepository messageRepository,
ChatService<MessageResponse> chatClient,
MessageBroker messageBroker
) {
this.chatClient = chatClient;
chatClient.stream()
.transform(MessageMapper::toDomainUnits)
.transform(messageRepository::saveAll)
.retryWhen(…)
.subscribe()
}
.transform()
121
.transform()
@Autowired
public DefaultMessageService(
MessageRepository messageRepository,
ChatService<MessageResponse> chatClient,
MessageBroker messageBroker
) {
this.chatClient = chatClient;
chatClient.stream()
.transform(MessageMapper::toDomainUnits)
.transform(messageRepository::saveAll)
.retryWhen(…)
.subscribe()
}
122
.transform()
@Autowired
public DefaultMessageService(
MessageRepository messageRepository,
ChatService<MessageResponse> chatClient,
MessageBroker messageBroker
) {
this.chatClient = chatClient;
chatClient.stream()
.transform(MessageMapper::toDomainUnits)
.transform(messageRepository::saveAll)
.retryWhen(…)
.subscribe()
}
123
@Override
public Flux<MessageVM> latest() {
return chatClient.stream()
.transform(MessageMapper
::toViewModelUnits
);
}
@Override
public Flux<MessageVM> latest() {
return chatClient.stream()
.transform(MessageMapper
::toViewModelUnits
);
}
@Override
public Flux<MessageVM> latest() {
return chatClient.stream()
.transform(MessageMapper
::toViewModelUnits
);
}
.transform()
124
Statistics Service
125
Code Session
126
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
127
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
128
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
129
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
130
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
131
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
.mergeWith(.defer(…))
.flatMap{ userStatistic()}
.userStatistic()
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
132
.mergeWith(.defer(…))
.flatMap{ userStatistic()}
.userStatistic()
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
133
.mergeWith(.defer(…))
.flatMap{ userStatistic()}
.userStatistic()
.cache( )
.userStatistic()
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
134
.mergeWith(.defer(…))
.flatMap{ userStatistic()}
.userStatistic()
.cache( )
.userStatistic()
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
135
.mergeWith(.defer(…))
.flatMap{ userStatistic()}
.cache( )
.userStatistic()
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
136
.mergeWith(.defer(…))
.flatMap{ userStatistic()}
.cache( )
public DefaultStatisticService(
ConfigurableApplicationContext context,
UserRepository userRepository
) {
this.userRepository = userRepository;
this.statisticPublisher =
Flux
.create(
s -> context.addApplicationListener(
(MessageSavedEvent e) ->
s.next(e.getSource())
),
FluxSink.OverflowStrategy.LATEST
)
.flatMap(s -> doGetUserStatistic())
.mergeWith(Mono.defer(this::doGetUserStatistic))
.cache(1);
}
137
.mergeWith(.defer(…))
.flatMap{ userStatistic()}
.cache( )
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
138
Mono<UserVM> addResilience(Mono<UserVM> input) {
return input
.timeout(Duration.ofSeconds(2))
.defaultIfEmpty(EMPTY_USER)
.onErrorReturn(EMPTY_USER);
}
Mono<UserVM> addResilience(Mono<UserVM> input) {
return input
.timeout(Duration.ofSeconds(2))
.defaultIfEmpty(EMPTY_USER)
.onErrorReturn(EMPTY_USER);
}
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…) 139
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…) 140
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…) 141
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…) 142
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…) 143
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…) 144
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.addResilience(…)
.zip(…)
.subscribe()
145
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.addResilience(…)
.zip(…)
.subscribe()
146
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…)
null
147
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…)
null
148
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…)
null
149
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…)
nullnull
150
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…)
null
151
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…)
null
152
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…)
null
153
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.map(…)
.addResilience(…)
.zip(…)
154
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.addResilience(…)
.zip(…)
.subscribe()
155
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.addResilience(…)
.zip(…)
.subscribe()
156
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.defaultIfEmpty(EMPTY_USER),
topMentionedUser.defaultIfEmpty(EMPTY_USER),
UsersStatisticVM::new
);
}
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.addResilience(…)
.zip(…)
.subscribe()
157
private Mono<US> doGetUserStatistic() {
Mono<UserVM> topActiveUser =
userRepository
.findMostActive()
.map(UserMapper::toViewModelUnits);
Mono<UserVM> topMentionedUser =
userRepository
.findMostPopular()
.map(UserMapper::toViewModelUnits);
return Mono.zip(
topActiveUser.transform(::addResilience),
topMentionedUser.transform(::addResilience),
UsersStatisticVM::new
);
}
.addResilience(…)
.zip(…)
.subscribe()
158
Reactive Endpoints
159
Toolset Tips
160
Spring MVC
Spring WebFlux
Reactive WebSocket
public interface WebSocketSession {
String getId();
HandshakeInfo getHandshakeInfo();
DataBufferFactory bufferFactory();
Flux<WebSocketMessage> receive();
Mono<Void> send(Publisher<WebSocketMessage> messages);
default Mono<Void> close() {
return close(CloseStatus.NORMAL);
}
Mono<Void> close(CloseStatus status);
WebSocketMessage textMessage(String payload);
WebSocketMessage binaryMessage(
Function<DataBufferFactory, DataBuffer> payloadFactory);
WebSocketMessage pingMessage(
Function<DataBufferFactory, DataBuffer> payloadFactory);
WebSocketMessage pongMessage(
Function<DataBufferFactory, DataBuffer> payloadFactory);
} 161
public interface WebSocketSession {
String getId();
HandshakeInfo getHandshakeInfo();
DataBufferFactory bufferFactory();
Flux<WebSocketMessage> receive();
Mono<Void> send(Publisher<WebSocketMessage> messages);
default Mono<Void> close() {
return close(CloseStatus.NORMAL);
}
Mono<Void> close(CloseStatus status);
WebSocketMessage textMessage(String payload);
WebSocketMessage binaryMessage(
Function<DataBufferFactory, DataBuffer> payloadFactory);
WebSocketMessage pingMessage(
Function<DataBufferFactory, DataBuffer> payloadFactory);
WebSocketMessage pongMessage(
Function<DataBufferFactory, DataBuffer> payloadFactory);
} 162
Code Session
163
@Override
public Publisher<?> handle()
{
return Flux.merge(
messageService.latest(),
statisticService
.usersStatisticStream()
);
}
network
.merge()
164
@Override
public Publisher<?> handle()
{
return Flux.merge(
messageService.latest(),
statisticService
.usersStatisticStream()
);
}
network
.merge()
165
@Override
public Publisher<?> handle()
{
return Flux.merge(
messageService.latest(),
statisticService
.usersStatisticStream()
);
}
network
.merge()
166
@Override
public Publisher<?> handle()
{
return Flux.merge(
messageService.latest(),
statisticService
.usersStatisticStream()
);
}
network
.merge()
167
.merge()
@Override
public Publisher<?> handle()
{
return Flux.merge(
messageService.latest(),
statisticService
.usersStatisticStream()
);
}
network
168
@Override
public Publisher<?> handle()
{
return Flux.merge(
messageService.latest(),
statisticService
.usersStatisticStream()
);
}
network
.merge()
169
@Override
public Publisher<?> handle()
{
return Flux.merge(
messageService.latest(),
statisticService
.usersStatisticStream()
);
}
network
.merge()
170
@Override
public Publisher<?> handle()
{
return Flux.merge(
messageService.latest(),
statisticService
.usersStatisticStream()
);
}
network
.merge()
171
New
Reactive Architecture
172
173
https://goo.gl/yzG2HC
174
Gitter Room:
Reactive EatDog
Demo
175
Checklist
• Message-Driven
• Resilient
• Responsive
• Elastic
176
Summary
177
Interfaces
178
TElement Mono<TElement>
Iterable<T> Flux<T>
ReactiveCrudRepository<T, ID>
179
CrudRepository<T, ID>
Database
Web Communication
180
RestTemplate WebClient
Web Endpoints
181
Spring MVC
Spring WebFlux
Web Server
182
Tomcat
Netty
Advantages
• Reactive System
• Higher throughput
• Spring Framework support
183
Disadvantages
• Understanding
• JDBC driver
• Debug
184
Q&A
185
1. Learn Project Reactor [http://projectreactor.io/learn]
2. Reactive WebFlux [https://docs.spring.io/spring-framework/
docs/5.0.0.RELEASE/spring-framework-reference/web-
reactive.html#spring-webflux]
3. File Reading in Reactor [https://simonbasle.github.io/2017/10/
file-reading-in-reactor/]
4. Zuul 2 - Netflix Tech Blog [https://medium.com/netflix-techblog/
zuul-2-the-netflix-journey-to-asynchronous-non-blocking-
systems-45947377fb5c]
5. Benchmark [https://github.com/CollaborationInEncapsulation/
blocking-vs-nonblocking-benchmark]
186
https://goo.gl/Rsp8ji
187
Presentation
188

Refactor to Reactive With Spring 5 and Project Reactor