mirror of https://github.com/dapr/java-sdk.git
Refactoring actor example. (#182)
Co-authored-by: Young Bu Park <youngp@microsoft.com>
This commit is contained in:
parent
838224f18a
commit
082c4a46fc
|
@ -11,70 +11,72 @@ import io.dapr.actors.client.ActorProxyBuilder;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Client for Actor runtime.
|
||||
* Client for Actor runtime to invoke actor methods.
|
||||
* 1. Build and install jars:
|
||||
* mvn clean install
|
||||
* 2. Run the client:
|
||||
* dapr run --app-id demoactorclient --port 3006 -- mvn exec:java \
|
||||
* -pl=examples -Dexec.mainClass=io.dapr.examples.actors.http.DemoActorClient
|
||||
* -pl=examples -Dexec.mainClass=io.dapr.examples.actors.http.DemoActorClient
|
||||
*/
|
||||
public class DemoActorClient {
|
||||
|
||||
private static final int NUM_ACTORS = 3;
|
||||
|
||||
private static final int NUM_MESSAGES_PER_ACTOR = 10;
|
||||
|
||||
private static final String METHOD_NAME = "say";
|
||||
|
||||
private static final ExecutorService POOL = Executors.newFixedThreadPool(NUM_ACTORS);
|
||||
|
||||
/**
|
||||
* The main method.
|
||||
* @param args Unused.
|
||||
* @throws Exception An Exception.
|
||||
* @param args Input arguments (unused).
|
||||
* @throws InterruptedException If program has been interrupted.
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
ActorProxyBuilder builder = new ActorProxyBuilder("DemoActor");
|
||||
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>(NUM_ACTORS);
|
||||
List<Thread> threads = new ArrayList<>(NUM_ACTORS);
|
||||
|
||||
// Creates multiple actors.
|
||||
for (int i = 0; i < NUM_ACTORS; i++) {
|
||||
ActorProxy actor = builder.build(ActorId.createRandom());
|
||||
futures.add(callActorNTimes(actor));
|
||||
|
||||
// Start a thread per actor.
|
||||
Thread thread = new Thread(() -> callActorForever(actor));
|
||||
thread.start();
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
futures.forEach(CompletableFuture::join);
|
||||
POOL.shutdown();
|
||||
POOL.awaitTermination(1, TimeUnit.MINUTES);
|
||||
// Waits for threads to finish.
|
||||
for (Thread thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
System.out.println("Done.");
|
||||
}
|
||||
|
||||
private static final CompletableFuture<Void> callActorNTimes(ActorProxy actor) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
actor.invokeActorMethod("registerReminder").block();
|
||||
for (int i = 0; i < NUM_MESSAGES_PER_ACTOR; i++) {
|
||||
actor.invokeActorMethod("incrementAndGet", 1).block();
|
||||
String result = actor.invokeActorMethod(METHOD_NAME,
|
||||
String.format("Actor %s said message #%d", actor.getActorId().toString(), i), String.class).block();
|
||||
System.out.println(String.format("Actor %s got a reply: %s", actor.getActorId().toString(), result));
|
||||
try {
|
||||
Thread.sleep((long) (1000 * Math.random()));
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Makes multiple method calls into actor until interrupted.
|
||||
* @param actor Actor to be invoked.
|
||||
*/
|
||||
private static final void callActorForever(ActorProxy actor) {
|
||||
// First, register reminder.
|
||||
actor.invokeActorMethod("registerReminder").block();
|
||||
|
||||
System.out.println(
|
||||
"Messages sent: " + actor.invokeActorMethod("incrementAndGet", 0, int.class).block());
|
||||
}, POOL);
|
||||
// Now, we run until thread is interrupted.
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
// Invoke actor method to increment counter by 1, then build message.
|
||||
int messageNumber = actor.invokeActorMethod("incrementAndGet", 1, int.class).block();
|
||||
String message = String.format("Actor %s said message #%d", actor.getActorId().toString(), messageNumber);
|
||||
|
||||
// Invoke the 'say' method in actor.
|
||||
String result = actor.invokeActorMethod("say", message, String.class).block();
|
||||
System.out.println(String.format("Actor %s got a reply: %s", actor.getActorId().toString(), result));
|
||||
|
||||
try {
|
||||
// Waits for up to 1 second.
|
||||
Thread.sleep((long) Math.rint(1000));
|
||||
} catch (InterruptedException e) {
|
||||
// We have been interrupted, so we set the interrupted flag to exit gracefully.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,9 +31,9 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl
|
|||
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
|
||||
/**
|
||||
* This is the constructor of an actor implementation.
|
||||
* This is the constructor of an actor implementation, while also registering a timer.
|
||||
* @param runtimeContext The runtime context object which contains objects such as the state provider.
|
||||
* @param id The id of this actor.
|
||||
* @param id The id of this actor.
|
||||
*/
|
||||
public DemoActorImpl(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||
super(runtimeContext, id);
|
||||
|
@ -46,6 +46,9 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl
|
|||
Duration.ofSeconds(1)).block();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a reminder.
|
||||
*/
|
||||
@Override
|
||||
public void registerReminder() {
|
||||
super.registerReminder(
|
||||
|
@ -55,6 +58,11 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl
|
|||
Duration.ofSeconds(2)).block();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message and appends the timestamp.
|
||||
* @param something Something to be said.
|
||||
* @return What was said appended with timestamp.
|
||||
*/
|
||||
@Override
|
||||
public String say(String something) {
|
||||
Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||
|
@ -71,6 +79,23 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl
|
|||
return utcNowAsString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments a persistent counter, saves and returns its updated value.
|
||||
* Example of method implemented with Reactor's Mono class.
|
||||
* This method could be rewritten with blocking calls in Mono, using block() method:
|
||||
*
|
||||
* <p>public int incrementAndGet(int delta) {
|
||||
* int counter = 0;
|
||||
* if (super.getActorStateManager().contains("counter").block()) {
|
||||
* counter = super.getActorStateManager().get("counter", int.class).block();
|
||||
* }
|
||||
* counter = counter + 1;
|
||||
* super.getActorStateManager().set("counter", counter).block();
|
||||
* return counter;
|
||||
* }</p>
|
||||
* @param delta Amount to be added to counter.
|
||||
* @return Mono response for the incremented value.
|
||||
*/
|
||||
@Override
|
||||
public Mono<Integer> incrementAndGet(int delta) {
|
||||
return super.getActorStateManager().contains("counter")
|
||||
|
@ -79,6 +104,10 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl
|
|||
.flatMap(c -> super.getActorStateManager().set("counter", c).thenReturn(c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method invoked by timer.
|
||||
* @param message Message to be printed.
|
||||
*/
|
||||
@Override
|
||||
public void clock(String message) {
|
||||
Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||
|
@ -90,20 +119,34 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl
|
|||
+ (message == null ? "" : message + " @ " + utcNowAsString));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to determine reminder's state type.
|
||||
* @return Class for reminder's state.
|
||||
*/
|
||||
@Override
|
||||
public Class<Integer> getStateType() {
|
||||
return Integer.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used be invoked for a reminder.
|
||||
* @param reminderName The name of reminder provided during registration.
|
||||
* @param state The user state provided during registration.
|
||||
* @param dueTime The invocation due time provided during registration.
|
||||
* @param period The invocation period provided during registration.
|
||||
* @return Mono result.
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> receiveReminder(String reminderName, Integer state, Duration dueTime, Duration period) {
|
||||
Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||
String utcNowAsString = DATE_FORMAT.format(utcNow.getTime());
|
||||
return Mono.fromRunnable(() -> {
|
||||
Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||
String utcNowAsString = DATE_FORMAT.format(utcNow.getTime());
|
||||
|
||||
// Handles the request by printing message.
|
||||
System.out.println(String.format(
|
||||
"Server reminded actor %s of: %s for %d @ %s",
|
||||
this.getId(), reminderName, state, utcNowAsString));
|
||||
return Mono.empty();
|
||||
String message = String.format("Server reminded actor %s of: %s for %d @ %s",
|
||||
this.getId(), reminderName, state, utcNowAsString);
|
||||
|
||||
// Handles the request by printing message.
|
||||
System.out.println(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||
* dapr run --app-id demoactorservice --app-port 3000 --port 3005 \
|
||||
* -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.actors.http.DemoActorService -Dexec.args="-p 3000"
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class DemoActorService {
|
||||
|
||||
/**
|
||||
|
@ -42,13 +41,9 @@ public class DemoActorService {
|
|||
int port = Integer.parseInt(cmd.getOptionValue("port"));
|
||||
|
||||
// Register the Actor class.
|
||||
ActorRuntime.getInstance().registerActor(
|
||||
DemoActorImpl.class, new DefaultObjectSerializer(), new DefaultObjectSerializer());
|
||||
ActorRuntime.getInstance().registerActor(DemoActorImpl.class);
|
||||
|
||||
// Start Dapr's callback endpoint.
|
||||
DaprApplication.start(port);
|
||||
|
||||
// Start application's endpoint.
|
||||
SpringApplication.run(DemoActorService.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package io.dapr.examples.actors.http;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class HelloController {
|
||||
@RequestMapping("/")
|
||||
public String index() {
|
||||
return "Greetings from your Spring Boot Application!";
|
||||
}
|
||||
}
|
|
@ -44,24 +44,65 @@ public class DemoActorService {
|
|||
public static void main(String[] args) throws Exception {
|
||||
///...
|
||||
// Register the Actor class.
|
||||
ActorRuntime.getInstance().registerActor(
|
||||
DemoActorImpl.class, new DefaultObjectSerializer(), new DefaultObjectSerializer());
|
||||
ActorRuntime.getInstance().registerActor(DemoActorImpl.class);
|
||||
|
||||
// Start Dapr's callback endpoint.
|
||||
DaprApplication.start(port);
|
||||
|
||||
// Start application's endpoint.
|
||||
SpringApplication.run(DemoActorService.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This application uses `ActorRuntime.getInstance().registerActor()` in order to register `DemoActorImpl` as an actor in the Dapr Actor runtime. Notice that this call passes in two serializer implementations: one is for Dapr's sent and received object and the other is for objects to be persisted.
|
||||
This application uses `ActorRuntime.getInstance().registerActor()` in order to register `DemoActorImpl` as an actor in the Dapr Actor runtime. Internally, it is using `DefaultObjectSerializer` for two properties: `objectSerializer` is for Dapr's sent and received objects, and `stateSerializer` is for objects to be persisted.
|
||||
|
||||
|
||||
`DaprApplication.start()` method will run the Spring Boot [DaprApplication](../../../springboot/DaprApplication.java), which registers the Dapr Spring Boot controller [DaprController](../../springboot/DaprController.java). This controller contains all Actor methods implemented as endpoints. The Dapr's sidecar will call into the controller. At the end of the main method, this class uses `SpringApplication.run()` to boostrap itself a an Spring application.
|
||||
`DaprApplication.start()` method will run the Spring Boot [DaprApplication](../../../springboot/DaprApplication.java), which registers the Dapr Spring Boot controller [DaprController](../../springboot/DaprController.java). This controller contains all Actor methods implemented as endpoints. The Dapr's sidecar will call into the controller.
|
||||
|
||||
Execute the follow script in order to run the DemoActorService:
|
||||
See [DemoActorImpl](DemoActorImpl.java) for details on the implementation of an actor:
|
||||
```java
|
||||
@ActorType(name = "DemoActor")
|
||||
public class DemoActorImpl extends AbstractActor implements DemoActor, Remindable<Integer> {
|
||||
//...
|
||||
|
||||
public DemoActorImpl(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||
super(runtimeContext, id);
|
||||
//...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerReminder() {
|
||||
//...
|
||||
}
|
||||
|
||||
@Override
|
||||
public String say(String something) {
|
||||
//...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Integer> incrementAndGet(int delta) {
|
||||
//...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clock(String message) {
|
||||
//...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Integer> getStateType() {
|
||||
return Integer.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> receiveReminder(String reminderName, Integer state, Duration dueTime, Duration period) {
|
||||
//...
|
||||
}
|
||||
}
|
||||
```
|
||||
An actor inherits from `AbstractActor` and implements the constructor to pass through `ActorRuntimeContext` and `ActorId`. By default, the actor's name will be the same as the class' name. Optionally, it can be annotated with `ActorType` and override the actor's name. The actor's methods can be synchronously or use [Project Reactor's Mono](https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html) return type. Finally, state management is done via methods in `super.getActorStateManager()`.
|
||||
|
||||
|
||||
Now, execute the following script in order to run DemoActorService:
|
||||
```sh
|
||||
cd to [repo-root]
|
||||
dapr run --app-id demoactorservice --app-port 3000 --port 3005 -- mvn exec:java -pl=examples -D exec.mainClass=io.dapr.examples.actors.http.DemoActorService -D exec.args="-p 3000"
|
||||
|
@ -77,39 +118,47 @@ The `DemoActorClient.java` file contains the `DemoActorClient` class. See the co
|
|||
public class DemoActorClient {
|
||||
|
||||
private static final int NUM_ACTORS = 3;
|
||||
private static final int NUM_MESSAGES_PER_ACTOR = 10;
|
||||
|
||||
private static final ExecutorService POOL = Executors.newFixedThreadPool(NUM_ACTORS);
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
///...
|
||||
for (int i = 0; i < NUM_ACTORS; i++) {
|
||||
ActorProxy actor = builder.build(ActorId.createRandom());
|
||||
futures.add(callActorNTimes(actor));
|
||||
|
||||
// Start a thread per actor.
|
||||
Thread thread = new Thread(() -> callActorForever(actor));
|
||||
thread.start();
|
||||
threads.add(thread);
|
||||
}
|
||||
///...
|
||||
|
||||
private static final CompletableFuture<Void> callActorNTimes(ActorProxy actor) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
actor.invokeActorMethod("registerReminder").block();
|
||||
for (int i = 0; i < NUM_MESSAGES_PER_ACTOR; i++) {
|
||||
//Invoking the "incrementAndGet" method:
|
||||
actor.invokeActorMethod("incrementAndGet", 1).block();
|
||||
//Invoking "say" method
|
||||
String result = actor.invokeActorMethod("say",
|
||||
String.format("Actor %s said message #%d", actor.getActorId().toString(), i), String.class).block();
|
||||
System.out.println(String.format("Actor %s got a reply: %s", actor.getActorId().toString(), result));
|
||||
///...
|
||||
}
|
||||
System.out.println(
|
||||
"Messages sent: " + actor.invokeActorMethod("incrementAndGet", 0, int.class).block());
|
||||
}, POOL);
|
||||
}
|
||||
|
||||
private static final void callActorForever(ActorProxy actor) {
|
||||
// First, register reminder.
|
||||
actor.invokeActorMethod("registerReminder").block();
|
||||
|
||||
// Now, we run until thread is interrupted.
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
// Invoke actor method to increment counter by 1, then build message.
|
||||
int messageNumber = actor.invokeActorMethod("incrementAndGet", 1, int.class).block();
|
||||
String message = String.format("Actor %s said message #%d", actor.getActorId().toString(), messageNumber);
|
||||
|
||||
// Invoke the 'say' method in actor.
|
||||
String result = actor.invokeActorMethod("say", message, String.class).block();
|
||||
System.out.println(String.format("Actor %s got a reply: %s", actor.getActorId().toString(), result));
|
||||
|
||||
try {
|
||||
// Waits for up to 1 second.
|
||||
Thread.sleep((long) Math.rint(1000));
|
||||
} catch (InterruptedException e) {
|
||||
// We have been interrupted, so we set the interrupted flag to exit gracefully.
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
First, The client defines how many actors it is going to create, as well as how many invocation calls it will perform per actor. Then the main method declares a `ActorProxyBuilder` for the `DemoActor` class for creating `ActorProxy` instances, which are the actor representation provided by the SDK. The code executes the `callActorNTimes` private method once per actor. This method executes functionality for the DemoActor implementation using `actor.invokeActorMethod()` in the follow order: `registerReminder()` which sets the due time and period for the reminder, `incrementAndGet()` which increments a counter, persists it and sends it back as response, and finally `say` method wich will print a message containing the received string along with the formatted server time. See [DemoActorImpl](DemoActorImpl.java) for details on the implementation of these methods.
|
||||
First, the client defines how many actors it is going to create. Then the main method declares a `ActorProxyBuilder` for the `DemoActor` class to create `ActorProxy` instances, which are the actor representation provided by the SDK. The code executes the `callActorForever` private method once per actor. This method triggers the DemoActor's implementation by using `actor.invokeActorMethod()`. Initially, it will invoke `registerReminder()`, which sets the due time and period for the reminder. Then, `incrementAndGet()` increments a counter, persists it and sends it back as response. Finally `say` method which will print a message containing the received string along with the formatted server time.
|
||||
|
||||
Use the follow command to execute the DemoActorClient:
|
||||
|
||||
|
|
Loading…
Reference in New Issue