spring-boot-starter-actor
spring-boot-starter-actor copied to clipboard
Actors kindly introduced to Spring
|
Spring Boot Starter ActorBring the power of the actor model to your Spring Boot applications with Pekko (an open-source fork of Akka). |
Why spring-boot-starter-actor?
I'm a Java developer, and I love using the actor model. However, Spring is everywhere in production. The goal of this project is to help Spring Boot projects easily integrate actor models with a great developer experience.
Key Features:
- Auto-configure necessary actor components (e.g., ActorSystem) within Spring Boot's context
- Dependency injection for actors
- Simplify cluster and sharding
- Built-in metrics using ByteBuddy to intercept actors and collect metrics
With spring-boot-starter-actor, you can build stateful distributed systems without third-party middleware (e.g., Redis, Kafka).
Architecture
graph TB
subgraph "Node 1"
U1[UserActor 1]
subgraph CR["ChatRoomActor"]
T[Topic]
end
end
subgraph "Node 2"
U2[UserActor 2]
end
subgraph "Node 3"
U3[UserActor 3]
end
WS1[🔌 WebSocket] --> U1
WS2[🔌 WebSocket] --> U2
WS3[🔌 WebSocket] --> U3
U1 -.->|subscribe| T
U2 -.->|subscribe| T
U3 -.->|subscribe| T
U1 -->|send message| CR
T -->|broadcast| U1
T -->|broadcast| U2
T -->|broadcast| U3
style CR fill:#8B5CF6,stroke:#7C3AED,color:#fff
style T fill:#A78BFA,stroke:#8B5CF6,color:#fff
style U1 fill:#06B6D4,stroke:#0891B2,color:#fff
style U2 fill:#06B6D4,stroke:#0891B2,color:#fff
style U3 fill:#06B6D4,stroke:#0891B2,color:#fff
Quick Start
Prerequisites
- Java 11 or higher
- Spring Boot 2.x or 3.x
Installation
Add the dependency to your project:
Gradle:
dependencyManagement {
imports {
// Pekko requires Jackson 2.17.3+
mavenBom("com.fasterxml.jackson:jackson-bom:2.17.3")
}
}
// Spring Boot 2.7.x
implementation 'io.github.seonwkim:spring-boot-starter-actor:0.3.0'
// Spring Boot 3.2.x
implementation 'io.github.seonwkim:spring-boot-starter-actor_3:0.3.0'
Maven:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.17.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Spring Boot 2.7.x -->
<dependency>
<groupId>io.github.seonwkim</groupId>
<artifactId>spring-boot-starter-actor</artifactId>
<version>0.3.0</version>
</dependency>
<!-- Spring Boot 3.2.x -->
<dependency>
<groupId>io.github.seonwkim</groupId>
<artifactId>spring-boot-starter-actor_3</artifactId>
<version>0.3.0</version>
</dependency>
Latest versions: spring-boot-starter-actor | spring-boot-starter-actor_3
Enable Actor Support
Add @EnableActorSupport to your application:
@SpringBootApplication
@EnableActorSupport
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Create Your First Actor
Create an actor by implementing SpringActor:
@Component
public class GreeterActor implements SpringActor<GreeterActor.Command> {
public interface Command {
}
public static class Greet extends AskCommand<String> implements Command {
public final String name;
public Greet(String name) {
this.name = name;
}
}
@Override
public SpringActorBehavior<Command> create(SpringActorContext actorContext) {
return SpringActorBehavior.builder(Command.class, actorContext)
.onMessage(Greet.class, (ctx, msg) -> {
msg.reply("Hello, " + msg.name + "!");
return Behaviors.same();
})
.build();
}
}
Use Actors in Your Services
Inject SpringActorSystem and interact with actors:
@Service
public class GreeterService {
private final SpringActorSystem actorSystem;
public GreeterService(SpringActorSystem actorSystem) {
this.actorSystem = actorSystem;
}
public CompletionStage<String> greet(String name) {
return actorSystem.getOrSpawn(GreeterActor.class, "greeter")
.thenCompose(actor -> actor
.ask(new GreeterActor.Greet(name))
.withTimeout(Duration.ofSeconds(5))
.execute()
);
}
}
Core Concepts
Actor Lifecycle Management
Spawn a New Actor:
// Create and start a new actor
CompletionStage<SpringActorHandle<Command>> actorHandle = actorSystem
.actor(MyActor.class)
.withId("my-actor-1")
.withTimeout(Duration.ofSeconds(5)) // Optional
.spawn();
Get Existing Actor:
// Get reference to existing actor (returns null if not found)
CompletionStage<SpringActorHandle<Command>> actorHandle = actorSystem
.get(MyActor.class, "my-actor-1");
Get or Spawn (Recommended):
// Automatically gets existing or spawns new actor
CompletionStage<SpringActorHandle<Command>> actorHandle = actorSystem
.getOrSpawn(MyActor.class, "my-actor-1");
Check if Actor Exists:
CompletionStage<Boolean> exists = actorSystem
.exists(MyActor.class, "my-actor-1");
Stop an Actor:
actorHandle.thenAccept(actor -> actor.stop());
Communication Patterns
Fire-and-forget (tell):
actor.tell(new ProcessOrder("order-123"));
Request-response (ask):
CompletionStage<String> response = actor
.ask(new GetValue())
.withTimeout(Duration.ofSeconds(5))
.execute();
With error handling:
CompletionStage<String> response = actor
.ask(new GetValue())
.withTimeout(Duration.ofSeconds(5))
.onTimeout(() -> "default-value")
.execute();
Spring Dependency Injection
Actors are Spring components with full DI support:
@Component
public class OrderActor implements SpringActor<OrderActor.Command> {
private final OrderRepository orderRepository;
public OrderActor(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public interface Command {
}
public record ProcessOrder(String orderId) implements Command {
}
@Override
public SpringActorBehavior<Command> create(SpringActorContext actorContext) {
return SpringActorBehavior.builder(Command.class, actorContext)
.onMessage(ProcessOrder.class, (ctx, msg) -> {
Order order = orderRepository.findById(msg.orderId);
// Process order...
return Behaviors.same();
})
.build();
}
}
Configuration
Local Mode (Default)
spring:
actor:
pekko:
actor:
provider: local
Cluster Mode
spring:
actor:
pekko:
actor:
provider: cluster
remote:
artery:
canonical:
hostname: "127.0.0.1"
port: 2551
cluster:
seed-nodes:
- "pekko://[email protected]:2551"
Advanced Features
Sharded Actors (Cluster Mode)
For distributed systems, use sharded actors that are automatically distributed across cluster nodes:
Define a Sharded Actor:
@Component
public class UserSessionActor implements SpringShardedActor<UserSessionActor.Command> {
public static final EntityTypeKey<Command> TYPE_KEY =
EntityTypeKey.create(Command.class, "UserSession");
public interface Command extends JsonSerializable {
}
public record UpdateActivity(String activity) implements Command {
}
public static class GetActivity extends AskCommand<String> implements Command {
public GetActivity() {
}
}
@Override
public EntityTypeKey<Command> typeKey() {
return TYPE_KEY;
}
@Override
public SpringShardedActorBehavior<Command> create(SpringShardedActorContext<Command> ctx) {
return SpringShardedActorBehavior.builder(Command.class, ctx)
.withState(entityCtx -> new UserSessionBehavior(ctx.getEntityId()))
.onMessage(UpdateActivity.class, UserSessionBehavior::onUpdateActivity)
.onMessage(GetActivity.class, UserSessionBehavior::onGetActivity)
.build();
}
private static class UserSessionBehavior {
private final String userId;
private String activity = "idle";
UserSessionBehavior(String userId) {
this.userId = userId;
}
Behavior<Command> onUpdateActivity(UpdateActivity msg) {
this.activity = msg.activity;
return Behaviors.same();
}
Behavior<Command> onGetActivity(GetActivity msg) {
msg.reply(activity);
return Behaviors.same();
}
}
}
Using Sharded Actors:
// Get reference (entity created on-demand)
SpringShardedActorHandle<Command> actor = actorSystem
.sharded(UserSessionActor.class)
.withId("user-123")
.get();
// Fire-and-forget
actor.tell(new UpdateActivity("logged-in"));
// Request-response
CompletionStage<String> activity = actor
.ask(new GetActivity())
.withTimeout(Duration.ofSeconds(5))
.execute();
Key Differences from Regular Actors:
- Created automatically when first message arrives (no
spawn()needed) - Always available, even if not currently running
- Automatically distributed across cluster nodes
- Passivated after idle timeout (configurable)
- Use
get()to obtain reference (notspawn())
Supervision and Fault Tolerance
Build self-healing systems with supervision strategies:
Available Strategies:
// Restart on failure (default)
SupervisorStrategy.restart()
// Restart with limit (e.g., 3 times within 1 minute)
SupervisorStrategy.restart().withLimit(3, Duration.ofMinutes(1))
// Stop on failure
SupervisorStrategy.stop()
// Resume and ignore failure
SupervisorStrategy.resume()
Spawn Actors with Supervision:
// Top-level actor
actorSystem.actor(WorkerActor.class)
.withId("worker-1")
.withSupervisionStrategy(SupervisorStrategy.restart().withLimit(3, Duration.ofMinutes(1)))
.spawn();
Spawn Child Actors with Supervision:
@Component
public class SupervisorActor implements SpringActor<SupervisorActor.Command> {
public interface Command {
}
@Override
public SpringActorBehavior<Command> create(SpringActorContext actorContext) {
return SpringActorBehavior.builder(Command.class, actorContext)
.onMessage(DelegateWork.class, (ctx, msg) -> {
SpringActorHandle<Command> self = new SpringActorHandle<>(ctx.getSystem().scheduler(), ctx.getSelf());
// Spawn supervised child
self.child(WorkerActor.class)
.withId("worker-1")
.withSupervisionStrategy(SupervisorStrategy.restart())
.spawn();
return Behaviors.same();
})
.build();
}
}
Child Actor Operations:
// Spawn new child
CompletionStage<SpringActorHandle<Command>> child = parentRef
.child(ChildActor.class)
.withId("child-1")
.spawn();
// Get existing child
CompletionStage<SpringActorHandle<Command>> existing = parentRef
.child(ChildActor.class)
.withId("child-1")
.get();
// Get or spawn (recommended)
CompletionStage<SpringActorHandle<Command>> childRef = parentRef
.child(ChildActor.class)
.withId("child-1")
.getOrSpawn();
Running Examples
Chat Application (Distributed)
Run a distributed chat application across multiple nodes:
# Start 3-node cluster on ports 8080, 8081, 8082
$ sh cluster-start.sh chat io.github.seonwkim.example.SpringPekkoApplication 8080 2551 3
# run frontend
$ cd example/chat/frontend
$ npm run dev
# Stop cluster
$ sh cluster-stop.sh
Monitoring
WIP
Documentation
Full documentation: https://seonwkim.github.io/spring-boot-starter-actor/
Community & Support
Join our community to ask questions, share ideas, and get help:
- Discord: Join our Discord server - Real-time chat with the community
- Issues: GitHub Issues - Bug reports and feature requests
- Discussions: GitHub Discussions - Q&A and general discussions
Contributing
Contributions welcome! Please:
- Create an issue describing your contribution
- Open a PR with clear explanation
- Run
./gradlew spotlessApplyfor formatting - Ensure tests pass
See CONTRIBUTION.md for detailed guidelines and roadmap/ROADMAP.md for the implementation roadmap.
License
This project is licensed under the Apache License 2.0.