Leitfaden für reaktive Microservices mit Lagom Framework

1. Überblick

In diesem Artikel werden wir das Lagom-Framework untersuchen und eine Beispielanwendung ** mit einer reaktiven, auf Mikroservices basierenden Architektur implementieren.

Einfach ausgedrückt, reaktive Softwareanwendungen basieren auf einer nachrichtengesteuerten asynchronen Kommunikation und sind in der Natur sehr Responsive , Resilient und Elastic .

Unter einer Microservice-gesteuerten Architektur meinten wir, das System in Grenzen zwischen kollaborativen Diensten aufzuteilen, um die Ziele Isolation , Autonomy , Single Responsibility , Mobility usw. zu erreichen. Weitere Informationen zu diesen beiden Konzepten finden Sie unter Das reaktive Manifest und Reactive Microservices Architecture .

2. Warum Lagom?

Lagom ist ein Open-Source-Framework, das vor allem von Monolithen zu einer auf Mikroservices basierenden Anwendungsarchitektur gedacht ist. Es abstrahiert die Komplexität beim Erstellen, Ausführen und Überwachen von Mikrodienst-gesteuerten Anwendungen.

Hinter den Kulissen verwendet Lagom Framework die Play Framework , eine Akka -Nachrichten-gesteuerte Laufzeit, Kafka für Entkopplungsdienste, Event Sourcing und CQRS -Muster und https://conductr .lightbend.com/[ConductR]Unterstützung für die Überwachung und Skalierung von Mikrodiensten in der Containerumgebung.

** 3. Hallo Welt in Lagom

**

Wir erstellen eine Lagom-Anwendung, um eine Grußanforderung des Benutzers zu bearbeiten und mit einer Grußmeldung sowie Wetterstatistiken für den Tag zu antworten.

Und wir werden zwei separate Microservices entwickeln: Greeting und Weather.

Greeting konzentriert sich auf die Bearbeitung einer Grußanforderung und Interaktion mit dem Wetterdienst, um dem Benutzer zu antworten. Der Weather Microservice wird die Anfrage nach Wetterstatistiken für heute abwickeln.

Wenn ein vorhandener Benutzer mit Greeting microservice interagiert, wird dem Benutzer die unterschiedliche Begrüßungsnachricht angezeigt.

3.1. Voraussetzungen

  1. Installieren Sie Scala (wir verwenden derzeit die Version 2.11.8) von

hier . Installieren Sie das sbt build-Tool (wir verwenden derzeit 0.13.11) von

** 4. Projektaufbau

**

Lassen Sie uns nun einen kurzen Blick auf die Schritte zum Einrichten eines funktionierenden Lagom-Systems werfen.

4.1. SBT Build

Erstellen Sie einen Projektordner lagom-hello-world , gefolgt von der Erstellungsdatei build.sbt . Ein Lagom-System besteht normalerweise aus einer Menge von sbt -Builds, wobei jeder Build einer Gruppe verwandter Dienste entspricht:

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))

Zu Beginn haben wir die Organisationsdetails, die scala -Version und das deaktivierte Kafka für das aktuelle Projekt angegeben. Lagom folgt einer Konvention aus zwei separaten Projekten für jeden Microservice : API-Projekt und ein Implementierungsprojekt.

Das API-Projekt enthält die Service-Schnittstelle, von der die Implementierung abhängt.

Wir haben den relevanten Lagom-Modulen, wie lagomJavadslApi , lagomJavadslPersistenceCassandra , Abhängigkeiten für die Verwendung der Lagom-Java-API in unseren Mikrodiensten bzw. das Speichern von Ereignissen in __Cassandra hinzugefügt.

Das greeting-impl -Projekt hängt auch davon ab, dass das weather-api -Projekt Wetterdaten abruft und bereitstellt, während er einen Benutzer begrüßt.

Unterstützung für das Lagom-Plugin wird hinzugefügt, indem ein Plugin-Ordner mit der Datei ** plugins.sbt erstellt wird, die einen Eintrag für das Lagom-Plugin enthält. Es bietet alle erforderliche Unterstützung für das Erstellen, Ausführen und Bereitstellen unserer Anwendung.

