Blog

Das vollständige Bild: Lagom und Play in Aktion (Java)

Cristiana

Aktualisiert Oktober 21, 2025
7 Minuten

Wenn Sie schon einmal von Lightbend gehört haben, der treibenden Kraft hinter Scala und Akka, haben Sie vielleicht auch schon von Lagom und Play gehört.

Lagom und Play fallen in die Kategorie der reaktiven Microservices. Ich werde auf nicht ins Detail gehen, denn es gibt zahlreiche Ressourcen (z.B. hier und auf einer breiteren Perspektive, hier). Die wichtigste Erkenntnis ist, dass Sie mit Lagom eine MicroserviceArchitektur aufbauen können. Play ergänzt Lagom, da es das Web-Framework bereitstellt, das dem Benutzer mit Hilfe der Microservices von Lagom im Hintergrund Inhalte bereitstellt.

Was mich allerdings verwundert, ist, dass Play und Lagom isoliert behandelt werden, wenn nach Informationen sucht oder die Tutorials durchgeht (zumindest ist das meine Erfahrung).

Wie können Sie Lagom und Play kombinieren, um ein komplettes System einzurichten?

Es gibt zwar einige Beispiele, wie die Online-Auktion oder Chirper, aber die sind bereits archiviert worden. Das stellt (fortgeschrittene) Anfänger vor eine Herausforderung: Wie kann ich die beweglichen Teile kombinieren, um ein komplettes System aufzubauen? Wenn Sie die anfänglichen Anleitungen von Lagom und Play, einige zufällige Tutorials und Beispiele durchgearbeitet haben, erreichen Sie schließlich die magische Lücke zwischen "Ich kenne die Grundlagen" und "Ich weiß genug, um etwas Sinnvolles damit anzufangen".

Lagom und Play machen es einem nicht leicht, einen Fuß auf den Boden zu bekommen, dieser Beitrag soll helfen. Aber das Wichtigste zuerst: Was möchte ich am Ende haben? Ein minimales Beispiel könnte so aussehen (Microservices sind mit "μs" gekennzeichnet):

Dem Benutzer wird eine einseitige Anwendung (Web-Framework/UI im Diagramm) z.B. mit React angeboten. Das Web-Framework stellt eine Verbindung zu einem Frontend-Gateway (einer Play-App) her, das Aufrufe an die Lagom-basierten Microservices verarbeitet.

Ein Frontend-Gateway bringt zusätzliche Vorteile mit sich:

  1. können Sie Ihre Single-Page-App (SPA) oder statische Ressourcen mit dieser Einrichtung bedienen
  2. Die APIs von Microservices können geändert werden, ohne dass die API, die von der SPA verwendet wird, angepasst werden muss.
  3. Gateways können Ihre Dienste von der "Außenwelt" abschirmen
  4. können Sie transparent zusätzliche Funktionen hinzufügen, wie z.B. Caching oder Authentifizierung

Weitere Informationen hierzu finden Sie unter microservices.io.

Sie können die Architektur so komplex gestalten, wie Sie wollen. Eine komplexere oder anspruchsvollere Architektur könnte wie folgt aussehen (Microservices sind mit "μs" gekennzeichnet):

Die Client-Seite bleibt gleich, aber die Server-Seite hat jetzt einen Reverse-Proxy, z.B. haproxy, einen Caching-Proxy, z.B. nginx, einen node.js-Server mit z.B. Express, Koa oder hapi, um das serverseitige Rendering zu ermöglichen. Sie könnten mehrere Frontend-Gateways verwenden, um Anliegen aufzuteilen und Microservices zu gruppieren. Dieses Setup ist für die meisten Zwecke zu anspruchsvoll, mit der Minimalarchitektur können Sie die meisten Anwendungsfälle bereits abdecken.

Gehen wir also näher auf das Minimalbeispiel ein.

Erste Schritte mit Lagom Microservices

Bevor wir beginnen, gehe ich davon aus, dass Sie sbt bereits installiert haben. Falls nicht, gibt es zahlreiche Tutorials dazu, Google ist Ihr bester Freund! Der Start mit Lagom ist recht einfach. Verwenden Sie einfach die giter8-Vorlage, um ein neues Projekt zu erstellen (ich habe mein Projekt hello-world genannt, die Vorgabe):

