1. 概述
本文将深入探讨Lagom框架,并通过实现一个示例应用来展示响应式微服务架构的实践。
简单来说,响应式应用依赖消息驱动的异步通信,具有高度响应性、弹性和可伸缩性的特点。
微服务驱动架构意味着将系统拆分为多个协作服务边界,以实现隔离性、自治性、单一职责和可移动性等目标。关于这些概念的深入理解,可参考响应式宣言和响应式微服务架构。
2. 为什么选择Lagom?
Lagom是专为从单体架构向微服务架构转型而设计的开源框架。它抽象了构建、运行和监控微服务应用的复杂性。
底层实现上,Lagom整合了:
3. Lagom入门示例
我们将构建一个Lagom应用,处理用户问候请求并返回包含当日天气统计的问候消息。系统包含两个微服务:
- Greeting服务:处理问候请求,调用天气服务返回完整响应
- Weather服务:提供当日天气统计数据
当已注册用户再次访问时,Greeting服务会显示不同的问候语。
3.1 前置条件
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项目+实现项目的约定
- ✅ 添加
lagomJavadslApi
和lagomJavadslPersistenceCassandra
依赖 - ✅
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
命令将生成以下模板:
- greeting-api
- greeting-impl
- weather-api
- weather-impl
⚠️ 注意:需在每个项目中手动创建src/main/java
和src/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");
}
};
}
}
实现要点:
- ✅ 注入
PersistentEntityRegistry
和WeatherService
- ✅ 通过实体引用处理命令
- ✅ 异步调用天气服务
- ✅ 组合响应结果
注册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
启动流程:
- 启动嵌入式Service Locator
- 启动嵌入式Cassandra
- 并行启动微服务
- ✅ 自动热重载(代码修改无需手动重启)
成功启动后输出:
................
[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初始化可能较慢,耐心等待即可。