Guide sur les microservices réactifs à l’aide du framework Lagom

1. Vue d’ensemble

Dans cet article, nous allons explorer le framework Lagom et mettre en œuvre un exemple d’application en utilisant une architecture réactive pilotée par microservices.

En termes simples, les applications logicielles réactives reposent sur une communication asynchrone pilotée par message et ont une nature hautement Responsive , Resilient et Elastic .

Par architecture pilotée par microservices, nous voulions diviser le système en frontières entre services collaboratifs afin d’atteindre les objectifs d’Isolation , Autonomie , Single Responsibility , Mobility__, etc. Pour en savoir plus sur ces deux concepts, consultez Manifeste réactif et Architecture de microservices réactifs .

2. Pourquoi Lagom?

Lagom est un framework open source construit dans le souci de passer d’une architecture d’application pilotée par microservices à des monolithes. Il résume la complexité de la création, de l’exécution et de la surveillance d’applications pilotées par microservices.

Dans les coulisses, le framework Lagom utilise le Play Framework , un Akka runtime piloté par message, Kafka pour les services de découplage, Event Sourcing , et CQRS , et https://conductr .lightbend.com/[ConductR]prend en charge la surveillance et la mise à l’échelle de microservices dans l’environnement de conteneur.

** 3. Bonjour tout le monde à Lagom

**

Nous allons créer une application Lagom pour gérer une demande de bienvenue de l’utilisateur et y répondre avec un message de bienvenue accompagné des statistiques météo du jour.

Et nous allons développer deux microservices distincts: Greeting et Weather.

Greeting se concentrera sur le traitement d’une demande de message d’accueil, interagissant avec le service météo pour répondre à l’utilisateur. Le microservice Weather répondra à la demande de statistiques météorologiques pour aujourd’hui.

Dans le cas où l’utilisateur existant interagit avec Greeting microservice, les différents messages d’accueil seront présentés à l’utilisateur.

3.1. Conditions préalables

  1. Installez Scala (nous utilisons actuellement la version 2.11.8) à partir de

here . Installer l’outil de compilation sbt (nous utilisons actuellement la version 0.13.11) à partir de

** 4. Configuration du projet

**

Voyons maintenant les étapes à suivre pour configurer un système Lagom en état de marche.

4.1. SBT Build

Créez un dossier de projet lagom-hello-world suivi du fichier de construction build.sbt . Un système Lagom est généralement constitué d’un ensemble de builds sbt , chaque build correspondant à un groupe de services associés:

organization in ThisBuild := "org.baeldung"

scalaVersion in ThisBuild := "2.11.8"

lagomKafkaEnabled in ThisBuild := false

lazy val greetingApi = project("greeting-api")
  .settings(
    version := "1.0-SNAPSHOT",
    libraryDependencies ++= Seq(
      lagomJavadslApi
    )
  )

lazy val greetingImpl = project("greeting-impl")
  .enablePlugins(LagomJava)
  .settings(
    version := "1.0-SNAPSHOT",
    libraryDependencies ++= Seq(
      lagomJavadslPersistenceCassandra
    )
  )
  .dependsOn(greetingApi, weatherApi)

lazy val weatherApi = project("weather-api")
  .settings(
    version := "1.0-SNAPSHOT",
    libraryDependencies ++= Seq(
      lagomJavadslApi
    )
  )

lazy val weatherImpl = project("weather-impl")
  .enablePlugins(LagomJava)
  .settings(
    version := "1.0-SNAPSHOT"
  )
  .dependsOn(weatherApi)

def project(id: String) = Project(id, base = file(id))

Pour commencer, nous avons spécifié les détails de l’organisation, la version scala et désactivé Kafka pour le projet en cours. Lagom suit une convention de deux projets distincts pour chaque microservice : un projet API et un projet de mise en œuvre.

Le projet API contient l’interface de service dont dépend l’implémentation.

Nous avons ajouté des dépendances aux modules Lagom pertinents tels que lagomJavadslApi , lagomJavadslPersistenceCassandra pour utiliser l’API Java de Lagom dans nos microservices et stocker les événements liés à l’entité persistante dans __Cassandra, respectivement.

