1. Overview
1.概述
In this article, we’ll explore the Lagom framework and implement an example application using a reactive microservices driven architecture.
在本文中,我们将探讨Lagom框架,并使用反应式微服务驱动架构实现一个示例应用程序。
Simply put, reactive software applications rely on message-driven asynchronous communication and are highly Responsive, Resilient and Elastic in nature.
简单地说,反应式软件应用程序依赖于消息驱动的异步通信,具有高度的响应性、复原性和弹性性质。
By microservice-driven architecture, we meant splitting the system into boundaries between collaborative services for achieving goals of Isolation, Autonomy, Single Responsibility, Mobility, etc. For further reading on these two concepts, refer The Reactive Manifesto and Reactive Microservices Architecture.
通过微服务驱动的架构,我们指的是将系统分割成协作服务之间的边界,以实现隔离、自治、单一责任、移动等目标。关于这两个概念的进一步阅读,请参阅反应式宣言和反应式微服务架构。
2. Why Lagom?
2.为什么是Lagom?
Lagom is an open source framework built with the shifting from monoliths to microservices-driven application architecture in mind. It abstracts the complexity of building, running and monitoring microservices driven applications.
Lagom是一个开源的框架,它的构建考虑到了从单体到微服务驱动的应用架构的转变。它将构建、运行和监控微服务驱动的应用程序的复杂性抽象化。
Behind the scenes, Lagom framework uses the Play Framework, an Akka message-driven runtime, Kafka for decoupling services, Event Sourcing, and CQRS patterns, and ConductR support for monitoring and scaling microservices in the container environment.
在幕后,Lagom框架使用Play框架、Akka消息驱动的运行时、Kafka用于解耦服务、Event Sourcing以及CQRS模式,以及用于在容器环境中监控和扩展微服务的ConductR支持。
3. Hello World in Lagom
3.Lagom的Hello World
We’ll be creating a Lagom application to handle a greeting request from the user and reply back with a greeting message along with weather statistics for the day.
我们将创建一个Lagom应用程序来处理来自用户的问候请求,并回复问候信息和当天的天气统计数据。
And we’ll be developing two separate microservices: Greeting and Weather.
而我们将开发两个独立的微服务。问候和天气。
Greeting will focus on handling a greeting request, interacting with weather service to reply back to the user. The Weather microservice will service the request for weather statistics for today.
问候将专注于处理问候请求,与天气服务互动以回复用户。Weather microservice将为今天的天气统计请求提供服务。
In the case of existing user interacting with Greeting microservice, the different greeting message will be shown to the user.
在现有用户与Greeting微服务互动的情况下,不同的问候信息将被显示给用户。
3.1. Prerequisites
3.1.前提是
- Install Scala (we are currently using 2.11.8 version) from here
- Install sbt build tool (we are currently using 0.13.11) from here
4. Project Setup
4.项目设置
Let’s now have a quick look at the steps to set up a working Lagom system.
现在让我们快速了解一下建立一个有效的Lagom系统的步骤。
4.1. SBT Build
4.1.SBT构建
Create a project folder lagom-hello-world followed by the build file build.sbt. A Lagom system is typically made up of a set of sbt builds with each build corresponding to a group of related services:
创建一个项目文件夹lagom-hello-world,然后是构建文件build.sbt。一个Lagom系统通常由一组sbt构建组成,每个构建对应一组相关服务。
organization in ThisBuild := "com.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))
To start with, we’ve specified the organization details, scala version, and disabled Kafka for the current project. Lagom follows a convention of two separate projects for each microservice: API project and an implementation project.
首先,我们指定了组织细节、scala版本,并为当前项目禁用了Kafka。Lagom遵循的惯例是每个微服务有两个独立的项目。API项目和一个实施项目。
The API project contains the service interface on which the implementation depends.
API项目包含实现所依赖的服务接口。
We’ve added dependencies to the relevant Lagom modules like lagomJavadslApi, lagomJavadslPersistenceCassandra for using the Lagom Java API in our microservices and storing events related to the persistent entity in Cassandra, respectively.
我们已经向相关的Lagom模块添加了依赖关系,如lagomJavadslApi、lagomJavadslPersistenceCassandra,以便在我们的微服务中使用Lagom Java API,并在Cassandra中存储与持久化实体有关的事件,/em>。
Also, the greeting-impl project depends on the weather-api project to fetch and serve weather stats while greeting a user.
另外,greeting-impl项目依赖于weather-api项目,以便在问候用户时获取和提供天气统计信息。
Support for the Lagom plugin is added by creating a plugin folder with plugins.sbt file, having an entry for Lagom plugin. It provides all the necessary support for building, running, and deploying our application.
对Lagom插件的支持是通过创建一个带有 plugins.sbt文件的插件文件夹添加的,其中有一个Lagom插件的条目。它为构建、运行和部署我们的应用程序提供了所有必要的支持。
Also, the sbteclipse plugin will be handy if we use Eclipse IDE for this project. The code below shows the contents for both plugins:
另外,如果我们在这个项目中使用Eclipse IDE,sbteclipse插件会很方便。下面的代码显示了这两个插件的内容。
addSbtPlugin("com.lightbend.lagom" % "lagom-sbt-plugin" % "1.3.1")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "3.0.0")
Create project/build.properties file and specify sbt version to use:
创建project/build.properties文件并指定使用sbt版本。
sbt.version=0.13.11
4.2. Project Generation
4.2.项目生成
Running sbt command from the project root will generate the following project templates:
从项目根部运行sbt命令将生成以下项目模板。
- greeting-api
- greeting-impl
- weather-api
- weather-impl
Before we start implementing the microservices, let’s add the src/main/java and src/main/java/resources folders inside each of the projects, to follow Maven-like project directory layout.
在开始实现微服务之前,让我们在每个项目中添加src/main/java和src/main/java/resources文件夹,以遵循Maven的项目目录布局。
Also, two dynamic projects are generated inside project-root/target/lagom-dynamic-projects:
同时,在project-root/target/lagom-dynamic-projects内生成了两个动态项目。
- lagom-internal-meta-project-cassandra
- lagom-internal-meta-project-service-locator
These projects are used internally by Lagom.
这些项目是由Lagom内部使用的。
5. Service Interface
5.服务接口
In the greeting-api project, we specify the following interface:
在greeting-api项目中,我们指定以下接口。
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 exposes handleGreetFrom() to handle greet request from the user. A ServiceCall API is used as the return type of these methods. ServiceCall takes two type parameters Request and Response.
GreetingService暴露了handleGreetFrom()来处理来自用户的问候请求。一个ServiceCall API被用作这些方法的返回类型。ServiceCall需要两个类型参数Request和Response。
The Request parameter is the type of the incoming request message, and the Response parameter is the type of the outgoing response message.
Request参数是传入请求消息的类型,Response参数是传出响应消息的类型。
In the example above, we’re not using request payload, request type is NotUsed, and Response type is a String greeting message.
在上面的例子中,我们没有使用请求有效载荷,请求类型是NotUsed,Response类型是String问候信息。
GreetingService also specifies a mapping to the actual transport used during the invocation, by providing a default implementation of the Service.descriptor() method. A service named greetingservice is returned.
GreetingService还通过提供Service.descriptor()方法的默认实现,指定了与调用期间使用的实际传输的映射。一个名为greetingservice的服务被返回。
handleGreetFrom() service call is mapped using a Rest identifier: GET method type and path identifier /api/greeting/:fromUser mapped to handleGreetFrom() method. Check this link out for more details on service identifiers.
handleGreetFrom()服务调用使用Rest标识符进行映射。GET方法类型和路径标识/api/greeting/:fromUser映射到handleGreetFrom()方法。请查看这个链接,了解关于服务标识符的更多细节。
On the same lines, we define WeatherService interface in the weather-api project. weatherStatsForToday() method and descriptor() method are pretty much self explanatory:
按照同样的思路,我们在weather-api项目中定义WeatherService接口。weatherStatsForToday()方法和descriptor()方法几乎是自我解释的。
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 is defined as an enum with sample values for different weather and random lookup to return weather forecast for the day:
WeatherStats被定义为一个枚举,具有不同天气的样本值和随机查询,以返回当天的天气预报。
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
6.Lagom Persistence – Event Sourcing
Simply put, in a system making use of Event Sourcing, we’ll be able to capture all changes as immutable domain events appended one after the other. The current state is derived by replaying and processing events. This operation is essentially a foldLeft operation known from the Functional Programming paradigm.
简单地说,在一个利用事件源的系统中,我们将能够把所有的变化作为不可改变的领域事件,一个接一个地附加上去。当前状态是通过回放和处理事件而得出的。这种操作本质上是功能编程范式中已知的foldLeft操作。
Event sourcing helps to achieve high write performance by appending the events and avoiding updates and deletes of existing events.
事件源有助于通过追加事件和避免更新和删除现有事件来实现高写入性能。
Let’s now look at our persistent entity in the greeting-impl project, GreetingEntity:
现在让我们看看我们在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 provides PersistentEntity<Command, Entity, Event> API for processing incoming events of type Command via setCommandHandler() methods and persist state changes as events of type Event. The domain object state is updated by applying the event to the current state using the setEventHandler() method. The initialBehavior() abstract method defines the Behavior of the entity.
Lagom提供PersistentEntity<Command, Entity, Event> API,用于通过setCommandHandler()方法处理传入的Command类型的事件,并将状态变化持久化为Event类型的事件。通过使用setEventHandler()方法将事件应用于当前状态,域对象的状态被更新。initialBehavior()抽象方法定义了实体的Behavior。
In initialBehavior(), we build original GreetingState “Hello” text. Then we can define a ReceivedGreetingCommand command handler – which produces a ReceivedGreetingEvent Event and gets persisted in the event log.
在initialBehavior()中,我们建立原始的GreetingState“Hello “文本。然后我们可以定义一个ReceivedGreetingCommand命令处理程序–它产生一个ReceivedGreetingEvent事件并被保存在事件日志中。
GreetingState is recalculated to “Hello Again” by the ReceivedGreetingEvent event handler method. As mentioned earlier, we’re not invoking setters – instead, we are creating a new instance of State from the current event being processed.
GreetingState被ReceivedGreetingEvent事件处理方法重新计算为 “Hello Again”。如前所述,我们没有调用设置器–相反,我们从正在处理的当前事件中创建一个新的State实例。
Lagom follows the convention of GreetingCommand and GreetingEvent interfaces for holding together all the supported commands and events:
Lagom遵循GreetingCommand和GreetingEvent接口的惯例,把所有支持的命令和事件放在一起。
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 Implementation
7.服务的实施
7.1. Greeting Service
7.1.问候服务
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");
}
};
}
}
Simply put, we inject the PersistentEntityRegistry and WeatherService dependencies using @Inject (provided by Guice framework), and we register the persistent GreetingEntity.
简单地说,我们使用@Inject(由Guice框架提供)注入PersistentEntityRegistry和WeatherService依赖关系,然后我们注册持久化的GreetingEntity。
The handleGreetFrom() implementation is sending ReceivedGreetingCommand to the GreetingEntity to process and return greeting string asynchronously using CompletableFuture implementation of CompletionStage API.
handleGreetFrom()实现向GreetingEntity发送ReceivedGreetingCommand,使用CompletionStage API的CompletableFuture实现,异步地处理并返回问候字符串。
Similarly, we make an async call to Weather microservice to fetch weather stats for today.
同样地,我们对Weather微服务进行异步调用,以获取今天的天气统计信息。
Finally, we concatenate both outputs and return the final result to the user.
最后,我们将两个输出连接起来,并将最终结果返回给用户。
To register an implementation of the service descriptor interface GreetingService with Lagom, let’s create GreetingServiceModule class which extends AbstractModule and implements ServiceGuiceSupport:
为了向Lagom注册服务描述符接口GreetingService的实现,让我们创建GreetingServiceModule类,它扩展了AbstractModule并实现了ServiceGuiceSupport。
public class GreetingServiceModule extends AbstractModule
implements ServiceGuiceSupport {
@Override
protected void configure() {
bindServices(
serviceBinding(GreetingService.class, GreetingServiceImpl.class));
bindClient(WeatherService.class);
}
}
Also, Lagom internally uses the Play Framework. And so, we can add our module to Play’s list of enabled modules in src/main/resources/application.conf file:
另外,Lagom内部使用Play框架。因此,我们可以在src/main/resources/application.conf文件中把我们的模块加入Play的启用模块列表。
play.modules.enabled
+= com.baeldung.lagom.helloworld.greeting.impl.GreetingServiceModule
7.2. Weather Service
7.2.气象局
After looking at the GreetingServiceImpl, WeatherServiceImpl is pretty much straightforward and self-explanatory:
在看了GreetingServiceImpl之后,WeatherServiceImpl几乎是直截了当和不言自明的。
public class WeatherServiceImpl implements WeatherService {
@Override
public ServiceCall<NotUsed, WeatherStats> weatherStatsForToday() {
return req ->
CompletableFuture.completedFuture(WeatherStats.forToday());
}
}
We follow the same steps as we did above for greeting module to register the weather module with Lagom:
我们按照上面打招呼模块的相同步骤,向Lagom注册天气模块。
public class WeatherServiceModule
extends AbstractModule
implements ServiceGuiceSupport {
@Override
protected void configure() {
bindServices(serviceBinding(
WeatherService.class,
WeatherServiceImpl.class));
}
}
Also, register the weather module to Play’s framework list of enabled modules:
同时,将天气模块注册到Play框架的启用模块列表中。
play.modules.enabled
+= com.baeldung.lagom.helloworld.weather.impl.WeatherServiceModule
8. Running the Project
8.运行项目
Lagom allows running any number of services together with a single command.
Lagom允许用一个命令一起运行任何数量的服务。
We can start our project by hitting the below command:
我们可以通过点击下面的命令来启动我们的项目。
sbt lagom:runAll
This will start the embedded Service Locator, embedded Cassandra and then start microservices in parallel. The same command also reloads our individual microservice when the code changes so that we don’t have to restart them manually.
这将启动嵌入式服务定位器、嵌入式Cassandra,然后平行地启动微服务。当代码发生变化时,同一命令也会重新加载我们的各个微服务,这样我们就不必手动重启它们了。
We can be focused on our logic and Lagom handle the compilation and reloading. Once started successfully, we will see the following output:
我们可以专注于我们的逻辑,由Lagom处理编译和重载。一旦启动成功,我们将看到以下输出。
................
[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...)
Once started successfully we can make a curl request for greeting:
一旦启动成功,我们就可以发出curl请求,进行问候。
curl http://localhost:9000/api/greeting/Amit
We will see following output on the console:
我们将在控制台看到以下输出。
Hello Amit! Today's weather stats: Going to Rain, Take Umbrella
Running the same curl request for an existing user will change the greeting message:
为一个现有的用户运行同样的curl请求将改变问候语。
Hello Again Amit! Today's weather stats: Going to Rain, Take Umbrella
9. Conclusion
9.结论
In this article, we have covered how to use Lagom framework to create two micro services that interact asynchronously.
在这篇文章中,我们已经介绍了如何使用Lagom框架来创建两个异步交互的微服务。
The complete source code and all code snippets for this article are available in the GitHub project.
本文的完整源代码和所有代码片段都可以在GitHub项目中获得,。