# Spring Boot WebFlux

Get familiar with Spring Boot and WebFlux features. Time: 20 minutes.

## Code Snippet Manager App

We are going to make our Code Snippet Manager reactive. You will use the spring-boot-starter-webflux starter. Remember that Spring WebFlux provides two ways to create a reactive apps: Functional and Annotation-Based; in this lab will explore both.

1. Open a browser and hit the url: http://start.spring.io

2. Click the Switch to the full version link.

3. Fill out the Code Snippet Manager App Project metadata with (See Figure 1.0):

   Table 1. Code Snippet Manager App App - metadata
   Property | Value
   --- | ---
   Group: | io.pivotal.workshop
   Artifact: | code-snippet-manager-flux
   Name: | code-snippet-manager-flux
   Package Name: | io.pivotal.workshop.snippet
   Dependencies: | Reactive Web, DevTools, MongoDB, Actuator
   Spring Boot: | 2.0.0.M7

   Figure 1.0: Spring Initializr - http://start.spring.io

   Tip | You can choose either Maven or Gradle project types.

4. Type Reactive Web, DevTools, MongoDB, Reactive MongoDB and Actuator in the Dependencies field and press Enter.

5. Click the Generate Project button.

6. Unzip the file in any directory you want.

7. Import your project in any IDE you want.

8. Add the following domain classes: io.pivotal.workshop.snippet.domain.Language and io.pivotal.workshop.snippet.domain.Snippet, practically are the same as before, just a small modifications:

   src/main/java/io/pivotal/workshop/snippet/domain/Language.java

       package io.pivotal.workshop.snippet.domain;

       import org.bson.types.ObjectId;
       import org.springframework.data.annotation.Id;
       import org.springframework.data.mongodb.core.mapping.Document;

       @Document
       public class Language {

               @Id
               private String id;
               private String name;
               private String syntax = "text";

               public Language() {
               }

               public Language(String name) {
                       this();
                       this.name = name;
               }

               public Language(String name, String syntax) {
                       this(name);
                       this.syntax = syntax;
               }

               public String getId() {
                       return id;
               }

               public void setId(String id) {
                       this.id = id;
               }

               public String getName() {
                       return name;
               }

               public void setName(String name) {
                       this.name = name;
               }

               public String getSyntax() {
                       return syntax;
               }

               public void setSyntax(String syntax) {
                       this.syntax = syntax;
               }

           @Override
           public String toString() {
               return "Language{" +
                       "id='" + id + '\'' +
                       ", name='" + name + '\'' +
                       ", syntax='" + syntax + '\'' +
                       '}';
           }
       }

   src/main/java/io/pivotal/workshop/snippet/domain/Snippet.java

       package io.pivotal.workshop.snippet.domain;

       import org.bson.types.ObjectId;
       import org.springframework.data.annotation.Id;
       import org.springframework.data.mongodb.core.mapping.DBRef;
       import org.springframework.data.mongodb.core.mapping.Document;

       import java.util.Date;

       @Document
       public class Snippet {

               @Id
               private String id;
               private String title;
               private String keywords = "";
               private String description = "";

           @DBRef
               private Language lang;
           private String code;

           private Date created;
               private Date modified;

               public Snippet() {
                       this.created = new Date();
                       this.modified = new Date();
               }

               public Snippet(String title, String keywords, String description, Language lang, String code) {
                       this();
                       this.title = title;
                       this.keywords = keywords;
                       this.description = description;
                       this.lang = lang;
                       this.code = code;
               }

               public Snippet(String title, Language lang, String code) {
                       this(title, "", "", lang, code);
               }

               public String getId() {
                       return id;
               }

               public void setId(String id) {
                       this.id = id;
               }

               public String getTitle() {
                       return title;
               }

               public void setTitle(String title) {
                       this.title = title;
               }

               public Language getLang() {
                       return lang;
               }

               public void setLang(Language lang) {
                       this.lang = lang;
               }

               public String getDescription() {
                       return description;
               }

               public void setDescription(String description) {
                       this.description = description;
               }

               public String getKeywords() {
                       return keywords;
               }

               public void setKeywords(String keywords) {
                       this.keywords = keywords;
               }

               public String getCode() {
                       return code;
               }

               public void setCode(String code) {
                       this.code = code;
               }

               public Date getCreated() {
                       return created;
               }

               public void setCreated(Date created) {
                       this.created = created;
               }

               public Date getModified() {
                       return modified;
               }

               public void setModified(Date modified) {
                       this.modified = modified;
               }

           @Override
           public String toString() {
               return "Snippet{" +
                       "id='" + id + '\'' +
                       ", title='" + title + '\'' +
                       ", keywords='" + keywords + '\'' +
                       ", description='" + description + '\'' +
                       ", lang=" + lang +
                       ", code='" + code + '\'' +
                       ", created=" + created +
                       ", modified=" + modified +
                       '}';
           }
       }