De plus, le projet greeting-impl dépend du projet weather-api pour extraire et fournir les statistiques météorologiques tout en saluant un utilisateur.

Le support pour le plugin Lagom est ajouté en créant un dossier de plugin avec le fichier plugins.sbt , ayant une entrée pour le plugin Lagom. Il fournit tout le support nécessaire à la création, à l’exécution et au déploiement de notre application.

De plus, le plugin sbteclipse sera utile si nous utilisons Eclipse IDE pour ce projet. Le code ci-dessous montre le contenu des deux plugins:

addSbtPlugin("com.lightbend.lagom" % "lagom-sbt-plugin" % "1.3.1")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "3.0.0")

Créez le fichier project/build.properties et spécifiez sbt version à utiliser:

sbt.version=0.13.11

** 4.2. Génération de projet

**

L’exécution de la commande sbt à partir de la racine du projet générera les modèles de projet suivants:

  1. salut-api

  2. salutation-impl

  3. météo api

  4. météo-impl

Avant de commencer à implémenter les microservices, ajoutons les dossiers src/main/java et src/main/java/resources à l’intérieur de chacun des projets, afin de suivre la disposition du répertoire de projet de type Maven.

De plus, deux projets dynamiques sont générés dans project-root/target/lagom-dynamic-projects :

  1. lagom-internal-meta-project-cassandra

  2. lagom-internal-meta-project-service-locator

Ces projets sont utilisés en interne par Lagom.

** 5. Interface de service

**

Dans le projet greeting-api , nous spécifions l’interface suivante:

public interface GreetingService extends Service {

    public ServiceCall<NotUsed, String> handleGreetFrom(String user);

    @Override
    default Descriptor descriptor() {
        return named("greetingservice")
          .withCalls(restCall(Method.GET, "/api/greeting/:fromUser",
            this::handleGreetFrom))
          .withAutoAcl(true);
    }
}

GreetingService expose handleGreetFrom () pour gérer la demande de message d’accueil de l’utilisateur. Une API ServiceCall est utilisée comme type de retour de ces méthodes. ServiceCall prend deux paramètres de type Request et Response .

Le paramètre Request est le type du message de demande entrant et le paramètre Response est le type du message de réponse sortant.

Dans l’exemple ci-dessus, nous n’utilisons pas de charge de requête, le type de requête est NotUsed et le type Response est un message d’accueil String .

GreetingService spécifie également un mappage sur le transport réel utilisé lors de l’appel, en fournissant une implémentation par défaut de la méthode Service.descriptor () . Un service nommé greetingservice est renvoyé.

L’appel de service handleGreetFrom () est mappé à l’aide d’un identifiant Rest:

GET type de méthode et identificateur de chemin /api/greeting/: fromUser mappé sur handleGreetFrom () méthode. Consultez la page https://www.lagomframework.com/documentation/1.3.x/java/ServiceDescriptors.html pour plus de détails sur les identificateurs de service.

Sur les mêmes lignes, nous définissons l’interface WeatherService dans le projet weather-api . Les méthodes weatherStatsForToday () et descriptor () sont assez explicites:

public interface WeatherService extends Service {

    public ServiceCall<NotUsed, WeatherStats> weatherStatsForToday();

    @Override
    default Descriptor descriptor() {
        return named("weatherservice")
          .withCalls(
            restCall(Method.GET, "/api/weather",
              this::weatherStatsForToday))
          .withAutoAcl(true);
    }
};

WeatherStats est défini comme une énumération avec des exemples de valeurs pour différentes conditions météorologiques et une recherche aléatoire pour renvoyer les prévisions météorologiques du jour

public enum WeatherStats {

    STATS__RAINY("Going to Rain, Take Umbrella"),
    STATS__HUMID("Going to be very humid, Take Water");

    public static WeatherStats forToday() {
        return VALUES.get(RANDOM.nextInt(SIZE));
    }
}

6. Lagom Persistence - Détection d’événements

En un mot, dans un système utilisant Event Sourcing , nous pourrons capturer toutes les modifications sous forme d’événements de domaine immuables ajoutés les uns après les autres. L’état actuel est dérivé de la relecture et du traitement d’événements. Cette opération est essentiellement une opération foldLeft connue dans le paradigme de la programmation fonctionnelle.