Das sbteclipse -Plugin ist auch hilfreich, wenn wir für dieses Projekt die Eclipse-IDE verwenden. Der folgende Code zeigt den Inhalt für beide Plugins:

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

Erstellen Sie eine project/build.properties -Datei und geben Sie die zu verwendende sbt -Version an:

sbt.version=0.13.11

** 4.2. Projektgenerierung

**

Durch Ausführen des Befehls sbt vom Projektstamm aus werden die folgenden Projektvorlagen generiert:

  1. Gruß-api

  2. Gruß-Impl

  3. Wetter-api

  4. weather-impl

Bevor wir mit der Implementierung der Microservices beginnen, fügen wir die Ordner src/main/java und src/main/java/resources in jedem der Projekte hinzu, um dem Layout des Maven-ähnlichen Projektverzeichnisses zu folgen.

In project-root/target/lagom-dynamic-projects werden außerdem zwei dynamische Projekte generiert:

  1. lagom-internal-meta-project-cassandra

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

Diese Projekte werden von Lagom intern verwendet.

** 5. Service-Schnittstelle

**

Im greeting-api -Projekt geben wir die folgende Schnittstelle an:

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 macht handleGreetFrom () für die Bearbeitung der Grußanforderung des Benutzers verfügbar. Als Rückgabetyp dieser Methoden wird eine ServiceCall -API verwendet. ServiceCall akzeptiert zwei Typparameter Request und Response .

Der Parameter Request ist der Typ der eingehenden Anforderungsnachricht und der Response -Parameter ist der Typ der ausgehenden Antwortnachricht.

Im obigen Beispiel verwenden wir keine Anforderungsnutzdaten, der Anfragetyp ist NotUsed , und der Typ Response ist eine Begrüßungsnachricht String .

GreetingService gibt auch eine Zuordnung zu dem tatsächlichen Transport an, der während des Aufrufs verwendet wird, indem eine Standardimplementierung der Methode Service.descriptor () bereitgestellt wird. Ein Dienst mit dem Namen greetingservice wird zurückgegeben.

handleGreetFrom () Dienstaufruf wird mit einer Rest-Kennung zugeordnet:

GET Methodentyp und Pfadkennung /api/greeting/: fromUser der handleGreetFrom () Methode zugeordnet. Weitere Informationen zu Service-IDs finden Sie unter https://www.lagomframework.com/documentation/1.3.x/java/ServiceDescriptors.html (Link zu diesem Link).

In derselben Zeile definieren wir das WeatherService -Interface im weather-api -Projekt. Die Methode weatherStatsForToday () und die Methode descriptor () sind ziemlich selbsterklärend:

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 ist als Aufzählung mit Beispielwerten für verschiedene Wetterbedingungen und Zufallszahlen definiert, um die Wettervorhersage für den Tag anzuzeigen:

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 - Event Sourcing

Einfach ausgedrückt: In einem System, das Event Sourcing verwendet, können wir alle Änderungen als unveränderliche Domänenereignisse erfassen, die nacheinander angefügt werden. Der aktuelle Status wird durch Wiedergabe und Verarbeitung von Ereignissen abgeleitet. Diese Operation ist im Wesentlichen eine aus dem Functional Programming-Paradigma bekannte Operation foldLeft .

Event Sourcing hilft, eine hohe Schreibleistung zu erzielen, indem die Ereignisse angehängt werden und Aktualisierungen und Löschungen vorhandener Ereignisse vermieden werden.

Betrachten wir nun unsere beständige Entität im Greeting-Impl-Projekt 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 bietet PersistentEntity <Befehl, Entität, Ereignis> API zur Verarbeitung eingehender Daten Ereignisse vom Typ Command über setCommandHandler () -Methoden und persistente Statusänderungen als Ereignisse vom Typ Event . Der Status des Domänenobjekts wird aktualisiert, indem das Ereignis mit der Methode setEventHandler () auf den aktuellen Status angewendet wird. Die abstrakte Methode initialBehavior () definiert das Behavior der Entität.

