Giter Site home page Giter Site logo

nightclazz-spring5's Introduction

Reactive Drawing Application

Cette application permet de se familiariser avec différentes fonctionnalités de Spring 5. Ce fichier présente les différents exercices qui permettent de reconstruire l'application. Les dossiers nc-spring-webflux et nc-spring-webmvc contiennent un exemple de résultat recherché.

L'application

L'application est une interface WEB de construction et partage de dessins. Lorsque l'utilisateur se connecte, son nom lui est demandé pour démarrer un dessin. Via une palette, l'utilisateur dispose de différents outils pour dessiner. Une fois son dessin terminé, il peut ajouter son dessin à une liste contenant les dessins de tous les utilisateurs connectés à l'application. Dès qu'un dessin est ajouté, il apparaît dans la liste des dessins de tous les utilisateurs.

Architecture

Le schéma d'architecture peut se présenter ainsi :

                     +-------------+
                     |             |
                +--->|   MongoDB   |<---+
                |    |             |    |
 Tailable cursor|    +-------------+    | Load all images
                |                       |
                |                       |
   Push new +-------------+  +-------------+
   image ID |             |  |             |
    +-------|   WebFlux   |  |    WebMvc   |<---+
    |       |             |  |             |    |
    |       +-------------+  +-------------+    |
    |                ^            ^             |
    |     Add image  |            |HTTP/2       |
    |     Open SSE   |            |Load statics |
    |     connection |            |Load images  |       
    |                |            |             |
    |               +---------------+           |
    |               |               |HTTP/2 push|
    +-------------> |    Browser    |-----------+
                    |               |
                    +---------------+
  • Les images sont stockées dans MongoDB en base 64, elles seront chargées par les modules WEB
  • Un premier module est basé sur WebFlux, il correspond à l'API de l'application:
    • Le navigateur y ouvre une connexion SSE pour recevoir les images
    • Afin d'envoyer les URLs des images, le module WebFlux crée un tailable cursor
    • Un service WEB permet l'ajout d'une nouvelle image qui sera naturellement reçue via le tailable cursor dès son enregistrement
  • Un second module basé sur WebMvc permet de servir les statics:
    • Un service WEB permet de charger une image en fonction de son ID.
    • Le navigateur s'y connecte en HTTP/2
    • Lorsque la page HTML est retournée, le module utilise le HTTP/2 server-push pour envoyer le contenu des images existantes

Pré-requis

  • Disposer d'un JDK 8
  • Avoir un accès à une instance MongoDB 3
  • Avoir une connexion internet
  • Utiliser Eclipse ou IntelliJ de préférence

Resources utiles

Les ressources suivantes contiennent différents exemples qui permettent de réaliser les exercices. Elles sont citées dans un ordre cohérent avec celui des exercices.

Exercices

Le module WebFlux

Dans cet exercice, nous allons déclarer le contrat d'interface de l'API, les mappings et tests unitaires. Pour initialiser le module, allez sur spring initializr et sélectionnez Spring Boot 2. Ajoutez les modules et Reactive Web et Reactive Mongo, conservez Maven. Téléchargez l'archive ZIP et ouvrez le projet dans votre IDE.

Créez les objets du domaine:

  • Une classe DrawingInfo annotée @Document(collection = "drawings") qui contient deux attributs de type String id et author
  • Une classe Drawing qui étend DrawingInfo avec un attribut de type String base64Image

Créez l'interface ReactiveDrawingController avec les méthodes suivantes:

  • Mono<String> add(Mono<Drawing> drawing)
  • Flux<DrawingInfo> getDrawings()

Il faut maintenant déclarer les mappings. Ils se font traditionnellement avec les annotations GetMapping, RequestMapping, etc. Nous allons plutôt utiliser un bean de type RouterFunction pour câbler les méthodes du contrôleur avec les URIs.