L’approvisionnement en événements permet d’obtenir des performances d’écriture élevées en ajoutant les événements et en évitant les mises à jour et les suppressions d’événements existants.

Regardons maintenant notre entité persistante dans le projet greeting-impl, GreetingEntity :

public class GreetingEntity extends
  PersistentEntity<GreetingCommand, GreetingEvent, GreetingState> {

      @Override
      public Behavior initialBehavior(
        Optional<GreetingState> snapshotState) {
            BehaviorBuilder b
              = newBehaviorBuilder(new GreetingState("Hello "));

            b.setCommandHandler(
              ReceivedGreetingCommand.class,
              (cmd, ctx) -> {
                  String fromUser = cmd.getFromUser();
                  String currentGreeting = state().getMessage();
                  return ctx.thenPersist(
                    new ReceivedGreetingEvent(fromUser),
                    evt -> ctx.reply(
                      currentGreeting + fromUser + "!"));
              });

            b.setEventHandler(
              ReceivedGreetingEvent.class,
              evt -> state().withMessage("Hello Again "));

            return b.build();
      }
}

Lagom fournit PersistentEntity <Commande, entité, événement> API de traitement les événements de type Command via les méthodes setCommandHandler () et l’état persistant changent en tant qu’événements de type Event . L’état de l’objet de domaine est mis à jour en appliquant l’événement à l’état actuel à l’aide de la méthode setEventHandler () . La méthode abstractBehavior () abstract définit le comportement de l’entité.

Dans initialBehavior () , nous construisons le texte original GreetingState «Hello».

Ensuite, nous pouvons définir un gestionnaire de commandes ReceivedGreetingCommand - qui produit un événement ReceivedGreetingEvent et est maintenu dans le journal des événements.

GreetingState est recalculé sur «Hello Again» par la méthode du gestionnaire d’événements ReceivedGreetingEvent . Comme mentionné précédemment, nous n’invoquons pas de paramètres, mais nous créons une nouvelle instance de State à partir de l’événement en cours de traitement .

Lagom suit la convention des interfaces GreetingCommand et GreetingEvent pour contenir ensemble tous les événements et commandes supportés:

public interface GreetingCommand extends Jsonable {

    @JsonDeserialize
    public class ReceivedGreetingCommand implements
      GreetingCommand,
      CompressedJsonable,
      PersistentEntity.ReplyType<String> {
          @JsonCreator
          public ReceivedGreetingCommand(String fromUser) {
              this.fromUser = Preconditions.checkNotNull(
                fromUser, "fromUser");
          }
    }
}
public interface GreetingEvent extends Jsonable {
    class ReceivedGreetingEvent implements GreetingEvent {

        @JsonCreator
        public ReceivedGreetingEvent(String fromUser) {
            this.fromUser = fromUser;
        }
    }
}

** 7. Mise en service

**

7.1. Service de voeux

public class GreetingServiceImpl implements GreetingService {

    @Inject
    public GreetingServiceImpl(
      PersistentEntityRegistry persistentEntityRegistry,
      WeatherService weatherService) {
          this.persistentEntityRegistry = persistentEntityRegistry;
          this.weatherService = weatherService;
          persistentEntityRegistry.register(GreetingEntity.class);
      }

    @Override
    public ServiceCall<NotUsed, String> handleGreetFrom(String user) {
        return request -> {
            PersistentEntityRef<GreetingCommand> ref
              = persistentEntityRegistry.refFor(
                GreetingEntity.class, user);
            CompletableFuture<String> greetingResponse
              = ref.ask(new ReceivedGreetingCommand(user))
                .toCompletableFuture();
            CompletableFuture<WeatherStats> todaysWeatherInfo
              = (CompletableFuture<WeatherStats>) weatherService
                .weatherStatsForToday().invoke();

            try {
                return CompletableFuture.completedFuture(
                  greetingResponse.get() + " Today's weather stats: "
                    + todaysWeatherInfo.get().getMessage());
            } catch (InterruptedException | ExecutionException e) {
                return CompletableFuture.completedFuture(
                  "Sorry Some Error at our end, working on it");
            }
        };
    }
}

