1. 概述

本文将深入探讨Lagom框架,并通过实现一个示例应用来展示响应式微服务架构的实践。

简单来说,响应式应用依赖消息驱动的异步通信,具有高度响应性弹性可伸缩性的特点。

微服务驱动架构意味着将系统拆分为多个协作服务边界,以实现隔离性自治性单一职责可移动性等目标。关于这些概念的深入理解,可参考响应式宣言响应式微服务架构

2. 为什么选择Lagom?

Lagom是专为从单体架构向微服务架构转型而设计的开源框架。它抽象了构建、运行和监控微服务应用的复杂性。

底层实现上,Lagom整合了:

3. Lagom入门示例

我们将构建一个Lagom应用,处理用户问候请求并返回包含当日天气统计的问候消息。系统包含两个微服务:

  • Greeting服务:处理问候请求,调用天气服务返回完整响应
  • Weather服务:提供当日天气统计数据

当已注册用户再次访问时,Greeting服务会显示不同的问候语。

3.1 前置条件

  1. 安装Scala(当前使用2.11.8版本)下载地址
  2. 安装sbt构建工具(当前使用0.13.11)下载地址

4. 项目搭建

4.1 SBT构建配置

创建项目根目录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))

关键配置说明:

  • ✅ 指定组织信息、Scala版本并禁用Kafka
  • ✅ 每个微服务遵循API项目+实现项目的约定
  • ✅ 添加lagomJavadslApilagomJavadslPersistenceCassandra依赖
  • greeting-impl依赖weather-api以获取天气数据

project/plugins.sbt中添加插件支持:

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. greeting-api
  2. greeting-impl
  3. weather-api
  4. weather-impl

⚠️ 注意:需在每个项目中手动创建src/main/javasrc/main/resources目录以遵循Maven目录结构。

同时会生成两个内部项目:

  • lagom-internal-meta-project-cassandra
  • lagom-internal-meta-project-service-locator

这些项目由Lagom内部使用,无需关注。

5. 服务接口定义

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

核心要点:

  • ServiceCall<Request, Response>定义服务方法签名
  • NotUsed表示无请求体,String为响应类型
  • descriptor()方法将服务映射到REST接口:
    • 服务名:greetingservice
    • GET请求:/api/greeting/:fromUser
    • 自动ACL配置

类似地,在weather-api项目中定义WeatherService

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持久化——事件溯源

事件溯源的核心思想:将所有状态变更捕获为不可变领域事件,通过重放事件计算当前状态。这种模式本质是函数式编程中的foldLeft操作。

优势:

  • ✅ 高写入性能(仅追加事件)
  • ✅ 避免更新/删除操作
  • ✅ 完整的审计跟踪

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

关键实现:

  • ✅ 继承PersistentEntity<Command, Event, State>
  • setCommandHandler处理命令并持久化事件
  • setEventHandler通过事件更新状态(不可变更新
  • ✅ 初始状态为"Hello ",收到事件后变为"Hello Again "

命令和事件接口定义:

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 Greeting服务实现

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

实现要点:

  • ✅ 注入PersistentEntityRegistryWeatherService
  • ✅ 通过实体引用处理命令
  • ✅ 异步调用天气服务
  • ✅ 组合响应结果

注册Weather服务模块:

public class WeatherServiceModule 
  extends AbstractModule 
  implements ServiceGuiceSupport {
 
      @Override
      protected void configure() {
          bindServices(serviceBinding(
            WeatherService.class, 
            WeatherServiceImpl.class));
      }
}

application.conf中启用模块:

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

8. 运行项目

Lagom支持单命令启动所有服务,简单粗暴:

sbt runAll

启动流程:

  1. 启动嵌入式Service Locator
  2. 启动嵌入式Cassandra
  3. 并行启动微服务
  4. ✅ 自动热重载(代码修改无需手动重启)

成功启动后输出:

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

再次调用(已存在用户):

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

9. 总结

本文展示了如何使用Lagom框架构建两个异步交互的微服务。核心要点:

  • ✅ 响应式架构实现
  • ✅ 事件溯源模式应用
  • ✅ 服务接口与实现分离
  • ✅ 内置服务发现与热重载

完整源码可在GitHub项目获取。踩坑提醒:首次启动时Cassandra初始化可能较慢,耐心等待即可。


原始标题:Guide to Reactive Microservices Using Lagom Framework