9. Add the Repositories. Create/Copy the io.pivotal.workshop.snippet.repository.LanguageRespository.java and io.pivotal.workshop.snippet.repository.SnippetRespository.java interfaces.

   src/main/java/io/pivotal/workshop/snippet/repository/LanguageRepository.java

       package io.pivotal.workshop.snippet.repository;

       import io.pivotal.workshop.snippet.domain.Language;
       import io.pivotal.workshop.snippet.domain.Snippet;
       import org.springframework.data.mongodb.repository.MongoRepository;
       import reactor.core.publisher.Mono;

       public interface LanguageRepository extends MongoRepository<Language, String> {

           Language findByName(String name);

       }

   src/main/java/io/pivotal/workshop/snippet/repository/SnippetRepository.java

       package io.pivotal.workshop.snippet.repository;

       import io.pivotal.workshop.snippet.domain.Snippet;
       import org.springframework.data.mongodb.repository.MongoRepository;
       import org.springframework.data.mongodb.repository.Query;

       import java.util.concurrent.CompletableFuture;
       import java.util.stream.Stream;

       public interface SnippetRepository extends MongoRepository<Snippet, String> {

           @Query("{}")
           Stream<Snippet> findAllAsStream();
       }

   As you can see in the SnippetRepository interface declares a findAllAsStream method that returns all the snippets from MongoDB.

10. Next, lets create the Reative Server and the Functional Router for the app. Create the io.pivotal.workshop.snippet.config.SnippetReactive class:

    src/main/java/io/pivotal/workshop/snippet/config/SnippetReactiveConfig.java

        package io.pivotal.workshop.snippet.config;

        import io.pivotal.workshop.snippet.domain.Snippet;
        import io.pivotal.workshop.snippet.reactive.SnippetHandler;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
        import org.springframework.web.reactive.function.server.RouterFunction;
        import org.springframework.web.reactive.function.server.RouterFunctions;
        import org.springframework.web.reactive.function.server.ServerResponse;
        import reactor.core.publisher.EmitterProcessor;
        import reactor.ipc.netty.http.server.HttpServer;

        import static org.springframework.http.MediaType.APPLICATION_JSON;
        import static org.springframework.web.reactive.function.server.RequestPredicates.*;

        @Configuration
        public class SnippetReactiveConfig {

            @Bean
            public HttpServer httpServer(RouterFunction<ServerResponse> router){        //(1)
                HttpServer server = HttpServer.create("localhost", 8080);
                server.newHandler(new ReactorHttpHandlerAdapter(RouterFunctions.toHttpHandler(router))).block();
                return server;
            }

            @Bean
            public RouterFunction<ServerResponse> router(SnippetHandler handler){       //(2)
                return RouterFunctions
                        .route(GET("/snippets").and(accept(APPLICATION_JSON)), handler::findAll)
                        .andRoute(GET("/snippets/{id}").and(accept(APPLICATION_JSON)),handler::findById)
                        .andRoute(POST("/snippets").and(accept(APPLICATION_JSON)),handler::createSnippet);
            }

            @Bean
            public EmitterProcessor snippetStream(){                                   //(3)
                return EmitterProcessor.<Snippet>create();
            }
        }

    1. This method returns a HttpServer bean class that belongs to the Netty package, meaning that the app will run in a Reactive Server. This method is receiving a router function.

    2. This method returns a RouterFunction bean as a ServerResponse type. It defines what are the endpoints and how they will be handled. In this case we are using the SnippetHandler class that will have all the logic for handling all incoming requests.

    3. This method returns a EmitterProcessor bean that will be use to stream all the new Snippets created.

