Skip to content

Commit

Permalink
auto validate actors (dapr#863)
Browse files Browse the repository at this point in the history
Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com>
Signed-off-by: Mahmut Canga <cangamahmut@gmail.com>
  • Loading branch information
mukundansundar authored and macromania committed Jun 21, 2023
1 parent 64b3ad8 commit 4117801
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 42 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ jobs:
working-directory: ./examples
run: |
mm.py ./src/main/java/io/dapr/examples/configuration/http/README.md
- name: Validate actors example
working-directory: ./examples
run: |
mm.py ./src/main/java/io/dapr/examples/actors/README.md
- name: Validate query state HTTP example
working-directory: ./examples
run: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
@ActorType(name = "DemoActor")
public interface DemoActor {

void registerReminder();
void registerTimer(String state);

void registerReminder(int index);

@ActorMethod(name = "echo_message")
String say(String something);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public static void main(String[] args) throws InterruptedException {
DemoActor actor = builder.build(actorId);

// Start a thread per actor.
Thread thread = new Thread(() -> callActorForever(actorId.toString(), actor));
int finalI = i;
Thread thread = new Thread(() -> callActorForever(finalI, actorId.toString(), actor));
thread.start();
threads.add(thread);
}
Expand All @@ -68,19 +69,23 @@ public static void main(String[] args) throws InterruptedException {
* @param actorId Actor's identifier.
* @param actor Actor to be invoked.
*/
private static final void callActorForever(String actorId, DemoActor actor) {
private static final void callActorForever(int index, String actorId, DemoActor actor) {
// First, register reminder.
actor.registerReminder();
actor.registerReminder(index);
// Second register timer.
actor.registerTimer("ping! {" + index + "} ");

// 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.incrementAndGet(1).block();
String message = String.format("Actor %s said message #%d", actorId, messageNumber);
String message = String.format("Message #%d received from actor at index %d with ID %s", messageNumber,
index, actorId);

// Invoke the 'say' method in actor.
String result = actor.say(message);
System.out.println(String.format("Actor %s got a reply: %s", actorId, result));
System.out.println(String.format("Reply %s received from actor at index %d with ID %s ", result,
index, actorId));

try {
// Waits for up to 1 second.
Expand Down
24 changes: 16 additions & 8 deletions examples/src/main/java/io/dapr/examples/actors/DemoActorImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,18 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl
*/
public DemoActorImpl(ActorRuntimeContext runtimeContext, ActorId id) {
super(runtimeContext, id);
}

/**
* Register a timer.
*/
@Override
public void registerTimer(String state) {
// For example, the state will be formatted as `ping! {INDEX}` where INDEX is the index of the actor related to ID.
super.registerActorTimer(
null,
"clock",
"ping!",
state,
Duration.ofSeconds(2),
Duration.ofSeconds(1)).block();
}
Expand All @@ -56,10 +63,11 @@ public DemoActorImpl(ActorRuntimeContext runtimeContext, ActorId id) {
* Registers a reminder.
*/
@Override
public void registerReminder() {
public void registerReminder(int index) {
// For this example, the state reminded by the reminder is deterministic to be the index(not ID) of the actor.
super.registerReminder(
"myremind",
(int) (Integer.MAX_VALUE * Math.random()),
index,
Duration.ofSeconds(5),
Duration.ofSeconds(2)).block();
}
Expand Down Expand Up @@ -120,9 +128,9 @@ public void clock(String message) {
String utcNowAsString = DATE_FORMAT.format(utcNow.getTime());

// Handles the request by printing message.
System.out.println("Server timer for actor "
+ super.getId() + ": "
+ (message == null ? "" : message + " @ " + utcNowAsString));
System.out.println("Server timer triggered with state "
+ (message == null ? "" : message) + " for actor "
+ super.getId() + "@ " + utcNowAsString);
}

/**
Expand All @@ -148,8 +156,8 @@ public Mono<Void> receiveReminder(String reminderName, Integer state, Duration d
Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
String utcNowAsString = DATE_FORMAT.format(utcNow.getTime());

String message = String.format("Server reminded actor %s of: %s for %d @ %s",
this.getId(), reminderName, state, utcNowAsString);
String message = String.format("Reminder %s with state {%d} triggered for actor %s @ %s",
reminderName, state, this.getId(), utcNowAsString);

// Handles the request by printing message.
System.out.println(message);
Expand Down
105 changes: 77 additions & 28 deletions examples/src/main/java/io/dapr/examples/actors/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl
}

@Override
public void registerReminder() {
public void registerTimer(String state) {
//...
}

@Override
public void registerReminder(int index) {
//...
}

Expand Down Expand Up @@ -115,8 +120,10 @@ import io.dapr.actors.ActorMethod;
*/
@ActorType(name = "DemoActor")
public interface DemoActor {

void registerReminder();

void registerTimer(String state);

void registerReminder(int index);

@ActorMethod(name = "echo_message")
String say(String something);
Expand All @@ -134,12 +141,35 @@ The `@ActorType` annotation indicates the Dapr Java SDK that this interface is a
The `@ActorMethod` annotation can be applied to an interface method to specify configuration for that method. In this example, the `say` method, is renamed to `echo_message` - this can be used when invoking an actor method implemented in a different programming language (like C# or Python) and the method name does not match Java's naming conventions.
Some methods can return a `Mono` object. In these cases, the `@ActorMethod` annotation is used to hint the Dapr Java SDK of the type encapsulated in the `Mono` object. You can read more about Java generic type erasure [here](https://docs.oracle.com/javase/tutorial/java/generics/erasure.html).

<!-- STEP
name: Run Demo Actor Service
match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'Message #2 received from actor at index 1 with ID'
- 'Message #2 received from actor at index 2 with ID'
- 'Message #2 received from actor at index 0 with ID'
- 'Message #1 received from actor at index 1 with ID'
- 'Message #1 received from actor at index 0 with ID'
- 'Message #3 received from actor at index 2 with ID'
- 'Server timer triggered with state ping! {2} for actor'
- 'Server timer triggered with state ping! {1} for actor'
- 'Server timer triggered with state ping! {0} for actor'
- 'Reminder myremind with state {2} triggered for actor'
- 'Reminder myremind with state {0} triggered for actor'
- 'Reminder myremind with state {1} triggered for actor'
background: true
sleep: 10
timeout_seconds: 90
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->


Now, execute the following script in order to run DemoActorService:
```sh
dapr run --components-path ./components/actors --app-id demoactorservice --app-port 3000 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.actors.DemoActorService -p 3000
```

<!-- END_STEP -->
### Running the Actor client

The actor client is a simple java class with a main method that uses the Dapr Actor capabilities in order to create the actors and execute the different methods based on the Actor pattern.
Expand Down Expand Up @@ -167,19 +197,23 @@ public class DemoActorClient {
}
}

private static final void callActorForever(String actorId, DemoActor actor) {
private static final void callActorForever(int index, String actorId, DemoActor actor) {
// First, register reminder.
actor.registerReminder();
actor.registerReminder(index);
// Second register timer.
actor.registerTimer("ping! {" + index + "} ");

// 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.incrementAndGet(1).block();
String message = String.format("Actor %s said message #%d", actorId, messageNumber);
String message = String.format("Message #%d received from actor at index %d with ID %s", messageNumber,
index, actorId);

// Invoke the 'say' method in actor.
String result = actor.say(message);
System.out.println(String.format("Actor %s got a reply: %s", actorId, result));
System.out.println(String.format("Reply %s received from actor at index %d with ID %s ", result,
index, actorId));

try {
// Waits for up to 1 second.
Expand All @@ -199,57 +233,72 @@ Then, the code executes the `callActorForever` private method once per actor. In

Use the follow command to execute the DemoActorClient:

<!-- STEP
name: Run Demo Actor Client
match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'received from actor at index 2 with ID'
- 'received from actor at index 1 with ID'
- 'received from actor at index 0 with ID '
background: true
sleep: 20
timeout_seconds: 45
-->


```sh
dapr run --components-path ./components/actors --app-id demoactorclient -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.actors.DemoActorClient
```

<!-- END_STEP -->

Once running, the `demoactorservice` logs will start displaying the different steps:
First, we can see actors being activated and the `say` method being invoked:
```text
== APP == 2021-03-10 21:08:28,941 {HH:mm:ss.SSS} [http-nio-3000-exec-1] INFO io.dapr.actors.ActorTrace - Actor:b7b8e745-bc1b-44ff-a0d3-c9a71f68956c Activating ...
== APP == 2023-05-23 11:04:47,348 {HH:mm:ss.SSS} [http-nio-3000-exec-5] INFO io.dapr.actors.ActorTrace - Actor:a855706e-f477-4530-9bff-d7b1cd2988f8 Activating ...
== APP == 2021-03-10 21:08:28,941 {HH:mm:ss.SSS} [http-nio-3000-exec-2] INFO io.dapr.actors.ActorTrace - Actor:d0455670-557b-4ff5-ab4c-8743aca9a423 Activating ...
== APP == 2023-05-23 11:04:47,348 {HH:mm:ss.SSS} [http-nio-3000-exec-6] INFO io.dapr.actors.ActorTrace - Actor:4720f646-baaa-4fae-86dd-aec2fc2ead6e Activating ...
== APP == 2021-03-10 21:08:28,941 {HH:mm:ss.SSS} [http-nio-3000-exec-10] INFO io.dapr.actors.ActorTrace - Actor:56d741b6-b685-45df-974b-9e94efb3e7b4 Activating ...
== APP == 2023-05-23 11:04:47,348 {HH:mm:ss.SSS} [http-nio-3000-exec-7] INFO io.dapr.actors.ActorTrace - Actor:d54592a5-5b5b-4925-8974-6cf309fbdbbf Activating ...
== APP == 2021-03-10 21:08:28,941 {HH:mm:ss.SSS} [http-nio-3000-exec-10] INFO io.dapr.actors.ActorTrace - Actor:56d741b6-b685-45df-974b-9e94efb3e7b4 Activated
== APP == 2023-05-23 11:04:47,348 {HH:mm:ss.SSS} [http-nio-3000-exec-5] INFO io.dapr.actors.ActorTrace - Actor:a855706e-f477-4530-9bff-d7b1cd2988f8 Activated
== APP == 2021-03-10 21:08:28,941 {HH:mm:ss.SSS} [http-nio-3000-exec-1] INFO io.dapr.actors.ActorTrace - Actor:b7b8e745-bc1b-44ff-a0d3-c9a71f68956c Activated
== APP == 2023-05-23 11:04:47,348 {HH:mm:ss.SSS} [http-nio-3000-exec-7] INFO io.dapr.actors.ActorTrace - Actor:d54592a5-5b5b-4925-8974-6cf309fbdbbf Activated
== APP == 2021-03-10 21:08:28,941 {HH:mm:ss.SSS} [http-nio-3000-exec-2] INFO io.dapr.actors.ActorTrace - Actor:d0455670-557b-4ff5-ab4c-8743aca9a423 Activated
== APP == 2023-05-23 11:04:47,348 {HH:mm:ss.SSS} [http-nio-3000-exec-6] INFO io.dapr.actors.ActorTrace - Actor:4720f646-baaa-4fae-86dd-aec2fc2ead6e Activated
== APP == Server say method for actor 56d741b6-b685-45df-974b-9e94efb3e7b4: Actor 56d741b6-b685-45df-974b-9e94efb3e7b4 said message #1 @ 2021-03-10 21:08:29.170
== APP == Server say method for actor d54592a5-5b5b-4925-8974-6cf309fbdbbf: Message #2 received from actor at index 1 with ID d54592a5-5b5b-4925-8974-6cf309fbdbbf @ 2023-05-23 11:04:48.459
== APP == Server say method for actor b7b8e745-bc1b-44ff-a0d3-c9a71f68956c: Actor b7b8e745-bc1b-44ff-a0d3-c9a71f68956c said message #1 @ 2021-03-10 21:08:29.170
== APP == Server say method for actor 4720f646-baaa-4fae-86dd-aec2fc2ead6e: Message #4 received from actor at index 2 with ID 4720f646-baaa-4fae-86dd-aec2fc2ead6e @ 2023-05-23 11:04:48.695
== APP == Server say method for actor d0455670-557b-4ff5-ab4c-8743aca9a423: Actor d0455670-557b-4ff5-ab4c-8743aca9a423 said message #1 @ 2021-03-10 21:08:29.170
== APP == Server say method for actor d54592a5-5b5b-4925-8974-6cf309fbdbbf: Message #3 received from actor at index 1 with ID d54592a5-5b5b-4925-8974-6cf309fbdbbf @ 2023-05-23 11:04:48.708
```

Then we can see reminders and timers in action:
```text
== APP == Server timer for actor b7b8e745-bc1b-44ff-a0d3-c9a71f68956c: ping! @ 2021-03-10 21:08:32.945
== APP == Server timer triggered with state ping! {0} for actor a855706e-f477-4530-9bff-d7b1cd2988f8@ 2023-05-23 11:04:49.021
== APP == Server timer for actor d0455670-557b-4ff5-ab4c-8743aca9a423: ping! @ 2021-03-10 21:08:32.945
== APP == Server timer triggered with state ping! {1} for actor d54592a5-5b5b-4925-8974-6cf309fbdbbf@ 2023-05-23 11:04:49.021
== APP == Server timer for actor 56d741b6-b685-45df-974b-9e94efb3e7b4: ping! @ 2021-03-10 21:08:32.945
== APP == Reminder myremind with state {2} triggered for actor 4720f646-baaa-4fae-86dd-aec2fc2ead6e @ 2023-05-23 11:04:52.012
== APP == Server reminded actor b7b8e745-bc1b-44ff-a0d3-c9a71f68956c of: myremind for 1251123938 @ 2021-03-10 21:08:33.007
== APP == Reminder myremind with state {1} triggered for actor d54592a5-5b5b-4925-8974-6cf309fbdbbf @ 2023-05-23 11:04:52.012
== APP == Reminder myremind with state {0} triggered for actor a855706e-f477-4530-9bff-d7b1cd2988f8 @ 2023-05-23 11:04:52.012
```

Finally, the console for `demoactorclient` got the service responses:
```text
== APP == Actor 56d741b6-b685-45df-974b-9e94efb3e7b4 got a reply: 2021-03-10 21:08:29.170
== APP == Actor b7b8e745-bc1b-44ff-a0d3-c9a71f68956c got a reply: 2021-03-10 21:08:29.170
== APP == Reply 2023-05-23 11:04:49.288 received from actor at index 0 with ID a855706e-f477-4530-9bff-d7b1cd2988f8
== APP == Actor d0455670-557b-4ff5-ab4c-8743aca9a423 got a reply: 2021-03-10 21:08:29.170
== APP == Reply 2023-05-23 11:04:49.408 received from actor at index 0 with ID a855706e-f477-4530-9bff-d7b1cd2988f8
== APP == Actor d0455670-557b-4ff5-ab4c-8743aca9a423 got a reply: 2021-03-10 21:08:29.292
== APP == Reply 2023-05-23 11:04:49.515 received from actor at index 1 with ID d54592a5-5b5b-4925-8974-6cf309fbdbbf
== APP == Actor 56d741b6-b685-45df-974b-9e94efb3e7b4 got a reply: 2021-03-10 21:08:29.752
== APP == Reply 2023-05-23 11:04:49.740 received from actor at index 0 with ID a855706e-f477-4530-9bff-d7b1cd2988f8
== APP == Actor 56d741b6-b685-45df-974b-9e94efb3e7b4 got a reply: 2021-03-10 21:08:29.804
== APP == Reply 2023-05-23 11:04:49.863 received from actor at index 2 with ID 4720f646-baaa-4fae-86dd-aec2fc2ead6e
```

For more details on Dapr SpringBoot integration, please refer to [Dapr Spring Boot](../../../springboot/DaprApplication.java) Application implementation.
Expand Down

0 comments on commit 4117801

Please sign in to comment.