1. 概述
在本教程中,我们将借助 Akka 的 Actor 和 Stream 模型,学习如何搭建一个提供基本 CRUD 操作的 HTTP API。
2. Maven 依赖
首先,来看一下使用 Akka HTTP 所需的核心依赖:
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-http_2.12</artifactId>
<version>10.0.11</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-stream_2.12</artifactId>
<version>2.5.11</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-http-jackson_2.12</artifactId>
<version>10.0.11</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-http-testkit_2.12</artifactId>
<version>10.0.11</version>
<scope>test</scope>
</dependency>
当然,你也可以在 Maven Central 上找到这些 Akka 库的最新版本。
3. 创建 Actor
我们以一个用户资源管理的 HTTP API 为例,支持两个操作:
- 创建新用户
- 获取已有用户
在提供 HTTP API 之前,我们需要先实现一个 Actor 来处理这些操作:
class UserActor extends AbstractActor {
private UserService userService = new UserService();
static Props props() {
return Props.create(UserActor.class);
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(CreateUserMessage.class, handleCreateUser())
.match(GetUserMessage.class, handleGetUser())
.build();
}
private FI.UnitApply<CreateUserMessage> handleCreateUser() {
return createUserMessage -> {
userService.createUser(createUserMessage.getUser());
sender()
.tell(new ActionPerformed(
String.format("User %s created.", createUserMessage.getUser().getName())), getSelf());
};
}
private FI.UnitApply<GetUserMessage> handleGetUser() {
return getUserMessage -> {
sender().tell(userService.getUser(getUserMessage.getUserId()), getSelf());
};
}
}
简单来说,我们继承了 AbstractActor
类并实现了 createReceive()
方法。
在 createReceive()
方法中,我们将不同类型的传入消息映射到对应的处理方法上。
这些消息类型是简单的可序列化类,用于描述特定操作。比如:
GetUserMessage
:包含一个userId
字段,用于标识要获取的用户CreateUserMessage
:包含一个User
对象,用于创建新用户
后续我们会看到,如何将 HTTP 请求转换为这些消息。
最终,我们将所有消息委托给 UserService
实例处理,它负责用户数据的持久化逻辑。
⚠️ 注意:虽然 props()
方法不是必须的,但在后续创建 ActorSystem
时会非常有用。
如果你对 Akka Actor 不太熟悉,可以看看我们的 Akka Actor 入门指南。
4. 定义 HTTP 路由
有了处理业务逻辑的 Actor 后,剩下的工作就是提供一个 HTTP API,将请求转发给 Actor 处理。
Akka 使用“路由(Route)”来描述 HTTP API。每个操作都需要一个路由。
要创建 HTTP 服务,我们继承 HttpApp
类并实现 routes
方法:
class UserServer extends HttpApp {
private final ActorRef userActor;
Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS));
UserServer(ActorRef userActor) {
this.userActor = userActor;
}
@Override
public Route routes() {
return path("users", this::postUser)
.orElse(path(segment("users").slash(longSegment()), id -> route(getUser(id))));
}
private Route getUser(Long id) {
return get(() -> {
CompletionStage<Optional<User>> user =
PatternsCS.ask(userActor, new GetUserMessage(id), timeout)
.thenApply(obj -> (Optional<User>) obj);
return onSuccess(() -> user, performed -> {
if (performed.isPresent())
return complete(StatusCodes.OK, performed.get(), Jackson.marshaller());
else
return complete(StatusCodes.NOT_FOUND);
});
});
}
private Route postUser() {
return route(post(() -> entity(Jackson.unmarshaller(User.class), user -> {
CompletionStage<ActionPerformed> userCreated =
PatternsCS.ask(userActor, new CreateUserMessage(user), timeout)
.thenApply(obj -> (ActionPerformed) obj);
return onSuccess(() -> userCreated, performed -> {
return complete(StatusCodes.CREATED, performed, Jackson.marshaller());
});
})));
}
}
虽然代码看起来有点多,但核心逻辑和之前一样:将操作映射为路由。我们来拆解一下。
在 getUser()
中,我们将传入的用户 ID 包装成 GetUserMessage
并发送给 userActor
。
当 Actor 处理完请求后,onSuccess
回调会被触发,我们在这里构造 HTTP 响应,返回状态码和 JSON 数据。这里使用了 Jackson 来序列化数据。
在 postUser()
中,由于需要处理 JSON 请求体,我们使用 entity()
方法将 JSON 映射为 User
对象,然后包装成 CreateUserMessage
发送给 Actor。
✅ 由于 HttpApp
要求只返回一个 Route
对象,我们通过 orElse
将多个路由合并。使用 path
指令定义了 API 的访问路径。
- POST 请求绑定到
/users
- GET 请求绑定到
/users/{id}
如果请求方法不匹配,Akka 会自动返回 405(Method Not Allowed)。
更多关于 Akka HTTP 路由的细节,可以参考 Akka 官方文档。
5. 启动服务
实现完 HttpApp
后,只需要几行代码就可以启动服务:
public static void main(String[] args) throws Exception {
ActorSystem system = ActorSystem.create("userServer");
ActorRef userActor = system.actorOf(UserActor.props(), "userActor");
UserServer server = new UserServer(userActor);
server.startServer("localhost", 8080, system);
}
我们创建了一个 ActorSystem
,启动了一个 UserActor
实例,并在本地 8080 端口运行 HTTP 服务。
6. 总结
本文介绍了 Akka HTTP 的基本使用方式,通过一个例子展示了如何搭建 HTTP 服务并提供类似 REST API 的接口,支持创建和查询用户资源。
一如既往,本文的完整代码可以在 GitHub 上找到。