In initialBehavior () erstellen wir den ursprünglichen GreetingState -Text "Hello".

Dann können wir einen ReceivedGreetingCommand -Befehlshandler definieren, der ein ReceivedGreetingEvent -Ereignis erzeugt und im Ereignisprotokoll gespeichert bleibt.

GreetingState wird von der ReceivedGreetingEvent -Ereignishandlermethode zu "Hello Again" neu berechnet. ** Wie bereits erwähnt, rufen wir keine Setter auf. Stattdessen erstellen wir eine neue Instanz von State aus dem aktuellen Ereignis, das gerade verarbeitet wird.

Lagom folgt der Konvention der Schnittstellen GreetingCommand und GreetingEvent , um alle unterstützten Befehle und Ereignisse zusammenzuhalten:

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. Service-Implementierung

**

7.1. Begrüßungsdienst

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");
            }
        };
    }
}

Einfach ausgedrückt, wir injizieren die PersistentEntityRegistry - und WeatherService -Abhängigkeiten mit @ Inject (bereitgestellt vom Guice -Framework) und registrieren das persistente GreetingEntity .

Die handleGreetFrom () -Implementierung sendet ReceivedGreetingCommand an GreetingEntity , um die Begrüßungszeichenfolge asynchron mit der CompletableFuture -Implementierung der CompletionStage -API zu verarbeiten und zurückzugeben.

In ähnlicher Weise rufen wir den Weather Microservice async auf, um Wetterstatistiken für heute abzurufen.

Schließlich verketten wir beide Ausgaben und geben das Endergebnis an den Benutzer zurück.

Um eine Implementierung des Service Descriptor Interface GreetingService bei Lagom zu registrieren, erstellen wir die GreetingServiceModule -Klasse, die AbstractModule erweitert und ServiceGuiceSupport implementiert:

public class GreetingServiceModule extends AbstractModule
  implements ServiceGuiceSupport {

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

Lagom verwendet intern auch das Play Framework. So können wir unser Modul zur Play-Liste der aktivierten Module in der Datei src/main/resources/application.conf hinzufügen:

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

7.2. Wetterdienst

Nach dem Betrachten des GreetingServiceImpl ist WeatherServiceImpl ziemlich einfach und selbsterklärend:

public class WeatherServiceImpl implements WeatherService {

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

Wir folgen den gleichen Schritten wie oben für das Begrüßungsmodul, um das Wettermodul bei Lagom zu registrieren:

public class WeatherServiceModule
  extends AbstractModule
  implements ServiceGuiceSupport {

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

Registrieren Sie das Wettermodul auch in der Liste der aktivierten Module von Play:

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

** 8. Projekt ausführen

**

Lagom erlaubt es, eine beliebige Anzahl von Diensten zusammen mit einem einzigen Befehl auszuführen ** .

Wir können unser Projekt mit dem folgenden Befehl starten:

sbt lagom:runAll

Dadurch werden der eingebettete Service Locator , der eingebettete Cassandra und dann die Microservices parallel gestartet. Derselbe Befehl lädt auch unseren individuellen Microservice neu, wenn sich der Code ändert, so dass wir ihn nicht manuell neu starten müssen ** .

Wir können uns auf unsere Logik konzentrieren und Lagom übernimmt das Kompilieren und Nachladen. Nach dem erfolgreichen Start wird folgende Ausgabe angezeigt:

................[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...)

Nach erfolgreichem Start können wir eine Anfrage zur Begrüßung machen:

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

Wir werden folgende Ausgabe auf der Konsole sehen:

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

Wenn Sie dieselbe Curl-Anforderung für einen vorhandenen Benutzer ausführen, wird die Begrüßungsnachricht geändert:

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

** 9. Fazit

**

In diesem Artikel haben wir beschrieben, wie Sie mit dem Lagom-Framework zwei Micro-Services erstellen, die asynchron interagieren.

Der vollständige Quellcode und alle Codeausschnitte für diesen Artikel sind verfügbar s im GitHub-Projekt .