sbt new lagom/lagom-java.g8
cd hello-world
sbt runAll

Sie können es mit curl oder, meinem bevorzugten Tool, httpie testen:

curl https://localhost:9000/api/hello/lagom
http :9000/api/hello/lagom

führt zu

HTTP/1.1 200 OK
Hello, lagom!

So weit, so gut. Hello World läuft! Zeit, unser Projekt zu spielen.

Hinzufügen des Play Frontend Gateway

Dieses Snippet wird Ihnen bei Play angezeigt:

cd hello-world
sbt new playframework/play-java-seed.g8

Standardmäßig verwenden die Play-Anwendungen das Layout der Play-Anwendung . Lagom verwendet das sbt-Layout, also müssen wir die Play-Anwendung an das sbt-Layout anpassen. Man würde erwarten, dass dieses Layout bereits als giter8-seed verfügbar ist, aber ich konnte nichts gut Gepflegtes finden. Um es in sbt-layout zu bekommen (auf die harte Tour), müssen Sie einige Dinge umstellen (ich habe mein Play-Projekt frontend genannt):

cd hello-world/frontend
mkdir -p src/main/{java,twirl}
mkdir -p src/test/java
mv app/controllers src/main/java
mv app/views src/main/twirl
mv conf src/main/resources
mv public src/main/
mv test/controllers src/test/java
rm -r app
rm -r test

Sie müssen noch die sbt-Einstellungen (build.sbt und plugins.sbt) des Lagom-Projekts anpassen:

cd hello-world
cat frontend/project/plugins.sbt >>project/plugins.sbt
rm -r frontend/build.sbt frontend/project

Fügen Sie dies zu Ihrem build.sbt hinzu:

lazy val frontend = (project in file("frontend"))
  .settings(common)
  .enablePlugins(PlayJava, LagomPlay)
  .disablePlugins(PlayLayoutPlugin)
  .settings(
    libraryDependencies ++= Seq(
      lagomJavadslClient,
      guice
    )
  )
  .dependsOn(`hello-world-api`)

Außerdem müssen Sie das Frontend-Projekt zum Stamm-Projekt des Aggregats hinzufügen:

lazy val `hello-world` = (project in file("."))
  .aggregate(`hello-world-api`, `hello-world-impl`, `hello-world-stream-api`, `hello-world-stream-impl`, `frontend`)

Das sollte genügen. Versuchen wir es

cd hello-world
sbt runAll

Bei diesem Schritt stoßen die meisten Leute auf eine Art Fehler bei der Injektion von Abhängigkeiten. Er wird in den Tutorials für Lagom oder Play nicht sehr gut erklärt und der Fehler selbst ist auch nicht sehr klar:

com.google.inject.CreationException: Unable to create injector, see the following errors:
1) Could not find a suitable constructor in com.lightbend.lagom.javadsl.api.ServiceInfo. Classes must have
either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.
  at com.lightbend.lagom.javadsl.api.ServiceInfo.class(ServiceInfo.java:47)
  while locating com.lightbend.lagom.javadsl.api.ServiceInfo
    for the 3rd parameter of com.lightbend.lagom.internal.javadsl.client.JavadslServiceClientImplementor.
    <init>(JavadslServiceClientImplementor.scala:46)
  at com.lightbend.lagom.internal.javadsl.client.ServiceClientModule.bindings(ServiceClientModule.scala:15):
Binding(class com.lightbend.lagom.internal.javadsl.client.JavadslServiceClientImplementor to self) (via modules:
com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4)
2) Could not find a suitable constructor in com.lightbend.lagom.javadsl.api.ServiceInfo. Classes must have either
one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.
  at com.lightbend.lagom.javadsl.api.ServiceInfo.class(ServiceInfo.java:47)
  while locating com.lightbend.lagom.javadsl.api.ServiceInfo
    for the 2nd parameter of com.lightbend.lagom.play.PlayRegisterWithServiceRegistry.<init>(LagomPlayModule.scala:87)
  at com.lightbend.lagom.play.LagomPlayModule.bindings(LagomPlayModule.scala:35):
Binding(class com.lightbend.lagom.play.PlayRegisterWithServiceRegistry to self eagerly) (via modules: com.google.
inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$4)
2 errors
  at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:543)
  at com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:159)
  at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:106)
  ...

Diese Fehlermeldung bedeutet: Wir müssen hello-world/frontend/src/main/java/Module.java erstellen und die Metadaten des Dienstes binden:

import com.google.inject.AbstractModule;
import com.lightbend.lagom.javadsl.api.ServiceAcl;
import com.lightbend.lagom.javadsl.api.ServiceInfo;
import com.lightbend.lagom.javadsl.client.ServiceClientGuiceSupport;
public class Module extends AbstractModule implements ServiceClientGuiceSupport {
    @Override
    protected void configure() {
        bindServiceInfo(ServiceInfo.of( "frontend", ServiceAcl.path("(?!/api/).*") ));
    }
}

Der Dienst frontend bindet alle Pfade mit Ausnahme der Pfade /api/... (die von den lagom Microservices verwendet werden). Lassen Sie es uns noch einmal versuchen:

sbt runAll

Besser! Es ist an der Zeit, dem Play-Frontend eine Route hinzuzufügen, die das Lagom-Backend aufruft.

Wir müssen die HelloWorldService binden, was zu folgender frontend/src/main/java/Module.java führt:

import com.example.helloworld.api.HelloWorldService;
import com.google.inject.AbstractModule;
import com.lightbend.lagom.javadsl.api.ServiceAcl;
import com.lightbend.lagom.javadsl.api.ServiceInfo;
import com.lightbend.lagom.javadsl.client.ServiceClientGuiceSupport;
public class Module extends AbstractModule implements ServiceClientGuiceSupport {
    @Override
    protected void configure() {
        bindServiceInfo(ServiceInfo.of( "frontend", ServiceAcl.path("(?!/api/).*") ));
        bindClient(HelloWorldService.class);
    }
}

Fügen Sie den Dienst zum Controller hinzu (frontend/src/main/java/controllers/HomeController.java):

package controllers;
import com.example.helloworld.api.HelloWorldService;
import com.google.inject.Inject;
import play.mvc.*;
import java.util.concurrent.CompletionStage;
/**
 * This controller contains an action to handle HTTP requests
 * to the application's home page.
 */
public class HomeController extends Controller {
    private HelloWorldService helloWorldService;
    @Inject
    public HomeController(HelloWorldService helloWorldService) {
        this.helloWorldService = helloWorldService;
    }
    /**
     * An action that renders an HTML page with a welcome message.
     * The configuration in the <code>routes</code> file means that
     * this method will be called when the application receives a
     * <code>GET</code> request with a path of <code>/</code>.
     */
    public Result index() {
        return ok(views.html.index.render());
    }
    public CompletionStage<Result> hello(String id) {
        CompletionStage<String> message = helloWorldService.hello(id).invoke();
        return message.thenApply(s -> ok(s + " (Proxied by frontend)" ));
    }
}

Und zu guter Letzt fügen Sie eine Route in frontend/src/main/resources/routes hinzu:

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# An example controller showing a sample home page
GET     /                           controllers.HomeController.index
GET     /hello/:id                  controllers.HomeController.hello(id: String)
# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

Wir können jetzt die HelloWorldService direkt aufrufen:

$ http :9000/api/hello/lagom
HTTP/1.1 200 OK
Hello, lagom!

Oder über das Frontend:

$ http :9000/hello/lagom
HTTP/1.1 200 OK
Hello, lagom! (Proxied by frontend)

Ja!

Fazit

In diesem Beitrag finden Sie ein funktionierendes Beispiel für Lagom und Play in Aktion.

Im Zusammenhang mit Microservices werden die Controller im Frontend-Gateway verwendet, um eine übergreifende Logik zu haben, die mehrere Microservices überspannt oder kombiniert. Ein Beispiel könnte eine Einrichtung mit zweiMicroservices sein, einem und einem . Mit einem Play-Controller können beide Services zu einem CheckoutGateway kombiniert werden. Die CheckoutGateway könnte Routen wie POST /payment bereitstellen, die die Einkaufstasche schließt und eine Bestellung erstellt, so dass es möglich ist, die Grenzen des Microservice zu "überschreiten".

Viel Spaß!

(Sie können diesen Beitrag auch in meinem persönlichen Blog finden)

Verfasst von

Cristiana

Some bio goes here

Contact

Let’s discuss how we can support your journey.