BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Articles Tutoriel Micronaut : Comment Construire Des Micro-Services Avec Ce Framework Basé Sur La JVM

Tutoriel Micronaut : Comment Construire Des Micro-Services Avec Ce Framework Basé Sur La JVM

Favoris

Points Clés

  • Micronaut est une framework full-stack moderne, exécuté dans une JVM, qui permet de créer des applications de type microservice modulaires et facilement testables.
  • Micronaut met en oeuvre l'injection de dépendance et AOP 100% compile-time, sans réflexion. 
  • L'équipe de développement du framework est le même groupe que celui du Framework Grail. 
  • Micronaut intègre les technologies cloud dans le framework, et des patterns de microservices tels que la découverte de services, le traçage distribué, les circuit breaker sont intégrés dans le framework.
  • Au cours de ce tutoriel, vous allez créer trois microservices avec différentes langues : Java, Kotlin et Groovy. Vous apprendrez également à quel point il est facile de consommer d'autres microservices avec Micronaut HTTP Client et comment créer des tests fonctionnels qui s'exécuten rapidement.

Contrairement aux applications construites à l'aide de frameworks traditionnels dans la JVM,  Micronaut utilise une injection de dépendance et l'AOP 100% compile-time, sans réflexion. Ainsi, les applications Micronaut sont petites et ont une faible empreinte mémoire. Avec Micronaut, vous pouvez développer un gros monolithe ou une petite fonction pouvant être déployée avec AWS Lambda. Vous n'êtes pas contraint par le framework.

Micronaut intègre également les technologies cloud dans le framewrok, et des patterns pour microservices tels que la découverte de services, le traçage distribué, le circuit-breaker sont intégrés dans le framework.

Micronaut a été publié en open source en mai 2018 et sa version 1.0.0 est publiée en octobre 2018. Vous pouvez essayer Micronaut aujourd'hui, dans la mesure où des versions release candidates et milestones sont disponibles.

L'équipe de développement de Micronaut est le même groupe que celui du framework Grails. Grails, qui vient de fêter ses 10 ans, continue d'aider les développeurs à concevoir des applications Web avec de nombreux accélérateurs de productivité. Grails 3 est construit au-dessus de Spring Boot. Comme vous le découvrirez bientôt, Micronaut offre une courbe d'apprentissage facile aux développeurs issus des deux frameworks, Grails et Spring Boot.

Présentation du tutoriel

Dans cet article, nous allons créer une application composée de plusieurs microservices :

  • Un microservice books; écrit en Groovy.
  • Un microservice inventory; écrit en Kotlin.
  • Un microservice gateway; écrit en Java.

Vous allez :

  • Écrire les endpoints et utilisez l'injection de dépendance (DI) compile-time.
  • Écrire des tests fonctionnels.
  • Configurer ces applications Micronaut pour s'enregistrer auprès de Consul.
  • Communiquez entre eux avec le client HTTP déclaratif de Micronaut.

Le diagramme ci-dessous illustre l'application que vous allez construire :

Microservice #1 - un Microservice Groovy

Le moyen le plus simple de créer des applications Micronaut consiste à utiliser son interface en ligne de commande (Micronaut CLI ), qui peut être facilement installée via SDKMan.

Les applications Micronaut peuvent être écrites en Java, Kotlin et Groovy. Commençons par créer une application Groovy Micronaut :

mn create-app example.micronaut.books --lang groovy .

La commande précédente crée une application nommée books avec un package par défaut example.micronaut.

Micronaut est agnostique aux frameworks de tests. Il sélectionne un framework de tests par défaut en fonction du langage que vous utilisez. Par défaut pour Java, JUnit est utilisé. Si vous sélectionnez Groovy, Spock est utilisé par défaut. Vous pouvez mélanger différents langages et frameworks de test. Par exemple, une application Java Micronaut testée avec Spock.

De plus, Micronaut est agnostique aux outils de build. Vous pouvez utiliser Maven ou Gradle. Gradle est utilisé par défaut.

