A social game, by it’s nature can spread very quickly to a large user audience. Since a game is typically interactive, the speed of retrieving information needed for the user’s interactions with the system is critical. Applications which exclusively rely on synchronous data access, very often hit a scalability wall, when things get slow and their thread pools are exhausted. New paradigms like reactive programming alleviate this and provide extensive tool sets to deal with the ever growing demands of web applications.
This talk:
– Describes why Couchbase is the most appropriate solution for many video game and gaming use cases.
– Shows how to build scalable and reactive applications by making use of the Couchbase Java SDK 2.x, RxJava library and Spring Framework 5.
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Bulding a reactive game engine with Spring 5 & Couchbase
1. Building reac-ve game engine
with Spring 5 & Couchbase
Alex Derkach
Senior So0ware Engineer @ Play7ka
2. @alexsderkach
‣ Java addict since ’12
‣ SSE @ Slotomania Feature Team
‣ Ac7ve Couchbase & Spring user
‣ All things reac7ve & distributed
2h>p://alexsderkach.io
slides
3. Plan for today
3
‣ Why Couchbase + Quick Overview
‣ Reac7ve founda7on behind Spring 5
‣ Demonstra7on & Case Study
‣ Summary
9. What is Couchbase?
9
‣ Key-Document storage
‣ Consistent high performance
‣ Scale with single “push” buTon
‣ Zero-down7me opera7ons
10. Server
10
Couchbase Architecture
Cluster
Manager
Data Service
Index Service
Query Service
Cache
Storage
Server
Cluster
Manager
Data Service
Index Service
Query Service
Cache
Storage
Server
Cluster
Manager
Data Service
Index Service
Query Service
Cache
Storage
Server
Cluster
Manager
Data Service
Index Service
Query Service
Cache
Storage
Server
Cluster
Manager
Data Service
Index Service
Query Service
Cache
Storage
Couchbase Cluster
28. Spring 5
28
‣ Based on Reactor Core 3
‣ Reac7ve HTTP Server Abstrac7on
‣ Compa7ble with RxJava 1 & 2
‣ Compa7ble with Java 9
‣ Many more!
29. 29
‣ Func7onal, declara7ve programming model
‣ Combine, transform, reduce sequences (500+ methods)
‣ Reac7ve Streams ready
‣ Focus on what, not how
Reactor 3 Crash Course
Flux / Mono Subscriber
0..N Data
+ 0..1 (Error, Completed)
Backpressure
regulation
30. 30
Learning to Flux
OR Flux.create(sink -> *blocking call*);Mono.create(sink -> *blocking call*);
31. 31
Learning to Flux
Mono.create(sink -> {
val result = repository.findOne(1L);
if (result.isPresent()) {
sink.success(result.get());
} else {
sink.error(new UserNotFoundException(id));
}
})
.subscribe(System.out::println);
Flux.create(sink -> *blocking call*);Mono.create(sink -> *blocking call*); OR
32. 32
Learning to Flux
Mono.create(sink -> {
val result = repository.findOne(1L);
if (result.isPresent()) {
sink.success(result.get());
} else {
sink.error(new UserNotFoundException(id));
}
})
.subscribe(System.out::println);
Flux.create(sink -> *blocking call*);Mono.create(sink -> *blocking call*);
Flux.create(sink -> {
val ids = asList(1L, 2L, 3L);
val result = userRepository.findAll(ids);
for (User user : result) {
sink.next(user);
}
sink.complete();
})
.subscribe(System.out::println);
OR
33. 33
Flux.create(..., );
enum OverflowStrategy {
// Completely ignore downstream backpressure requests.
IGNORE,
// Signal an IllegalStateException when the downstream
can't keep up
ERROR,
// Drop the incoming signal if the downstream is not
ready to receive it.
DROP,
// Downstream will get only the latest signals from
upstream.
LATEST,
// Buffer all signals if the downstream can't keep up.
BUFFER
}
Backpressure
34. 34
Flux.<User>create(sink -> {
userRepository.findAll(ids).forEach(sink::next);
sink.complete();
})
.filter(user -> user.getGender() == MALE)
.map(user -> new User(user.getId(), user.getName().toUpperCase()))
.buffer(20)
.flatMap(users ->
Flux.create(sink -> {
userRepository.save(users).forEach(sink::next);
sink.complete();
})
.subscribeOn(Schedulers.elastic())
)
.subscribeOn(Schedulers.elastic())
.subscribe(System.out::println, System.err::println);
I have a Flux. What’s next?
35. 35
Flux.<User>create(sink -> {
userRepository.findAll(ids).forEach(sink::next);
sink.complete();
})
.filter(user -> user.getGender() == MALE)
.map(user -> new User(user.getId(), user.getName().toUpperCase()))
.buffer(20)
.flatMap(users ->
Flux.create(sink -> {
userRepository.save(users).forEach(sink::next);
sink.complete();
})
.subscribeOn(Schedulers.elastic())
)
.subscribeOn(Schedulers.elastic())
.subscribe(System.out::println, System.err::println);
I have a Flux. What’s next?
42. 42
spring-web spring-webflux
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User get(long id) { }
@PostMapping("/users/")
public User create(User user) { }
@PutMapping("/users/{id}")
public User update(long id, User user) { }
@DeleteMapping("/users/{id}")
public User delete(long id) { }
}
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User get(long id) { }
@PostMapping("/users/")
public User create(User user) { }
@PutMapping("/users/{id}")
public User update(long id, User user) { }
@DeleteMapping("/users/{id}")
public User delete(long id) { }
}
43. 43
spring-web spring-webflux
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User get(long id) { }
@PostMapping("/users/")
public User create(User user) { }
@PutMapping("/users/{id}")
public User update(long id, User user) { }
@DeleteMapping("/users/{id}")
public User delete(long id) { }
}
@RestController
public class UserController {
@GetMapping("/users/{id}")
public Mono<User> get(long id) { }
@PostMapping("/users/")
public Mono<User> create(Mono<User> user) { }
@PutMapping("/users/{id}")
public Mono<User> update(long id,
Mono<User> user) { }
@DeleteMapping("/users/{id}")
public Mono<User> delete(long id) { }
}
55. 55
Automa-c Op-miza-ons
‣ Macro-fusion - replacing 2+ subsequent operators with a single operator:
1. map(A) = B
2. map(B) = C ƒ(A) = D
3. filter(C) = D
56. 56
‣ Macro-fusion - replacing 2+ subsequent operators with a single operator:
1. map(A) = B
2. map(B) = C ƒ(A) = D
3. filter(C) = D
‣ Micro-fusion - operators that end in an output queue and operators star7ng with
a front-queue could share the same Queue instance
1. 0..100 = B
2. map(B) = C [0..100] <-> map <-> groupBy <-> 🙄
3. groupBy(C) = D
Automa-c Op-miza-ons
push
queuequeue request(1) request(1)
push
request(1)
push
57. ‣ Macro-fusion - replacing 2+ subsequent operators with a single operator:
1. map(A) = B
2. map(B) = C ƒ(A) = D
3. filter(C) = D
‣ Micro-fusion - operators that end in an output queue and operators star7ng with
a front-queue could share the same Queue instance
1. 0..100 = B
2. map(B) = C [0..100] <- map <-> groupBy <- 🙄
3. groupBy(C) = D
57
Automa-c Op-miza-ons
poll
queuequeue
poll
request(1)
push
58. 58
Flux against the world
‣ Stream, Op-onal, CompletableFuture - solve specific tasks
‣ Reac-ve Libraries are universal
61. 61
‣ Pipeline = steps of transforma-ons
How do I test? #unit
62. 62
How do I test? #unit
🛴 🏍 🚐
transforma-on #1 transforma-on #2
‣ Pipeline = steps of transforma-ons
63. 63
How do I test? #unit
Input ExpectedTransformer
🛴 🏍 🚐
transforma-on #1 transforma-on #2
‣ Pipeline = steps of transforma-ons
64. 64
How do I test? #unit
🛴 🏍 🚐
transforma-on #1 transforma-on #2
‣ Pipeline = steps of transforma-ons
Input ExpectedTransformer
65. @Test
public void testEmojiSource() {
Flux<String> source = emojiSource();
Iterable<String> values = source.toIterable();
assertEquals(asList("🙈", "🙉", "🙊"), values);
}
@Test
public void testWelcomeSource() {
Mono<String> source = welcomeSource();
String value = source.block();
assertEquals("Hello", value);
}
65
How to verify expecta-ons?
h>ps://projectreactor.io/docs/core/release/reference/docs/index.html#tes-ng
1
@Test
public void testEmojiSource() {
Flux<String> source = emojiSource();
StepVerifier.create(source)
.expectNext("🙈", "🙉", "🙊")
.as("expect 3 more")
.expectNextCount(3)
.thenAwait(Duration.ofSeconds(3))
.expectNextMatches(v -> v.startsWith("O_O"))
.as("expect non empty batch")
.thenConsumeWhile(v -> !v.isEmpty())
.expectNextCount(1)
.verifyComplete();
}
2
3
1
2
3
convert to synchronous use StepVerifier
1
3
2
🏍
66. 66
How to generate input?🛴
@Test
public void testEmojiSource() {
Flux<String> source = Flux.just("🙈", "🙉", "🙊");
Iterable<String> values = source.toIterable();
assertEquals(asList("🙈", "🙉", "🙊"), values);
}
@Test
public void testWelcomeSource() {
Mono<String> source = Mono.just("Hello");
String value = source.block();
assertEquals("Hello", value);
}
67. 67
How to generate input?
Emit specific signals with TestPublisher<T>
‣ next(T…) will trigger 1..N onNext signals
‣ emit(T…) will do the same & complete
‣ complete() will terminate with onComplete signal
‣ error(Throwable) will terminate with an onError signal
🛴
68. 68
How to get pre>y stack-trace?
Ac-vate debug mode
Hooks.onOperator(Hooks.OperatorHook::operatorStacktrace);
Error has been observed by the following operator(s):
|_ Flux.map(FakeRepository.java:27)
|_ Flux.map(FakeRepository.java:28)
|_ Flux.filter(FakeUtils1.java:29)
|_ Flux.transform(GuideDebuggingExtraTests.java:41)
|_ Flux.elapsed(FakeUtils2.java:30)
|_ Flux.transform(GuideDebuggingExtraTests.java:42)
69. 69
How do I test Couchbase? #integra-on
without N1QL with N1QL
OR
70. 70
Future
‣ Flow API - Java 9
‣ Non-blocking JDBC spec (Java 10?)
‣ More non-blocking Spring modules
‣ …
71. Summary
71
‣ K-V Storage - perfect for video games
‣ Design Non-Blocking by Default
‣ RxJava and Reactor are friends
‣ Spring unites everyone
72. References
72
‣ Why Couchbase chose RxJava
hTps://goo.gl/dWeS35
‣ Reac7ve Spring by Josh Long
hTps://goo.gl/EXtJrB
‣ Developing Reac7ve applica7ons with Reac7ve Streams
hTps://goo.gl/eeNaAh
‣ Spring Boot 2.0 change-log
hTps://goo.gl/j8HFuY