Créez une classe de configuration @Configuration avec une méthode @Bean public RouterFunction<ServerResponse> routingFunction(ReactiveDrawingController). Utilisez les méthodes statiques de RouterFunctions pour créer deux mappings:

  • Un premier qui accepte une requête en GET sur /drawings et qui envoie en réponse ReactiveDrawingController#getDrawings() et MediaType.TEXT_EVENT_STREAM comme Content-Type.
  • Un second qui accepte une requête en POST sur /drawing qui envoie en réponse ReactiveDrawingController#add(Mono<Drawing>). La requête et la réponse sont en JSON.

Nous allons maintenant créer des tests unitaires. Créez une classe de test @SpringBootTest.

Déclarez une classe interne statique @Configuration dans laquelle vous créez un bean de type ReactiveDrawingController. Appuyez-vous sur Mockito pour retourner un Flux lors de l'appel de getDrawings(). Pensez à importer votre classe de configuration qui créee le RouterFunctions via @Import.

Avant d'écrire votre premier test, il vous faut un WebTestClient en tant qu'attribut. Regardez la Javadoc pour trouver comment créer une instance de WebTestClient qui tient compte de votre RouterFunction.

Ecrivez une méthode @Test qui utilise le WebTestClient pour envoyer une requête sur /drawings. Manipulez l'API pour vérifier que le statut de la réponse est ok et qu'il contient bien les éléments du Flux dans votre mock.

Le module WebMvc

Dans cet exercice, nous allons créer le module web qui va fournir les statics.

Pour initialiser le module, allez sur spring initializr et sélectionnez Spring Boot 2. Ajoutez les modules et Web et Reactive Mongo, conservez Maven. Téléchargez l'archive ZIP et ouvrez le projet dans votre IDE.