En termes simples, nous injectons les dépendances PersistentEntityRegistry et WeatherService en utilisant @ Inject (fourni par Guice framework), et nous enregistrons la persistante GreetingEntity .

L’implémentation handleGreetFrom () envoie ReceivedGreetingCommand à GreetingEntity pour traiter et renvoyer la chaîne de vœux de manière asynchrone à l’aide de CompletableFuture implémentation de CompletionStage API.

De même, nous faisons un appel asynchrone à Weather microservice pour obtenir les statistiques météorologiques du jour.

Enfin, nous concaténons les deux sorties et renvoyons le résultat final à l’utilisateur.

Pour enregistrer une implémentation de l’interface de descripteur de service GreetingService auprès de Lagom, créons la classe GreetingServiceModule qui étend AbstractModule et implémente ServiceGuiceSupport :

public class GreetingServiceModule extends AbstractModule
  implements ServiceGuiceSupport {

      @Override
      protected void configure() {
          bindServices(
            serviceBinding(GreetingService.class, GreetingServiceImpl.class));
          bindClient(WeatherService.class);
    }
}

De plus, Lagom utilise en interne le Play Framework. Nous pouvons donc ajouter notre module à la liste des modules activés de Play dans le fichier src/main/resources/application.conf :

play.modules.enabled
  += org.baeldung.lagom.helloworld.greeting.impl.GreetingServiceModule

7.2. Service Météo

Après avoir examiné le GreetingServiceImpl , le WeatherServiceImpl est plutôt simple et explicite:

public class WeatherServiceImpl implements WeatherService {

    @Override
    public ServiceCall<NotUsed, WeatherStats> weatherStatsForToday() {
        return req ->
          CompletableFuture.completedFuture(WeatherStats.forToday());
    }
}

Nous suivons les mêmes étapes que ci-dessus pour le module d’accueil afin d’enregistrer le module météo auprès de Lagom:

public class WeatherServiceModule
  extends AbstractModule
  implements ServiceGuiceSupport {

      @Override
      protected void configure() {
          bindServices(serviceBinding(
            WeatherService.class,
            WeatherServiceImpl.class));
      }
}

Enregistrez également le module météo dans la liste des modules activés de Play Framework:

play.modules.enabled
  += org.baeldung.lagom.helloworld.weather.impl.WeatherServiceModule

** 8. Lancer le projet

**

Lagom permet d’exécuter n’importe quel nombre de services avec une seule commande .

Nous pouvons commencer notre projet en appuyant sur la commande ci-dessous:

sbt lagom:runAll

Ceci démarrera le Service Locator incorporé, le Cassandra incorporé, puis démarrera les microservices en parallèle. La même commande recharge également notre microservice individuel lorsque le code est modifié, de sorte que nous ne devons pas les redémarrer manuellement .

Nous pouvons nous concentrer sur notre logique et Lagom se charge de la compilation et du rechargement. Une fois démarré avec succès, nous verrons le résultat suivant:

................[info]Cassandra server running at 127.0.0.1:4000[info]Service locator is running at http://localhost:8000[info]Service gateway is running at http://localhost:9000[info]Service weather-impl listening for HTTP on 0:0:0:0:0:0:0:0:56231 and how the services interact via[info]Service greeting-impl listening for HTTP on 0:0:0:0:0:0:0:0:49356[info](Services started, press enter to stop and go back to the console...)

Une fois démarré avec succès, nous pouvons faire une demande de curl pour les voeux:

curl http://localhost:9000/api/greeting/Amit

Nous verrons la sortie suivante sur la console:

Hello Amit! Today's weather stats: Going to Rain, Take Umbrella

L’exécution de la même requête curl pour un utilisateur existant modifiera le message d’accueil:

Hello Again Amit! Today's weather stats: Going to Rain, Take Umbrella

** 9. Conclusion

**

Dans cet article, nous avons expliqué comment utiliser le framework Lagom pour créer deux micro-services qui interagissent de manière asynchrone.

Le code source complet et tous les extraits de code de cet article sont disponibles à l’adresse dans le projet GitHub .