L'application générée comprend un serveur HTTP non bloquant basé sur Netty.

Créez un contrôleur pour exposer votre premier endpoint Micronaut :


books/src/main/groovy/example/micronaut/BooksController.groovy

package example.micronaut

import groovy.transform.CompileStatic
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@CompileStatic
@Controller("/api")
class BooksController {

    private final BooksRepository booksRepository

    BooksController(BooksRepository booksRepository) {
        this.booksRepository = booksRepository
    }

    @Get("/books")
    List<Book> list() {
        booksRepository.findAll()
    }
}

Plusieurs choses sont notables dans le code précédent.

  • Le contrôleur expose une route /api/books pouvant être appelée avec une requête GET.
  • La valeur des annotations @Get et @Controller est un template d'URI RFC-6570.
  • Par injection via le constructeur, Micronaut fournit un collaborateur; BooksRepository.
  • Les contrôleurs Micronaut consomment et produisent du JSON par défaut.

Le contrôleur précédent utilise une interface et un POGO :

books/src/main/groovy/example/micronaut/BooksRepository.groovy

package example.micronaut

interface BooksRepository {
    List<Book> findAll()
}

books/src/main/groovy/example/micronaut/Book.groovy

package example.micronaut

import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor

@CompileStatic
@TupleConstructor
class Book {
    String isbn
    String name
}

Micronaut connecte à la compilation un bean implémentant l'interface BooksRepository.

Pour cette application, nous créons un singleton, que nous définissons avec l'annotation javax.inject.Singleton.

books/src/main/groovy/example/micronaut/BooksRepositoryImpl.groovy

package example.micronaut

import groovy.transform.CompileStatic
import javax.inject.Singleton

@CompileStatic
@Singleton
class BooksRepositoryImpl implements BooksRepository {

    @Override
    List<Book> findAll() {
        [
            new Book("1491950358", "Building Microservices"),
            new Book("1680502395", "Release It!"),
        ]
    }
}

Les tests fonctionnels ajoutent le plus de valeur puisqu'ils testent l'application dans son intégralité. Cependant, avec d'autres frameworks, les tests fonctionnels et d'intégration sont rarement utilisés. Généralement, étant donné qu'ils impliquent le démarrage de toute l'application, ils sont lents.

Cependant, écrire des tests fonctionnels en Micronaut est une joie. Parce qu'ils sont rapides; très rapide.

Un test fonctionnel pour le contrôleur précédent est donné ci-dessous :

books/src/test/groovy/example/micronaut/BooksControllerSpec.groovy

package example.micronaut

import io.micronaut.context.ApplicationContext
import io.micronaut.core.type.Argument
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.RxHttpClient
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class BooksControllerSpec extends Specification {

    @Shared
    @AutoCleanup
    EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer)

    @Shared @AutoCleanup RxHttpClient client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL())

    void "test books retrieve"() { 
        when:
        HttpRequest request = HttpRequest.GET('/api/books')
        List<Book> books = client.toBlocking().retrieve(request, Argument.of(List, Book))

        then:
        books books.size() == 2
    }
}

Plusieurs points méritent d'être mentionnés dans le test précédent :

  • Il est facile d'exécuter l'application à partir d'un test unitaire avec l'interface EmbeddedServer.
  • Vous pouvez facilement créer un bean client HTTP pour utiliser le serveur intégré.
  • Micronaut Http Client facilite l'analyse de JSON en objets Java.

Second Microservice. Un microservice Kotlin

Exécutez la commande suivante pour créer un autre microservice nommé inventory. Cette fois, nous utilisons Kotlin comme langage.

mn create-app example.micronaut.inventory --lang kotlin

Ce nouveau microservice contrôle le stock de chaque livre.

Créez une Data Class Kotlin pour encapsuler le domaine :

inventory/src/main/kotlin/example/micronaut/Book.kt

package example.micronaut

data class Book(val isbn: String, val stock: Int)

Créez un contrôleur qui renvoie le stock d'un livre.

inventory/src/main/kotlin/example/micronaut/BookController.kt

