Skip to content

Commit 71456c5

Browse files
committed
Extended examples for Spring WebFlux.
Added an example for plain Spring WebFlux in combination with Spring Data MongoDB's tailable cursor to stream data to the client as it arrives in the database. Added Nyancat banner \ö/.
1 parent a880c3b commit 71456c5

File tree

10 files changed

+219
-14
lines changed

10 files changed

+219
-14
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
<url>https://repo.spring.io/libs-snapshot</url>
7070
</repository>
7171
</repositories>
72+
7273
<pluginRepositories>
7374
<pluginRepository>
7475
<id>spring-libs-snapshot</id>

readme.adoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
This repository contains the sample code for Spring WebFlux's functional web API and consists of the following building blocks:
44

5+
== A sample for Spring WebFlux
6+
7+
1. An `Event` domain type and a corresponding repository exposing a tailable query method.
8+
2. An `ApplicationRunner` that creates a capped collection for events and inserts a new one every two seconds.
9+
3. A Spring WebFlux controller to produce both a Server-Sent Events and JSON stream.
10+
11+
== A sample for Spring WebFlux.fn
12+
513
1. A `User` domain type and a reactive repository mapped using Spring Data MongoDB.
614
2. A `FunctionalWebController` to contain handler functions.
715
3. An `@Bean`-Definition for a `RouterFunction` that uses the functional API to configure the mappings of requests to the `FunctionalWebController`.
@@ -12,4 +20,10 @@ This repository contains the sample code for Spring WebFlux's functional web API
1220
$ git clone https://github.com/olivergierke/spring-five-functional-reactive
1321
$ cd spring-five-functional-reactive
1422
$ ./mvnw spring-boot:run
23+
24+
# To see the events inserted on the server streaming to the client
25+
$ curl -H "Accept: text/event-stream" http://localhost:8080/events
26+
27+
# To trigger the functional controller
1528
$ curl http://localhost:8080/users
29+
```
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package de.olivergierke.examples.spring5.webflux;
17+
18+
import lombok.Data;
19+
import lombok.RequiredArgsConstructor;
20+
import reactor.core.publisher.Flux;
21+
22+
import java.time.LocalDateTime;
23+
24+
import org.springframework.data.mongodb.core.mapping.Document;
25+
import org.springframework.data.mongodb.repository.Tailable;
26+
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
27+
import org.springframework.http.MediaType;
28+
import org.springframework.web.bind.annotation.GetMapping;
29+
import org.springframework.web.bind.annotation.RestController;
30+
31+
/**
32+
* @author Mark Paluch
33+
* @author Oliver Gierke
34+
*/
35+
@RestController
36+
@RequiredArgsConstructor
37+
class EventController {
38+
39+
private final EventRepository eventRepository;
40+
41+
@GetMapping(path = "/events", produces = { //
42+
MediaType.APPLICATION_STREAM_JSON_VALUE, //
43+
MediaType.TEXT_EVENT_STREAM_VALUE //
44+
})
45+
Flux<Event> streamEvents() {
46+
return eventRepository.findPeopleBy();
47+
}
48+
}
49+
50+
@Data
51+
@Document
52+
class Event {
53+
54+
String id;
55+
final LocalDateTime eventDate;
56+
}
57+
58+
interface EventRepository extends ReactiveCrudRepository<Event, String> {
59+
60+
@Tailable
61+
Flux<Event> findPeopleBy();
62+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package de.olivergierke.examples.spring5.webflux;
17+
18+
import reactor.core.publisher.Flux;
19+
20+
import java.io.IOException;
21+
import java.time.Duration;
22+
import java.time.LocalDateTime;
23+
24+
import org.springframework.boot.ApplicationRunner;
25+
import org.springframework.boot.SpringApplication;
26+
import org.springframework.boot.autoconfigure.SpringBootApplication;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.data.mongodb.core.CollectionOptions;
29+
import org.springframework.data.mongodb.core.MongoOperations;
30+
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
31+
32+
/**
33+
* @author Mark Paluch
34+
* @author Oliver Gierke
35+
*/
36+
@SpringBootApplication
37+
public class WebfluxDemo {
38+
39+
public static void main(String[] args) throws IOException {
40+
SpringApplication.run(WebfluxDemo.class, args);
41+
}
42+
43+
/**
44+
* Application runner to initialize a capped collection for {@link Event}s and insert a new {@link Event} every two
45+
* seconds.
46+
*
47+
* @param operations
48+
* @param reactiveOperations
49+
* @return
50+
*/
51+
@Bean
52+
ApplicationRunner onStart(MongoOperations operations, ReactiveMongoOperations reactiveOperations) {
53+
54+
return args -> {
55+
56+
CollectionOptions options = CollectionOptions.empty() //
57+
.capped() //
58+
.size(2048) //
59+
.maxDocuments(1000);
60+
61+
operations.dropCollection(Event.class);
62+
operations.createCollection(Event.class, options);
63+
64+
Flux.interval(Duration.ofSeconds(2)) //
65+
.map(counter -> new Event(LocalDateTime.now())) //
66+
.flatMap(reactiveOperations::save) //
67+
.log() //
68+
.subscribe();
69+
};
70+
}
71+
}

src/main/java/de/olivergierke/examples/spring5/User.java renamed to src/main/java/de/olivergierke/examples/spring5/webflux/fn/User.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package de.olivergierke.examples.spring5;
16+
package de.olivergierke.examples.spring5.webflux.fn;
1717

1818
import lombok.Data;
1919

src/main/java/de/olivergierke/examples/spring5/UserRepository.java renamed to src/main/java/de/olivergierke/examples/spring5/webflux/fn/UserRepository.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package de.olivergierke.examples.spring5;
16+
package de.olivergierke.examples.spring5.webflux.fn;
1717

1818
import reactor.core.publisher.Flux;
1919
import reactor.core.publisher.Mono;
@@ -48,5 +48,5 @@ interface UserRepository extends Repository<User, String> {
4848
* @param user
4949
* @return
5050
*/
51-
Mono<User> save(Mono<User> user);
51+
Mono<User> save(User user);
5252
}

src/main/java/de/olivergierke/examples/spring5/DemoApplication.java renamed to src/main/java/de/olivergierke/examples/spring5/webflux/fn/WebfluxFnConfiguration.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package de.olivergierke.examples.spring5;
16+
package de.olivergierke.examples.spring5.webflux.fn;
1717

1818
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
1919

@@ -22,21 +22,16 @@
2222
import reactor.core.publisher.Mono;
2323

2424
import org.springframework.boot.ApplicationRunner;
25-
import org.springframework.boot.SpringApplication;
26-
import org.springframework.boot.autoconfigure.SpringBootApplication;
2725
import org.springframework.context.annotation.Bean;
26+
import org.springframework.context.annotation.Configuration;
2827
import org.springframework.stereotype.Component;
2928
import org.springframework.web.reactive.function.server.RouterFunction;
3029
import org.springframework.web.reactive.function.server.RouterFunctions;
3130
import org.springframework.web.reactive.function.server.ServerRequest;
3231
import org.springframework.web.reactive.function.server.ServerResponse;
3332

34-
@SpringBootApplication
35-
class DemoApplication {
36-
37-
public static void main(String[] args) {
38-
SpringApplication.run(DemoApplication.class, args);
39-
}
33+
@Configuration
34+
class WebfluxFnConfiguration {
4035

4136
/**
4237
* Defines the routes mapped to {@link FunctionalUserController}.
@@ -62,7 +57,7 @@ RouterFunction<?> routes(FunctionalUserController controller) {
6257
ApplicationRunner onStartup(UserRepository repository) {
6358

6459
// We need to call ….block() here to actually execute the call.
65-
return (args) -> repository.save(Mono.just(new User("Dave Matthews"))).block();
60+
return (args) -> repository.save(new User("Dave Matthews")).block();
6661
}
6762

6863
/**
@@ -85,7 +80,7 @@ static class FunctionalUserController {
8580
Mono<ServerResponse> getUser(ServerRequest request) {
8681

8782
Mono<User> user = Mono.just(request.pathVariable("id")) //
88-
.then(repository::findById);
83+
.flatMap(repository::findById);
8984

9085
return ServerResponse.ok().body(user, User.class);
9186
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
spring.jackson.serialization.write-dates-as-timestamps=false

src/main/resources/banner.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
${AnsiColor.BRIGHT_BLUE}████████████████████████████████████████████████████████████████████████████████
2+
${AnsiColor.BRIGHT_BLUE}████████████████████████████████████████████████████████████████████████████████
3+
${AnsiColor.RED}██████████████████${AnsiColor.BRIGHT_BLUE}████████████████${AnsiColor.BLACK}██████████████████████████████${AnsiColor.BRIGHT_BLUE}████████████████
4+
${AnsiColor.RED}████████████████████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██████████████████████████████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}██████████████
5+
${AnsiColor.BRIGHT_RED}████${AnsiColor.RED}██████████████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██████${AnsiColor.MAGENTA}██████████████████████${AnsiColor.WHITE}██████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}████████████
6+
${AnsiColor.BRIGHT_RED}██████████████████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}████${AnsiColor.MAGENTA}████████████████${AnsiColor.BLACK}████${AnsiColor.MAGENTA}██████${AnsiColor.WHITE}████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}██${AnsiColor.BLACK}████${AnsiColor.BRIGHT_BLUE}██████
7+
${AnsiColor.BRIGHT_RED}██████████████████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██${AnsiColor.MAGENTA}████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}████${AnsiColor.BLACK}██${AnsiColor.MAGENTA}██████${AnsiColor.WHITE}██${AnsiColor.BLACK}████${AnsiColor.WHITE}████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}████
8+
${AnsiColor.BRIGHT_YELLOW}██████████████████${AnsiColor.BRIGHT_RED}████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██${AnsiColor.MAGENTA}████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██████${AnsiColor.MAGENTA}██████${AnsiColor.WHITE}██${AnsiColor.BLACK}██${AnsiColor.WHITE}██████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}████
9+
${AnsiColor.BRIGHT_YELLOW}██████████████████████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_YELLOW}██████${AnsiColor.BLACK}██${AnsiColor.WHITE}██${AnsiColor.MAGENTA}████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██████${AnsiColor.BLACK}████████${AnsiColor.WHITE}████████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}████
10+
${AnsiColor.BRIGHT_YELLOW}████████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██${AnsiColor.BLACK}██${AnsiColor.BRIGHT_YELLOW}████${AnsiColor.BLACK}██${AnsiColor.WHITE}██${AnsiColor.MAGENTA}████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██████████████████████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}████
11+
${AnsiColor.BRIGHT_GREEN}██████████████████${AnsiColor.BRIGHT_YELLOW}██${AnsiColor.BLACK}██${AnsiColor.WHITE}██${AnsiColor.BLACK}████████${AnsiColor.WHITE}██${AnsiColor.MAGENTA}██████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██████████████████████████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}██
12+
${AnsiColor.BRIGHT_GREEN}██████████████████████${AnsiColor.WHITE}████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██${AnsiColor.MAGENTA}██████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██████${AnsiColor.BRIGHT_YELLOW}██${AnsiColor.WHITE}██████████${AnsiColor.BRIGHT_YELLOW}██${AnsiColor.BLACK}██${AnsiColor.WHITE}████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}██
13+
${AnsiColor.BRIGHT_GREEN}██████████████████████${AnsiColor.BLACK}████${AnsiColor.WHITE}████${AnsiColor.BLACK}██${AnsiColor.WHITE}██${AnsiColor.MAGENTA}██████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██████${AnsiColor.BLACK}██${AnsiColor.WHITE}██████${AnsiColor.BLACK}██${AnsiColor.WHITE}██${AnsiColor.BLACK}████${AnsiColor.WHITE}████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}██
14+
${AnsiColor.BLUE}██████████████████${AnsiColor.BRIGHT_GREEN}████████${AnsiColor.BLACK}██████${AnsiColor.WHITE}██${AnsiColor.MAGENTA}██████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██${AnsiColor.MAGENTA}████${AnsiColor.WHITE}████████████████${AnsiColor.MAGENTA}████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}██
15+
${AnsiColor.BLUE}██████████████████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}████${AnsiColor.MAGENTA}██████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██████${AnsiColor.BLACK}████████████${AnsiColor.WHITE}████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}████
16+
${AnsiColor.BRIGHT_BLUE}██████████████████${AnsiColor.BLUE}████${AnsiColor.BLUE}██████${AnsiColor.BLACK}████${AnsiColor.WHITE}██████${AnsiColor.MAGENTA}██████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██████████████████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}██████
17+
${AnsiColor.BRIGHT_BLUE}██████████████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██${AnsiColor.BLACK}████${AnsiColor.WHITE}████████████████████${AnsiColor.BLACK}██████████████████${AnsiColor.BRIGHT_BLUE}████████
18+
${AnsiColor.BRIGHT_BLUE}████████████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}██████${AnsiColor.BLACK}████████████████████████████████${AnsiColor.WHITE}██${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}████████████
19+
${AnsiColor.BRIGHT_BLUE}████████████████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}██${AnsiColor.BLACK}██${AnsiColor.WHITE}████${AnsiColor.BRIGHT_BLUE}████████████${AnsiColor.BLACK}██${AnsiColor.WHITE}████${AnsiColor.BLACK}████${AnsiColor.WHITE}████${AnsiColor.BLACK}██${AnsiColor.BRIGHT_BLUE}████████████
20+
${AnsiColor.BRIGHT_BLUE}████████████████████████${AnsiColor.BLACK}██████${AnsiColor.BRIGHT_BLUE}████${AnsiColor.BLACK}██████${AnsiColor.BRIGHT_BLUE}████████████${AnsiColor.BLACK}██████${AnsiColor.BRIGHT_BLUE}████${AnsiColor.BLACK}██████${AnsiColor.BRIGHT_BLUE}████████████
21+
████████████████████████████████████████████████████████████████████████████████
22+
${AnsiColor.BRIGHT_BLUE}:: Meow :: Running Spring Boot ${spring-boot.version} :: \ö/${AnsiColor.BLACK}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package de.olivergierke.examples.spring5.reactor;
17+
18+
import reactor.core.publisher.Flux;
19+
import reactor.core.publisher.Mono;
20+
21+
import org.junit.Test;
22+
23+
/**
24+
* @author Oliver Gierke
25+
*/
26+
public class ReactorSample {
27+
28+
@Test
29+
public void reactorFundamentals() {
30+
31+
Mono.just("Hello") //
32+
.map(word -> word.concat(" World!")) //
33+
.subscribe(System.out::println);
34+
35+
Flux.just("Hello", "World") //
36+
.flatMap(word -> Flux.fromArray(word.split(""))) //
37+
.subscribe(System.out::println);
38+
}
39+
}

0 commit comments

Comments
 (0)