11. Let’s continue with the SnippetHandler. Create the io.pivotal.workshop.snippet.reactive.SnippetHandler class.

    src/main/java/io/pivotal/workshop/snippet/reactive/SnippetHandler.java

        package io.pivotal.workshop.snippet.reactive;

        import io.pivotal.workshop.snippet.domain.Language;
        import io.pivotal.workshop.snippet.domain.Snippet;
        import io.pivotal.workshop.snippet.repository.LanguageRepository;
        import io.pivotal.workshop.snippet.repository.SnippetRepository;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Component;
        import org.springframework.web.reactive.function.BodyInserters;
        import org.springframework.web.reactive.function.server.ServerRequest;
        import org.springframework.web.reactive.function.server.ServerResponse;
        import reactor.core.publisher.EmitterProcessor;
        import reactor.core.publisher.Flux;
        import reactor.core.publisher.Mono;

        import java.util.Optional;

        import static org.springframework.web.reactive.function.server.ServerResponse.notFound;
        import static org.springframework.web.reactive.function.server.ServerResponse.ok;

        @Component
        public class SnippetHandler {

            private EmitterProcessor<Snippet> snippetEmitterProcessor;
            private SnippetRepository snippetRepository;
            private LanguageRepository languageRepository;

            @Autowired  //(1)
            public SnippetHandler(SnippetRepository snippetRepository, LanguageRepository languageRepository, EmitterProcessor<Snippet> snippetEmitterProcessor) {
                this.snippetRepository = snippetRepository;
                this.languageRepository = languageRepository;
                this.snippetEmitterProcessor = snippetEmitterProcessor;
            }

            public Mono<ServerResponse> findAll(ServerRequest request){     //(2)
                return ok().body(BodyInserters.fromPublisher(Flux.fromStream(this.snippetRepository.findAllAsStream()),Snippet.class));
            }

            public Mono<ServerResponse> findById(ServerRequest request) {
                String snippetId = request.pathVariable("id");
                Optional<Snippet> result = this.snippetRepository.findById(snippetId);
                if(result.isPresent())
                    return ok().body(BodyInserters.fromPublisher(Mono.just(result.get()),Snippet.class));
                else
                    return notFound().build();
            }

            public Mono<ServerResponse> createSnippet(ServerRequest request) {
                Mono<Snippet> snippetMono = request.bodyToMono(Snippet.class);
                return ok().build(snippetMono.doOnNext(snippetConsumer -> {
                    Language language = new Language("Unknown","txt");
                    if(snippetConsumer.getLang()!=null){
                        language = this.languageRepository.findByName(snippetConsumer.getLang().getName());
                        if(language == null) {
                            language = this.languageRepository.save(snippetConsumer.getLang());
                        }
                    }else
                        language = this.languageRepository.save(language);

                    snippetConsumer.setLang(language);
                    Snippet result = this.snippetRepository.save(snippetConsumer);
                    this.snippetEmitterProcessor.onNext(result);   //(3)
                }).then());
            }

        }

    1. The constructor will autowired all the repositories and the EmitterProcessor.

    2. Take a look that all the methods receive a ServerRequest and the response is a Mono<ServerResponse> type.

    3. The createSnippet method will handle all the POST requests (/snippets) and create the necessary Language and Snippet, but the most important part here is that it will emit the snippet saved as stream.

12. Now, let’s create a rest controller that will stream all the new snippets. Create the io.pivotal.workshop.snippet.reactive.SnippetController class.

    src/main/java/io/pivotal/workshop/snippet/reactive/SnippetController.java

        package io.pivotal.workshop.snippet.reactive;

        import io.pivotal.workshop.snippet.domain.Snippet;
        import org.springframework.http.MediaType;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.RestController;
        import org.springframework.web.reactive.function.BodyInserters;
        import org.springframework.web.reactive.function.server.ServerRequest;
        import org.springframework.web.reactive.function.server.ServerResponse;
        import reactor.core.publisher.EmitterProcessor;
        import reactor.core.publisher.Flux;
        import reactor.core.publisher.Mono;

        import static org.springframework.web.reactive.function.server.ServerResponse.ok;

        @RestController
        public class SnippetController {

            private EmitterProcessor snippetStream;

            public SnippetController(EmitterProcessor snippetStream) {  //(1)
                this.snippetStream = snippetStream;
            }

            @GetMapping(path = "/logs", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
            public Flux<Snippet> snippetLog(){                          //(2)
                return snippetStream.doOnSubscribe(subscription -> { snippetStream.next().subscribe(); });

            }
        }

    1. The constructor injects the EmitterProcessor.

    2. This method will handle the stream publish in /logs endpoint. See that it’s subscribe to the stream.

13. In the src/main/resources/application.properties file add the following properties:

    src/main/resources/application.properties

        spring.data.mongodb.database=snippets-[YOUR INITIALS]
        spring.data.mongodb.host=[PROVIDED BY THE INSTRUCTOR]
        spring.data.mongodb.port=27017
        spring.data.mongodb.username=[PROVIDED BY THE INSTRUCTOR]
        spring.data.mongodb.password=[PROVIDED BY THE INSTRUCTOR]

    REPLACE the values accordingly.

14. Run the application and go to your browser and hit the http://localhost:8080/logs and browser should be waiting for incoming data.

15. Open a new browser tab or window and point to the http://localhost:8080/snippets and you will see an empty json collection.

16. Open a Terminal window and add some snippets using the cUrl command:

        curl -i -X POST -H "Content-Type: application/json" -H "Accept: application/json" -d '{"title":"Hello World","code":"println \"This is awesome\".upperCase()","lang":{"name": "Groovy", "syntax":"groovy"}}' http://localhost:8080/snippets

    Tip | If you are a Windows user, you can use the POSTMAN (https://www.getpostman.com/) application to execute the POST.

    Add more snippets and see how the http://localhost:8080/logs start receiving some Snippet streams.

17. Review the snippets by hitting again the http://localhost:8080/snippets

### Challenges

- Add an Embedded MongoDB and run the same application. You must add the following dependency to your pom.xml or gradle:

  pom.xml

      <dependency>
              <groupId>de.flapdoodle.embed</groupId>
              <artifactId>de.flapdoodle.embed.mongo</artifactId>
              <scope>runtime</scope>
      </dependency>

  or

  build.gradle

      runtime('de.flapdoodle.embed:de.flapdoodle.embed.mongo')

## HOMEWORK

- Use the annotation-based WebFlux variant for the Code Snippet Manager App for all the endpoints.