package example.micronaut

import io.micronaut.http.HttpResponse 
import io.micronaut.http.MediaType 
import io.micronaut.http.annotation.Controller 
import io.micronaut.http.annotation.Get 
import io.micronaut.http.annotation.Produces
import io.micronaut.security.annotation.Secured

@Controller("/api") 
class BooksController {

    @Produces(MediaType.TEXT_PLAIN) 
    @Get("/inventory/{isbn}") 
    fun inventory(isbn: String): HttpResponse<Int> {
        return when (isbn) { 
            "1491950358" -> HttpResponse.ok(2) 
            "1680502395" -> HttpResponse.ok(3) 
            else -> HttpResponse.notFound()
        }
    }
}

Troisième Microservice. Un microservice Java

Créez une application gateway en Java qui consomme à la fois les microservices books et inventory.

mn create-app example.micronaut.gateway

Java est sélectionné par défaut si vous ne spécifiez pas le flag lang.

Dans le microservice gateway, créez un client HTTP déclaratif pour communiquer avec le microservice books.

Commencez par créer une interface :

gateway/src/main/java/example/micronaut/BooksFetcher.java

package example.micronaut;

import io.reactivex.Flowable;

public interface BooksFetcher { 
    Flowable<Book> fetchBooks(); 
}

Créez ensuite un client HTTP déclaratif. une interface annotée avec @Client.

gateway/src/main/java/example/micronaut/BooksClient.java

package example.micronaut;

import io.micronaut.context.annotation.Requires; 
import io.micronaut.context.env.Environment; 
import io.micronaut.http.annotation.Get; 
import io.micronaut.http.client.annotation.Client; 
import io.reactivex.Flowable;

@Client("books") 

@Requires(notEnv = Environment.TEST) 

public interface BooksClient extends BooksFetcher {

    @Override @Get("/api/books") Flowable<Book> fetchBooks();

}

Les méthodes du client HTTP déclaratif de Micronaut seront implémentées pour vous au moment de la compilation, ce qui simplifiera grandement la création de clients HTTP.

En outre, Micronaut prend en charge le concept d'environnement d'application. Dans le code précédent, vous pouvez voir à quel point il est facile de désactiver le chargement de certains beans pour un environnement particulier avec l'annotation @Requires.

De plus, comme vous pouvez le constater dans l'exemple de code précédent, les types non bloquants sont des first class citizen dans Micronaut. La méthode BooksClient::fetchBooks() retourne un Flowable<Book> où Book est un POJO Java :

gateway/src/main/java/example/micronaut/Book.java

package example.micronaut;

public class Book {
     private String isbn; 
     private String name; 
     private Integer stock;
     
     public Book() {}

     public Book(String isbn, String name) { 
         this.isbn = isbn; 
         this.name = name; 
     }

     public String getIsbn() { return isbn; }

     public void setIsbn(String isbn) { this.isbn = isbn; }

     public String getName() { return name; }

     public void setName(String name) { this.name = name; }
     
     public Integer getStock() { return stock; }

     public void setStock(Integer stock) { this.stock = stock; }
}

Créez un autre client HTTP déclaratif pour communiquer avec le microservice inventory.

Commencez par créer une interface :

gateway/src/main/java/example/micronaut/InventoryFetcher.java

package example.micronaut;

import io.reactivex.Maybe;

public interface InventoryFetcher { 
    Maybe<Integer> inventory(String isbn); 
}

Ensuite, un client HTTP déclaratif :

gateway/src/main/java/example/micronaut/InventoryClient.java

package example.micronaut;

import io.micronaut.context.annotation.Requires; 
import io.micronaut.context.env.Environment; 
import io.micronaut.http.annotation.Get; 
import io.micronaut.http.client.Client; 
import io.reactivex.Maybe; 

@Client("inventory") 
@Requires(notEnv = Environment.TEST)
public interface InventoryClient extends InventoryFetcher {
    @Override 
    @Get("/api/inventory/{isbn}") 
    Maybe<Integer> inventory(String isbn);
}