Créez les classes du domaine Drawing et DrawingInfo comme dans WebFlux (on s'autorisera un peu de duplication plutôt que de créer un module commun).

Créez maintenant une classe DrawingController et injectez dans le constructeur un ReactiveMongoTemplate.

Implémentez un service web sur l'URI /drawing/{id} qui:

  • Prend en paramètre l'ID du dessin (type String)
  • Retourne un tableau de byte (precisez dans @GetMapping l'attribut produces=MediaType.IMAGE_PNG_VALUE et ajoutez l'annotation ResponseBody)
  • Utilise ReactiveMongoTemplate#findById() pour charger un dessin de type Drawing et le map en tableau de byte

Implémentez une seconde méthode avec un mapping sur /:

  • Elle prend en paramètre le PushBuilder l'API servlet 4
  • Utilisez ReactiveMongoTemplate#find() pour charger la liste des dessins via le type DrawingInfo et effectuez un server-push sur chacune des URLs d'image obtenues à partir des ID des dessins.
  • Retourne la String drawing.html

Enfin créez une classe @Configuration dans laquelle vous:

Dans src/main/resources, décompressez le contenu de cette archive: https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/Zenika/nightclazz-spring5/tree/master/nc-spring-webmvc/src/main/resources Vous y trouverez:

  • Un répertoire static dont le contenu sera exposé par Spring Boot. C'est la partie front de l'application.
  • Un application.yml servant de base à la configuration HTTPS
  • Un keystore référencé dans application.yml

HTTP/2 ne fonctionne qu'en HTTPs. C'est pour cela qu'il faut avoir les éléments de configuration nécessaires à l'activation de HTTPs.

A noter également que l'application envoie des requêtes vers l'API du module WebFlux. Si la page est chargée en HTTPs, alors WebFlux doit aussi écouter en HTTPs. Nous allons donc également configurer HTTPs pour WebFlux.

Activer HTTP/2 et HTTPs

Téléchargez et décompressez dans le répertoire src/main/resources du module WebFlux le contenu de cette archive: https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/Zenika/nightclazz-spring5/tree/master/nc-spring-webflux/src/main/resources Vous y trouverez des informations de même nature que celles concernant HTTPs dans le répertoire du module WebMvc.

Par défaut, Spring Boot doit pouvoir automatiquement configurer HTTPs avec les informations présentes, mais:

  • Avec WebMvc cela sera en HTTP/1.1 alors que nous avons besoin de HTTP/2.
  • Avec WebFlux, l'auto-configuration en HTTPs n'est pas encore implémentée.

Il faut donc substituer un bean de configuration spécifique qui accède à l'API native du conteneur de servlet. Nous allons utiliser Jetty dans les deux modules.

Modifiez le pom.xml de WebFlux comme ceci:

    <properties>
        <jetty.version>9.4.7.v20170914</jetty.version>
    </properties>

    ...

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-netty</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
    
    ...
    
    <repositories>
        <repository>
            <id>jetty-snapshots</id>
            <name>Jetty Snapshots</name>
            <url>https://oss.sonatype.org/content/repositories/jetty-snapshots</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

Modifiez le pom.xml de WebMvc comme ceci (il faut plus de modules pour HTTP/2):

    <properties>
        <jetty.version>10.0.0-SNAPSHOT</jetty.version>
        <!-- https://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-versions -->
        <alpn-version>8.1.11.v20170118</alpn-version>
    </properties>

    ...

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.0-b05</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
    <dependency>
        <groupId>org.eclipse.jetty.http2</groupId>
        <artifactId>http2-server</artifactId>
        <version>${jetty.version}</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-alpn-server</artifactId>
        <version>${jetty.version}</version>
    </dependency>
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-jndi</artifactId>
        <version>${jetty.version}</version>
    </dependency>
    <dependency>
        <groupId>org.mortbay.jetty.alpn</groupId>
        <artifactId>alpn-boot</artifactId>
        <version>${alpn-version}</version>
        <scope>provided</scope>
    </dependency>
    
    ...
    
    <repositories>
        <repository>
            <id>jetty-snapshots</id>
            <name>Jetty Snapshots</name>
            <url>https://oss.sonatype.org/content/repositories/jetty-snapshots</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

Notez que nous utilisons la version 10 de Jetty, toujours en snapshot. Cela nous permettra d'exploiter l'API servlet 4 afin de faire du server-push avec HTTP/2. Regardez également la version utilisée pour ALPN et assurez-vous qu'elle est compatible avec votre JDK.

Il faut maintenant déclarer les configurations. Pour WebFlux, il faut configurer le support de SSL : https://github.com/Zenika/nightclazz-spring5/blob/master/nc-spring-webflux/src/main/resources/application.yml#L7-L11

Pour WebMvc, on peut passer par un WebServerFactoryCustomizer:

Lancer WebMvc

Nous allons maintenant configurer les paramètres de lancement de WebMvc. Dans votre IDE, ajustez les options de la JVM avec le chemin du JAR ALPN: -Xbootclasspath/p:<<MAVEN_LCOAL_REPO>>/org/mortbay/jetty/alpn/alpn-boot/<<version>>/alpn-boot-<<version>>.jar

Nous allons également passer en argument les informations nécessaires pour écouter sur le port 8443 et se connecter à MongoDB.

  • --spring.data.mongodb.uri=mongodb://<<user>>:<<password>>@<<host>>:<<port>>/<<database>>
  • --server.port=8443

A ce stade, vous devriez pouvoir démarrer le module WebMvc. Néanmoins, l'application doit s'appuyer sur le module WebFlux. Nous allons maintenant le démarrer.

Lancer WebFlux

Le module est correctement configuré pour écouter en HTTPs. Nous avons néanmoins deux problèmes que nous allons traiter:

  • ReactiveDrawingController n'est pas implémenté (il a été bouchonné pour les tests unitaires)
  • Les requêtes envoyées par le navigateur viennent d'un port différent, il faut configurer le CORS

Créez une classe ReactiveDrawingControllerImpl. Assurez-vous qu'elle est bien injectée dans le contexte Spring tout en étant ignorée dans les tests unitaires.

Vous devez injecter dans votre contrôleur un bean qui étend ReactiveCrudRepository afin de récupérer et sauvegarder les dessins. Pour ce faire, créez une interface DrawingRepository qui étend ReactiveCrudRepository. Ce dernier peut être spécialisé pour les dessins, référez-vous à la Javadoc afin de savoir comment configurer vos generics. Pour la sauvegarde, vous disposez d'un Mono<Drawing> en paramètre de la méthode add du contrôleur qui peut être utilisé dans une méthode saveAll du ReactiveCrudRepository. Pour récupérer les dessins dans un flux continu qui alimentera la connexion SSE, vous devez utiliser un tailable cursor. Ajoutez dans votre DrawingRepository une méthode qui retournera un Flux qui est annotée @Tailable.

Il faut à présent configurer le CORS pour que des requêtes depuis https://localhost:8443 soit acceptées. Le protocole HTTP prévoit d'envoyer une requête OPTIONS afin de savoir quelles requêtes seront acceptées. Dans votre bean de configuration des routes, faites en sorte qu'une requête sur /drawing* avec la méthode OPTIONS soit ok.

Créez ensuite une nouvelle classe de configuration annotée @EnableWebFlux qui implémente WebFluxConfigurer. Surchargez addCorsMappings(CorsRegistry) et utilisez le registre pour faire les configurations suivantes:

  • Méthode GET autorisée sur /drawings
  • Méthode POST autorisée sur /drawing
  • https://localhost:8443 comme origine autorisée sur /drawings et /drawing
  • Content-Type comme header autorisée sur /drawings et /drawing

Il ne reste plus qu'à lancer le module WebFlux. Configurer votre IDE pour passer les arguments suivants;

  • --spring.data.mongodb.uri=mongodb://<<user>>:<<password>>@<<host>>:<<port>>/<<database>>
  • --server.port=9443

Connectez-vous à présent sur https://localhost:8443 et testez l'application!

Kotlin

Nous allons maintenant utiliser le language Kotlin pour déclarer nos routes de façon plus concise. Du code Java et Kotlin peuvent tout à fait cohabiter ensemble.

Dans le pom.xml du module WebFlux, déclarez les éléments suivants:

<properties>
    <kotin.version>1.1.2-5</kotin.version>
</properties>

...

<dependencies>
    <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-stdlib-jre8</artifactId>
        <version>${kotlin.version}</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-test</artifactId>
        <version>${kotlin.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

...

<build>
    <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
    <plugins>
        <plugin>
            <artifactId>kotlin-maven-plugin</artifactId>
            <executions>
                <execution>
                    <id>compile</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                    <configuration>
                        <sourceDirs>
                            <source>src/main/java</source>
                            <source>src/main/kotlin</source>
                        </sourceDirs>
                    </configuration>
                </execution>
                <execution>
                    <id>test-compile</id>
                    <phase>test-compile</phase>
                    <goals>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <groupId>org.jetbrains.kotlin</groupId>
            <version>${kotlin.version}</version>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <executions>
                <execution>
                    <id>default-compile</id>
                    <phase>none</phase>
                </execution>
                <execution>
                    <id>default-testCompile</id>
                    <phase>none</phase>
                </execution>
                <execution>
                    <id>compile</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
                <execution>
                    <id>testCompile</id>
                    <phase>test-compile</phase>
                    <goals>
                        <goal>testCompile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Commentez votre classe RouteConfig et ajoutez une classe RouteConfig.kt dans /src/main/kotlin. Essayez de réécrire les différentes méthodes de votre classe de configuration Java en Kotlin. Inspirez-vous des ressources citées plus haut afin de trouver des élémens syntaxiques qui pourront vous aider.

Si vous disposez de IntelliJ, un support avancé du language est proposé. Lorsque vous copier/coller du code Java dans une classe Kotlin, l'IDE peut vous convertir relatvement efficacement le code.

Une fois terminé testez de nouveau l'application.

nightclazz-spring5's People

Contributors

gdrouet avatar pyaillet avatar vickrame avatar

Stargazers

 avatar

Watchers

Sebastien Descamps avatar Julien Landuré avatar James Cloos avatar  avatar Carl Azoury avatar  avatar  avatar

Forkers

maxsky5

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.