From 787173eff1e6ff7033f02ffc0b707a143f7fbf63 Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Thu, 25 May 2023 01:04:24 +0530 Subject: [PATCH] auto validate actors (#863) Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .github/workflows/validate.yml | 4 + .../io/dapr/examples/actors/DemoActor.java | 4 +- .../dapr/examples/actors/DemoActorClient.java | 15 ++- .../dapr/examples/actors/DemoActorImpl.java | 24 ++-- .../java/io/dapr/examples/actors/README.md | 105 +++++++++++++----- 5 files changed, 110 insertions(+), 42 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 038c2db0b..104792700 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -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: | diff --git a/examples/src/main/java/io/dapr/examples/actors/DemoActor.java b/examples/src/main/java/io/dapr/examples/actors/DemoActor.java index 2dc69ec91..fcb6a7aee 100644 --- a/examples/src/main/java/io/dapr/examples/actors/DemoActor.java +++ b/examples/src/main/java/io/dapr/examples/actors/DemoActor.java @@ -23,7 +23,9 @@ import reactor.core.publisher.Mono; @ActorType(name = "DemoActor") public interface DemoActor { - void registerReminder(); + void registerTimer(String state); + + void registerReminder(int index); @ActorMethod(name = "echo_message") String say(String something); diff --git a/examples/src/main/java/io/dapr/examples/actors/DemoActorClient.java b/examples/src/main/java/io/dapr/examples/actors/DemoActorClient.java index cb4e45820..b19ca59b7 100644 --- a/examples/src/main/java/io/dapr/examples/actors/DemoActorClient.java +++ b/examples/src/main/java/io/dapr/examples/actors/DemoActorClient.java @@ -49,7 +49,8 @@ public class DemoActorClient { 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); } @@ -68,19 +69,23 @@ public class DemoActorClient { * @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. diff --git a/examples/src/main/java/io/dapr/examples/actors/DemoActorImpl.java b/examples/src/main/java/io/dapr/examples/actors/DemoActorImpl.java index 53d5dc273..39297d0e9 100644 --- a/examples/src/main/java/io/dapr/examples/actors/DemoActorImpl.java +++ b/examples/src/main/java/io/dapr/examples/actors/DemoActorImpl.java @@ -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(); } @@ -56,10 +63,11 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl * 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(); } @@ -120,9 +128,9 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl 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); } /** @@ -148,8 +156,8 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl 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); diff --git a/examples/src/main/java/io/dapr/examples/actors/README.md b/examples/src/main/java/io/dapr/examples/actors/README.md index 3f9100ff5..dd4cbfdaa 100644 --- a/examples/src/main/java/io/dapr/examples/actors/README.md +++ b/examples/src/main/java/io/dapr/examples/actors/README.md @@ -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) { //... } @@ -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); @@ -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). + + + 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 ``` - + ### 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. @@ -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. @@ -199,57 +233,72 @@ Then, the code executes the `callActorForever` private method once per actor. In Use the follow command to execute the DemoActorClient: + + + ```sh dapr run --components-path ./components/actors --app-id demoactorclient -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.actors.DemoActorClient ``` + + 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 == Reply 2023-05-23 11:04:49.288 received from actor at index 0 with ID a855706e-f477-4530-9bff-d7b1cd2988f8 -== 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.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.170 +== APP == Reply 2023-05-23 11:04:49.515 received from actor at index 1 with ID d54592a5-5b5b-4925-8974-6cf309fbdbbf -== 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.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.752 - -== 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.