Maintenant, créez un contrôleur qui injecte les deux beans et crée une réponse réactive.

gateway/src/main/java/example/micronaut/BooksController.java

package example.micronaut;

import io.micronaut.http.annotation.Controller; 
import io.micronaut.http.annotation.Get;
import io.micronaut.security.annotation.Secured; 
import io.reactivex.Flowable;
import java.util.List;

@Controller("/api") 
public class BooksController {

    private final BooksFetcher booksFetcher; 
    private final InventoryFetcher inventoryFetcher;

    public BooksController(BooksFetcher booksFetcher, InventoryFetcher inventoryFetcher) {
        this.booksFetcher = booksFetcher;
        this.inventoryFetcher = inventoryFetcher; 
    }

    @Get("/books") Flowable<Book> findAll() { 
        return booksFetcher.fetchBooks()
                   .flatMapMaybe(b -> inventoryFetcher.inventory(b.getIsbn())
                        .filter(stock -> stock > 0)
                        .map(stock -> { 
                            b.setStock(stock); 
                            return b; 
                        })
                    );

    }
}

Avant de pouvoir créer un test fonctionnel pour le contrôleur, nous devons créer des implémentations de bean pour ( BooksFetcheret  et InventoryFetcher) dans l'environnement de test.

Créer un bean implémentant l'interface BooksFetcher, disponible uniquement pour l'environnement de test; voir l'annotation   @Requires.


gateway/src/test/java/example/micronaut/MockBooksClient.java

package example.micronaut;

import io.micronaut.context.annotation.Requires; 
import io.micronaut.context.env.Environment; 
import io.reactivex.Flowable;
import javax.inject.Singleton;

@Singleton 
@Requires(env = Environment.TEST) 
public class MockBooksClient implements BooksFetcher {
    @Override
    public Flowable<Book> fetchBooks() { 
        return Flowable.just(new Book("1491950358", "Building Microservices"), new Book("1680502395", "Release It!"), new Book("0321601912", "Continuous Delivery:"));
    } 
}

Créez un bean qui implémente l'interface InventoryFetcher, disponible uniquement pour l'environnement de test.

gateway/src/test/java/example/micronaut/MockInventoryClient.java

package example.micronaut;

import io.micronaut.context.annotation.Requires; 
import io.micronaut.context.env.Environment; 
import io.reactivex.Maybe;
import javax.inject.Singleton;

@Singleton 
@Requires(env = Environment.TEST) 
public class MockInventoryClient implements InventoryFetcher {

    @Override 
    public Maybe<Integer> inventory(String isbn) { 
        if (isbn.equals("1491950358")) { 
            return Maybe.just(2); 
        } 
        if (isbn.equals("1680502395")) { 
            return Maybe.just(0); 
        } 
        return Maybe.empty();
    } 
}

Créez un test fonctionnel. Dans le microservice Groovy, nous avons écrit un test Spock. Cette fois, nous écrivons un test JUnit.

gateway/src/test/java/example/micronaut/BooksControllerTest.java

package example.micronaut;

import io.micronaut.context.ApplicationContext;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.HttpClient;
import io.micronaut.runtime.server.EmbeddedServer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.List;

public class BooksControllerTest {

    private static EmbeddedServer server; 
    private static HttpClient client;

    @BeforeClass 
    public static void setupServer() {
        server = ApplicationContext.run(EmbeddedServer.class); 
        client = server .getApplicationContext() .createBean(HttpClient.class, server.getURL());
    }

    @AfterClass 
    public static void stopServer() {
        if (server != null) { 
            server.stop();
        }
        if (client != null) { 
            client.stop();
        }
     }

     @Test 
     public void retrieveBooks() { 
         HttpRequest request = HttpRequest.GET("/api/books");         
         List<Book> books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class)); 
         assertNotNull(books); 
         assertEquals(1, books.size());
     } 
}

Service de découverte (Discovery service)

Nous allons configurer notre microservice Micronaut pour s'enregistrer auprès du service de découverte Consul.

