使用Lagom框架的反应式微服务指南

1.概述

在本文中,我们将探索Lagom框架并使用反应式微服务驱动架构实现示例应用程序

简而言之,反应式软件应用程序依赖于消息驱动的异步通信,并且本质上具有高度响应性弹性弹性

通过微服务驱动的架构,我们意味着将系统划分为协作服务之间的界限,以实现隔离自治单一责任移动性等目标。有关这两个概念的进一步阅读,请参阅“反应式宣言”和“ 反应式微服务架构”

2.为什么选择Lagom?

Lagom是一个开源框架,它构建了从单块到微服务驱动的应用程序架构的转变。它抽象了构建,运行和监视微服务驱动的应用程序的复杂性。

在幕后,Lagom框架使用Play框架Akka消息驱动的运行时,Kafka用于解耦服务,事件源CQRS模式,以及ConductR支持,用于监视和扩展容器环境中的微服务。

3.拉格姆的Hello World

我们将创建一个Lagom应用程序来处理来自用户的问候请求,并回复问候消息以及当天的天气统计信息。

我们将开发两个独立的微服务:问候天气。

问候语将集中于处理问候请求,与气象服务交互以回复用户。该天气微服务将服务于今天的天气统计数据的请求。

在现有用户与Greeting微服务交互的情况下,将向用户显示不同的问候消息。

3.1。先决条件

  1. 这里安装Scala(我们目前使用的是2.11.8版本)
  2. 这里安装sbt构建工具(我们目前使用的是0.13.11)

4.项目设置

现在让我们快速了解一下设置一个有效的Lagom系统的步骤。

4.1。SBT Build

创建项目文件夹lagom-hello-world,然后是构建文件build.sbt。Lagom系统通常由一组sbt构建组成,每个构建对应于一组相关服务:

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

首先,我们为当前项目指定了组织详细信息,scala版本和禁用的KafkaLagom遵循每个微服务的两个独立项目的约定:API项目和实施项目。

首先,我们为当前项目指定了组织详细信息,scala版本和禁用的KafkaLagom遵循每个微服务的两个独立项目的约定:API项目和实施项目。

API项目包含实现所依赖的服务接口。

我们已经将相关性添加到相关的Lagom模块,如lagomJavadslApilagomJavadslPersistenceCassandra,以便在我们的微服务中使用Lagom Java API,并分别在Cassandra中存储与持久实体相关的事件。

此外,greeting-impl项目依赖于weather-api项目来获取和提供天气统计数据,同时向用户致意。

通过使用plugins.sbt文件创建一个插件文件夹来添加对Lagom插件的支持,该文件具有Lagom插件的条目。它为构建,运行和部署应用程序提供了所有必要的支持。

此外,如果我们在这个项目中使用Eclipse IDE ,那么sbteclipse插件会很方便。下面的代码显示了两个插件的内容:

 

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

创建project / build.properties文件并指定要使用的sbt版本:

 

sbt.version=0.13.11

4.2。项目生成

从项目根目录运行sbt命令将生成以下项目模板:

  1. 问候-API
  2. 问候,IMPL
  3. 天气API
  4. 天气IMPL

在我们开始实现微服务之前,让我们在每个项目中添加src / main / javasrc / main / java / resources文件夹,以遵循类似Maven的项目目录布局。

此外,在project-root / target / lagom-dynamic-projects中生成了两个动态项目

  1. lagom内部-元项目卡桑德拉
  2. lagom内部-元项目服务定位器

这些项目由Lagom内部使用。

5.服务接口

greeting-api项目中,我们指定以下接口:

public interface GreetingService extends Service {

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公开handleGreetFrom()来处理来自用户的greet请求。甲ServiceCall API被用作这些方法的返回类型。ServiceCall采用两种类型的参数RequestResponse

所述请求参数是传入请求消息的类型,并且响应参数是输出响应消息的类型。

在上面的示例中,我们没有使用请求有效负载,请求类型是NotUsed响应类型是String问候消息。

GreetingService还通过提供Service.descriptor()方法的默认实现来指定调用期间使用的实际传输的映射。返回名为greetingservice的服务。

handleGreetFrom()服务调用使用Rest标识符映射:GET方法类型和路径标识符/ api / greeting /:fromUser映射到handleGreetFrom()方法。有关服务标识符的更多详细信息,请查看此链接

在同一行,我们在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定义为枚举,包含不同天气的样本值和随机查找以返回当天的天气预报:

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 – 事件采购

简而言之,在使用事件源的系统中,我们将能够捕获所有更改,因为一个接一个地附加了不可变域事件。通过重放和处理事件来导出当前状态。该操作本质上是从函数式编程范例中已知的foldLeft操作。

事件源通过附加事件并避免更新和删除现有事件来帮助实现高写入性能。

现在让我们看看greeting-impl项目中的持久实体GreetingEntity

public class GreetingEntity extends
PersistentEntity {

  @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提供了PersistentEntity <Command,Entity,Event> API,用于通过setCommandHandler()方法处理Command类型的传入事件,并将状态更改保存为Event类型的事件。通过使用setEventHandler()方法将事件应用于当前状态来更新域对象状态。initialBehavior()抽象方法定义实体的行为

initialBehavior()中,我们构建了原始的GreetingState “Hello”文本。然后我们可以定义一个ReceivedGreetingCommand命令处理程序 – 它生成一个ReceivedGreetingEvent事件并在事件日志中保留。

GreetingStateReceivedGreetingEvent事件处理程序方法重新计算为“Hello Again” 。如前所述,我们不是在调用setter – 而是从正在处理的当前事件中创建一个State的新实例

Lagom遵循GreetingCommandGreetingEvent接口的惯例,将所有支持的命令和事件保存在一起:

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.服务实施

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

}

简单地说,我们使用@Inject(由Guice框架提供)注入PersistentEntityRegistryWeatherService依赖项,并且我们注册持久的GreetingEntity

handleGreetFrom()实现发送ReceivedGreetingCommandGreetingEntity处理并返回问候串异步使用CompletableFuture的实施CompletionStage API。

同样,我们对Weather微服务进行异步调用以获取今天的天气统计数据。

最后,我们连接两个输出并将最终结果返回给用户。

要使用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);
}

}

此外,Lagom内部使用Play Framework。因此,我们可以在src / main / resources / application.conf文件中将我们的模块添加到Play的已启用模块列表中:

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

7.2。气象服务

在查看GreetingServiceImpl之后WeatherServiceImpl非常直接且不言自明:

public class WeatherServiceImpl implements WeatherService {

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

}

我们按照与上面用于问候模块相同的步骤向Lagom注册天气模块:

public class WeatherServiceModule
extends AbstractModule
implements ServiceGuiceSupport {

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

}

此外,将天气模块注册到已启用模块的Play框架列表:

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

8.运行项目

Lagom允许使用单个命令一起运行任意数量的服务

我们可以通过点击以下命令来启动我们的项目:

1sbt lagom:runAll

这将启动嵌入式服务定位器,嵌入式Cassandra,然后并行启动微服务。当代码更改时,相同的命令也会重新加载我们的单个微服务,这样我们就不必手动重启它们

我们可以专注于我们的逻辑和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…)

成功启动后,我们可以提出问候的卷曲请求:

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

我们将在控制台上看到以下输出:

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


对现有用户运行相同的curl请求将更改问候消息:


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

9.结论

在本文中,我们介绍了如何使用Lagom框架创建两个异步交互的微服务。

GitHub项目中提供完整的源代码和本文的所有代码片段。

英文:https://www.baeldung.com/lagom-reactive-microservices

最后更新于 三月 19, 2021

心有多大,路就有多远