Consul est un service mesh distribué permettant de connecter, sécuriser et configurer des services sur toute plate-forme d'exécution et sur un cloud public ou privé.

Intégrer Micronaut et Consul est simple.

Premièrement, ajoutez à chaque microservice books, inventory et gateway la dépendance discovery-client :

gateway/build.gradle

runtime "io.micronaut:micronaut-discovery-client"

books/build.gradle

runtime "io.micronaut:micronaut-discovery-client"

inventory/build.gradle

runtime "io.micronaut:micronaut-discovery-client"

Nous devons apporter des modifications de configuration à chaque application pour que, lors du démarrage de l'application, celle-ci s'enregistre auprès de Consul.

gateway/src/main/resources/application.yml

micronaut:
    application:
        name: gateway 
    server:
        port: 8080
consul:
    client:
        registration: 
            enabled: true
        defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"



books/src/main/resources/application.yml
micronaut:
    application:
        name: books
    server:
        port: 8082
consul:
    client:
        registration: 
            enabled: true
        defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"



inventory/src/main/resources/application.yml
micronaut:
    application:
        name: inventory
    server:
        port: 8081
consul:
    client:
        registration: 
            enabled: true
        defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

Chaque service utilise la propriété micronaut.application.name comme identifiant de service lors de son enregistrement dans Consul. C'est pourquoi nous utilisons ces noms exacts dans l'annotation @Client.

Les lignes de codes précédentes illustrent une autre caractéristique de Micronaut, l'interpolation des variables d'environnement avec les valeurs par défaut dans les fichiers de configuration. Voir :

defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

En outre, dans Micronaut, vous pouvez avoir des fichiers de configuration spécifiques à l'environnement. Nous allons créer un fichier nommé application-test.yml dans chacun des environnements pour l'enregistrement dans Consul lors de la phase de test.

gateway/src/test/resources/application-test.yml
consul:
    client:
        registration: enabled: false


books/src/test/resources/application-test.yml
consul:
    client:
        registration: enabled: false


inventory/src/test/resources/application-test.yml
consul:
    client:
        registration: enabled: false

Lancer l'application

Le moyen le plus simple de commencer à utiliser Consul est d'utiliser Docker. Maintenant, exécutez une instance de Docker.

docker run -p 8500:8500 consul

Créez un build multi-projets avec Gradle. Créez un fichier settings.gradle dans le dossier racine :

settings.gradle
include 'books'

include 'inventory'

include 'gateway'

Maintenant, vous pouvez exécuter chaque application en parallèle. Gradle a un flag pratique ( -parallel ) pour cela :

./gradlew -parallel run

Chaque microservice commence dans les ports configurés : 8080, 8081 et 8082.

Consul est livré avec une interface utilisateur HTML. Ouvrez http://localhost:8500/ui dans votre navigateur, vous verrez :

Chaque micro service Micronaut s'est enregistré auprès de Consul.

Vous pouvez appeler le microservice gateway avec la commande cURL :

$ curl http://localhost:8080/api/books [{"isbn":"1680502395","name":"Release It!","stock":3}, {"isbn":"1491950358","name":"Building Microservices","stock":2}]

Félicitations, vous avez créé votre premier réseau de microservices Micronaut!

Bilan

Au cours de ce tutoriel, vous avez créé trois microservices avec différentes langues : Java, Kotlin et Groovy. Vous avez également appris à quel point il est facile de consommer d'autres microservices avec le client HTTP Micronaut et comment créer des tests fonctionnels qui s'exécutenent rapidement. De plus, vous avez tout créé en bénéficiant d'une injection de dépendance et d'AOP sans réflexion.

A propos de l'auteur

Sergio del Amo Caballero est un développeur spécialisé dans le développement d'applications pour téléphone mobile (iOS, Android) reposant sur des backends Grails / Micronaut. Depuis 2015, Sergio del Amo écrit une lettre d'information, Groovy Calamari, consacrée à écosystème Groovy et Microservices. Groovy, Grails, Micronaut, Gradle, ...

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT