Strong typed actors. (#243)

This commit is contained in:
Artur Souza 2020-02-25 15:45:28 -08:00 committed by GitHub
parent 85e9d2de02
commit 7fd4e39b56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 346 additions and 88 deletions

View File

@ -5,11 +5,14 @@
package io.dapr.examples.actors.http;
import io.dapr.actors.ActorMethod;
import io.dapr.actors.ActorType;
import reactor.core.publisher.Mono;
/**
* Example of implementation of an Actor.
*/
@ActorType(name = "DemoActor")
public interface DemoActor {
void registerReminder();
@ -18,5 +21,6 @@ public interface DemoActor {
void clock(String message);
@ActorMethod(returns = Integer.class)
Mono<Integer> incrementAndGet(int delta);
}

View File

@ -6,7 +6,6 @@
package io.dapr.examples.actors.http;
import io.dapr.actors.ActorId;
import io.dapr.actors.client.ActorProxy;
import io.dapr.actors.client.ActorProxyBuilder;
import java.util.ArrayList;
@ -30,16 +29,17 @@ public class DemoActorClient {
* @throws InterruptedException If program has been interrupted.
*/
public static void main(String[] args) throws InterruptedException {
ActorProxyBuilder builder = new ActorProxyBuilder("DemoActor");
ActorProxyBuilder<DemoActor> builder = new ActorProxyBuilder(DemoActor.class);
List<Thread> threads = new ArrayList<>(NUM_ACTORS);
// Creates multiple actors.
for (int i = 0; i < NUM_ACTORS; i++) {
ActorProxy actor = builder.build(ActorId.createRandom());
ActorId actorId = ActorId.createRandom();
DemoActor actor = builder.build(actorId);
// Start a thread per actor.
Thread thread = new Thread(() -> callActorForever(actor));
Thread thread = new Thread(() -> callActorForever(actorId.toString(), actor));
thread.start();
threads.add(thread);
}
@ -54,21 +54,22 @@ public class DemoActorClient {
/**
* Makes multiple method calls into actor until interrupted.
* @param actorId Actor's identifier.
* @param actor Actor to be invoked.
*/
private static final void callActorForever(ActorProxy actor) {
private static final void callActorForever(String actorId, DemoActor actor) {
// First, register reminder.
actor.invokeActorMethod("registerReminder").block();
actor.registerReminder();
// 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);
int messageNumber = actor.incrementAndGet(1).block();
String message = String.format("Actor %s said message #%d", actorId, 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));
String result = actor.say(message);
System.out.println(String.format("Actor %s got a reply: %s", actorId, result));
try {
// Waits for up to 1 second.

View File

@ -8,7 +8,6 @@ package io.dapr.examples.actors.http;
import io.dapr.actors.ActorId;
import io.dapr.actors.runtime.AbstractActor;
import io.dapr.actors.runtime.ActorRuntimeContext;
import io.dapr.actors.runtime.ActorType;
import io.dapr.actors.runtime.Remindable;
import reactor.core.publisher.Mono;
@ -21,7 +20,6 @@ import java.util.TimeZone;
/**
* Implementation of the DemoActor for the server side.
*/
@ActorType(name = "DemoActor")
public class DemoActorImpl extends AbstractActor implements DemoActor, Remindable<Integer> {
/**

View File

@ -35,7 +35,7 @@ mvn install
### Running the Demo actor service
The first element is to run is `DemoActorService`. Its job is registering the `DemoActor` implementation in the Dapr's Actor runtime. In `DemoActorService.java` file, you will find the `DemoActorService` class and the `main` method. See the code snippet below:
The first Java class is `DemoActorService`. Its job is to register an implementation of `DemoActor` in the Dapr's Actor runtime. In `DemoActorService.java` file, you will find the `DemoActorService` class and the `main` method. See the code snippet below:
```java
@SpringBootApplication
@ -59,7 +59,6 @@ This application uses `ActorRuntime.getInstance().registerActor()` in order to r
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> {
//...
@ -99,7 +98,28 @@ public class DemoActorImpl extends AbstractActor implements DemoActor, Remindabl
}
}
```
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()`.
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()`. The `DemoActor` interface is used by the Actor runtime and also client. See how `DemoActor` interface can be annotated as Dapr Actor.
```java
/**
* Example of implementation of an Actor.
*/
@ActorType(name = "DemoActor")
public interface DemoActor {
void registerReminder();
String say(String something);
void clock(String message);
@ActorMethod(returns = Integer.class)
Mono<Integer> incrementAndGet(int delta);
}
```
The `@ActorType` annotation indicates the Dapr Java SDK that this interface is an Actor Type, allowing a name for the type to be defined. 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:
@ -120,31 +140,32 @@ public class DemoActorClient {
private static final int NUM_ACTORS = 3;
public static void main(String[] args) throws InterruptedException {
ActorProxyBuilder<DemoActor> builder = new ActorProxyBuilder(DemoActor.class);
///...
for (int i = 0; i < NUM_ACTORS; i++) {
ActorProxy actor = builder.build(ActorId.createRandom());
DemoActor actor = builder.build(ActorId.createRandom());
// Start a thread per actor.
Thread thread = new Thread(() -> callActorForever(actor));
Thread thread = new Thread(() -> callActorForever(actorId.toString(), actor));
thread.start();
threads.add(thread);
}
///...
}
private static final void callActorForever(ActorProxy actor) {
private static final void callActorForever(String actorId, DemoActor actor) {
// First, register reminder.
actor.invokeActorMethod("registerReminder").block();
actor.registerReminder();
// 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);
int messageNumber = actor.incrementAndGet(1).block();
String message = String.format("Actor %s said message #%d", actorId, 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));
String result = actor.say(message);
System.out.println(String.format("Actor %s got a reply: %s", actorId, result));
try {
// Waits for up to 1 second.
@ -158,7 +179,7 @@ public class DemoActorClient {
}
```
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.
First, the client defines how many actors it is going to create. Then the main method declares a `ActorProxyBuilder` to create instances of the `DemoActor` interface, which are implemented automatically by the SDK and make remote calls to the equivalent methods in Actor runtime. The code executes the `callActorForever` private method once per actor. 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:
@ -180,5 +201,10 @@ After invoking `incrementAndGet`, the code invokes `say` method (you'll see thes
On the other hand, the console for `DemoActorService` is also responding to the remote invocations:
![actordemo2](../../../../../../resources/img/demo-actor-service.png)
For more details on Dapr SpringBoot integration, please refer to [Dapr Spring Boot](../../springboot/DaprApplication.java) Application implementation.
For more details on Dapr SpringBoot integration, please refer to [Dapr Spring Boot](../../springboot/DaprApplication.java) Application implementation.
### Limitations
Currently, these are the limitations in the Java SDK for Dapr:
* Actor interface cannot have overloaded methods (methods with same name but different signature).
* Actor methods can only have zero or one parameter.

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
package io.dapr.actors;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActorMethod {
/**
* Actor's method return type. This is required when result object is within a Mono response.
*
* @return Actor's method return type.
*/
Class returns();
}

View File

@ -3,7 +3,7 @@
* Licensed under the MIT License.
*/
package io.dapr.actors.runtime;
package io.dapr.actors;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
package io.dapr.actors;
public final class ActorUtils {
/**
* Finds the actor type name for the given class or interface.
*
* @param actorClass Class or interface for Actor Type.
* @return Name for Actor Type.
*/
public static String findActorTypeName(Class<?> actorClass) {
if (actorClass == null) {
throw new IllegalArgumentException("ActorClass is required.");
}
Class<?> node = actorClass;
while ((node != null) && (node.getAnnotation(ActorType.class) == null)) {
node = node.getSuperclass();
}
if (node == null) {
// No annotation found in parent classes, so we scan interfaces.
for (Class<?> interfaze : actorClass.getInterfaces()) {
if (interfaze.getAnnotation(ActorType.class) != null) {
node = interfaze;
break;
}
}
}
if (node == null) {
// No ActorType annotation found, so we use the class name.
return actorClass.getSimpleName();
}
ActorType actorTypeAnnotation = node.getAnnotation(ActorType.class);
return actorTypeAnnotation != null ? actorTypeAnnotation.name() : actorClass.getSimpleName();
}
}

View File

@ -6,14 +6,17 @@
package io.dapr.actors.client;
import io.dapr.actors.ActorId;
import io.dapr.actors.ActorUtils;
import io.dapr.client.DaprHttpBuilder;
import io.dapr.serializer.DaprObjectSerializer;
import io.dapr.serializer.DefaultObjectSerializer;
import java.lang.reflect.Proxy;
/**
* Builder to generate an ActorProxy instance. Builder can be reused for multiple instances.
*/
public class ActorProxyBuilder {
public class ActorProxyBuilder<T> {
/**
* Builder for Dapr's raw http client.
@ -25,6 +28,11 @@ public class ActorProxyBuilder {
*/
private final String actorType;
/**
* Actor's type class.
*/
private final Class<T> clazz;
/**
* Dapr's object serializer.
*/
@ -35,15 +43,31 @@ public class ActorProxyBuilder {
*
* {@link DefaultObjectSerializer} is not recommended for production scenarios.
*
* @param actorType Actor's type.
* @param actorTypeClass Actor's type class.
*/
public ActorProxyBuilder(String actorType) {
public ActorProxyBuilder(Class<T> actorTypeClass) {
this(ActorUtils.findActorTypeName(actorTypeClass), actorTypeClass);
}
/**
* Instantiates a new builder for a given Actor type, using {@link DefaultObjectSerializer} by default.
*
* {@link DefaultObjectSerializer} is not recommended for production scenarios.
*
* @param actorType Actor's type.
* @param actorTypeClass Actor's type class.
*/
public ActorProxyBuilder(String actorType, Class<T> actorTypeClass) {
if ((actorType == null) || actorType.isEmpty()) {
throw new IllegalArgumentException("ActorType is required.");
}
if (actorTypeClass == null) {
throw new IllegalArgumentException("ActorTypeClass is required.");
}
this.actorType = actorType;
this.objectSerializer = new DefaultObjectSerializer();
this.clazz = actorTypeClass;
}
/**
@ -67,16 +91,26 @@ public class ActorProxyBuilder {
* @param actorId Actor's identifier.
* @return New instance of ActorProxy.
*/
public ActorProxy build(ActorId actorId) {
public T build(ActorId actorId) {
if (actorId == null) {
throw new IllegalArgumentException("Cannot instantiate an Actor without Id.");
}
return new ActorProxyImpl(
this.actorType,
actorId,
this.objectSerializer,
new DaprHttpClient(this.daprHttpBuilder.build()));
ActorProxyImpl proxy = new ActorProxyImpl(
this.actorType,
actorId,
this.objectSerializer,
new DaprHttpClient(this.daprHttpBuilder.build()));
if (this.clazz.equals(ActorProxy.class)) {
// If users want to use the not strongly typed API, we respect that here.
return (T) proxy;
}
return (T) Proxy.newProxyInstance(
ActorProxyImpl.class.getClassLoader(),
new Class[]{this.clazz},
proxy);
}
}

View File

@ -6,21 +6,18 @@
package io.dapr.actors.client;
import io.dapr.actors.ActorId;
import io.dapr.actors.runtime.ActorObjectSerializer;
import io.dapr.actors.ActorMethod;
import io.dapr.serializer.DaprObjectSerializer;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* Implements a proxy client for an Actor's instance.
*/
class ActorProxyImpl implements ActorProxy {
/**
* Serializer used for internal objects.
*/
private static final ActorObjectSerializer INTERNAL_SERIALIZER = new ActorObjectSerializer();
class ActorProxyImpl implements ActorProxy, InvocationHandler {
/**
* Actor's identifier for this Actor instance.
@ -107,6 +104,45 @@ class ActorProxyImpl implements ActorProxy {
return this.daprClient.invokeActorMethod(actorType, actorId.toString(), methodName, this.serialize(data)).then();
}
/**
* Handles an invocation via reflection.
*
* @param proxy Interface or class being invoked.
* @param method Method being invoked.
* @param args Arguments to invoke method.
* @return Response object for the invocation.
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
if (method.getParameterCount() > 1) {
throw new UnsupportedOperationException("Actor methods can only have zero or one arguments.");
}
if (method.getParameterCount() == 0) {
if (method.getReturnType().equals(Mono.class)) {
ActorMethod actorMethodAnnotation = method.getDeclaredAnnotation(ActorMethod.class);
if (actorMethodAnnotation == null) {
return invokeActorMethod(method.getName());
}
return invokeActorMethod(method.getName(), actorMethodAnnotation.returns());
}
return invokeActorMethod(method.getName(), method.getReturnType()).block();
}
if (method.getReturnType().equals(Mono.class)) {
ActorMethod actorMethodAnnotation = method.getDeclaredAnnotation(ActorMethod.class);
if (actorMethodAnnotation == null) {
return invokeActorMethod(method.getName(), args[0]);
}
return invokeActorMethod(method.getName(), args[0], actorMethodAnnotation.returns());
}
return invokeActorMethod(method.getName(), args[0], method.getReturnType()).block();
}
/**
* Extracts the response object from the Actor's method result.
*
@ -138,5 +174,4 @@ class ActorProxyImpl implements ActorProxy {
throw new RuntimeException(e);
}
}
}

View File

@ -5,6 +5,8 @@
package io.dapr.actors.runtime;
import io.dapr.actors.ActorUtils;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
@ -143,9 +145,8 @@ final class ActorTypeInformation<T> {
boolean isAbstract = Modifier.isAbstract(actorClass.getModifiers());
boolean isRemindable = ActorTypeUtilities.isRemindableActor(actorClass);
ActorType actorTypeAnnotation = actorClass.getAnnotation(ActorType.class);
String typeName = actorTypeAnnotation != null ? actorTypeAnnotation.name() : actorClass.getSimpleName();
String typeName = ActorUtils.findActorTypeName(actorClass);
return new ActorTypeInformation(typeName, actorClass, Arrays.asList(actorInterfaces), isAbstract, isRemindable);
}

View File

@ -6,7 +6,7 @@
package io.dapr.actors.client;
import io.dapr.actors.ActorId;
import io.dapr.serializer.DefaultObjectSerializer;
import io.dapr.actors.ActorType;
import org.junit.Assert;
import org.junit.Test;
@ -14,28 +14,28 @@ public class ActorProxyBuilderTest {
@Test(expected = IllegalArgumentException.class)
public void buildWithNullActorId() {
new ActorProxyBuilder("test")
new ActorProxyBuilder("test", Object.class)
.build(null);
}
@Test(expected = IllegalArgumentException.class)
public void buildWithEmptyActorType() {
new ActorProxyBuilder("")
new ActorProxyBuilder("", Object.class)
.build(new ActorId("100"));
}
@Test(expected = IllegalArgumentException.class)
public void buildWithNullActorType() {
new ActorProxyBuilder(null)
new ActorProxyBuilder(null, Object.class)
.build(new ActorId("100"));
}
@Test(expected = IllegalArgumentException.class)
public void buildWithNullSerializer() {
new ActorProxyBuilder("MyActor")
new ActorProxyBuilder("MyActor", Object.class)
.withObjectSerializer(null)
.build(new ActorId("100"));
@ -43,12 +43,34 @@ public class ActorProxyBuilderTest {
@Test()
public void build() {
ActorProxyBuilder builder = new ActorProxyBuilder("test");
ActorProxyBuilder<ActorProxy> builder = new ActorProxyBuilder("test", ActorProxy.class);
ActorProxy actorProxy = builder.build(new ActorId("100"));
Assert.assertNotNull(actorProxy);
Assert.assertEquals("test", actorProxy.getActorType());
Assert.assertEquals("100", actorProxy.getActorId().toString());
}
@Test()
public void buildWithType() {
ActorProxyBuilder<MyActor> builder = new ActorProxyBuilder(MyActor.class);
MyActor actorProxy = builder.build(new ActorId("100"));
Assert.assertNotNull(actorProxy);
}
@Test()
public void buildWithTypeDefaultName() {
ActorProxyBuilder<MyActorWithDefaultName> builder = new ActorProxyBuilder(MyActorWithDefaultName.class);
MyActorWithDefaultName actorProxy = builder.build(new ActorId("100"));
Assert.assertNotNull(actorProxy);
}
@ActorType(name = "MyActor")
public interface MyActor {
}
public interface MyActorWithDefaultName {
}
}

View File

@ -36,16 +36,16 @@ public class ActorProxyImplTest {
public void invokeActorMethodWithoutDataWithReturnType() {
final DaprClient daprClient = mock(DaprClient.class);
Mono<byte[]> daprResponse = Mono.just(
"{\n\t\t\"propertyA\": \"valueA\",\n\t\t\"propertyB\": \"valueB\"\n\t}".getBytes());
"{\n\t\t\"propertyA\": \"valueA\",\n\t\t\"propertyB\": \"valueB\"\n\t}".getBytes());
when(daprClient.invokeActorMethod(anyString(), anyString(), anyString(), Mockito.isNull()))
.thenReturn(daprResponse);
.thenReturn(daprResponse);
final ActorProxy actorProxy = new ActorProxyImpl(
"myActorType",
new ActorId("100"),
new DefaultObjectSerializer(),
daprClient);
"myActorType",
new ActorId("100"),
new DefaultObjectSerializer(),
daprClient);
Mono<MyData> result = actorProxy.invokeActorMethod("getData", MyData.class);
MyData myData = result.block();
@ -54,6 +54,27 @@ public class ActorProxyImplTest {
Assert.assertEquals("valueB", myData.getPropertyB());// propertyB=null
}
@Test()
public void invokeActorMethodWithoutDataWithReturnTypeViaReflection() throws NoSuchMethodException {
final DaprClient daprClient = mock(DaprClient.class);
Mono<byte[]> daprResponse = Mono.just(
"{\n\t\t\"propertyA\": \"valueA\",\n\t\t\"propertyB\": \"valueB\"\n\t}".getBytes());
when(daprClient.invokeActorMethod(anyString(), anyString(), anyString(), Mockito.isNull()))
.thenReturn(daprResponse);
final ActorProxyImpl actorProxy = new ActorProxyImpl(
"myActorType",
new ActorId("100"),
new DefaultObjectSerializer(),
daprClient);
MyData myData = (MyData) actorProxy.invoke(actorProxy, Actor.class.getMethod("getData"), null);
Assert.assertNotNull(myData);
Assert.assertEquals("valueA", myData.getPropertyA());
Assert.assertEquals("valueB", myData.getPropertyB());// propertyB=null
}
@Test()
public void invokeActorMethodWithoutDataWithEmptyReturnType() {
final DaprClient daprClient = mock(DaprClient.class);
@ -250,6 +271,10 @@ public class ActorProxyImplTest {
Assert.assertNull(emptyResponse);
}
interface Actor {
MyData getData();
}
static class MyData {
/// Gets or sets the value for PropertyA.

View File

@ -6,6 +6,7 @@
package io.dapr.actors.runtime;
import io.dapr.actors.ActorId;
import io.dapr.actors.ActorType;
import io.dapr.actors.client.ActorProxy;
import io.dapr.actors.client.ActorProxyForTestsImpl;
import io.dapr.actors.client.DaprClientStub;

View File

@ -6,6 +6,7 @@
package io.dapr.actors.runtime;
import io.dapr.actors.ActorId;
import io.dapr.actors.ActorType;
import io.dapr.serializer.DefaultObjectSerializer;
import org.junit.Assert;
import org.junit.Test;

View File

@ -6,6 +6,7 @@
package io.dapr.actors.runtime;
import io.dapr.actors.ActorId;
import io.dapr.actors.ActorType;
import io.dapr.actors.client.ActorProxy;
import io.dapr.actors.client.ActorProxyForTestsImpl;
import io.dapr.actors.client.DaprClientStub;

View File

@ -6,6 +6,7 @@
package io.dapr.actors.runtime;
import io.dapr.actors.ActorId;
import io.dapr.actors.ActorType;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;

View File

@ -6,6 +6,7 @@
package io.dapr.actors.runtime;
import io.dapr.actors.ActorId;
import io.dapr.actors.ActorType;
import io.dapr.actors.client.ActorProxy;
import io.dapr.actors.client.ActorProxyForTestsImpl;
import io.dapr.actors.client.DaprClientStub;
@ -604,23 +605,23 @@ public class ActorStatefulTest {
DaprClientStub daprClient = mock(DaprClientStub.class);
when(daprClient.invokeActorMethod(
eq(context.getActorTypeInformation().getName()),
eq(actorId.toString()),
any(),
any()))
.thenAnswer(invocationOnMock ->
this.manager.invokeMethod(
new ActorId(invocationOnMock.getArgument(1, String.class)),
invocationOnMock.getArgument(2, String.class),
invocationOnMock.getArgument(3, byte[].class)));
eq(context.getActorTypeInformation().getName()),
eq(actorId.toString()),
any(),
any()))
.thenAnswer(invocationOnMock ->
this.manager.invokeMethod(
new ActorId(invocationOnMock.getArgument(1, String.class)),
invocationOnMock.getArgument(2, String.class),
invocationOnMock.getArgument(3, byte[].class)));
this.manager.activateActor(actorId).block();
return new ActorProxyForTestsImpl(
context.getActorTypeInformation().getName(),
actorId,
new DefaultObjectSerializer(),
daprClient);
context.getActorTypeInformation().getName(),
actorId,
new DefaultObjectSerializer(),
daprClient);
}
private byte[] createReminderParams(String data) throws IOException {

View File

@ -5,6 +5,7 @@
package io.dapr.actors.runtime;
import io.dapr.actors.ActorType;
import org.junit.Assert;
import org.junit.Test;
import reactor.core.publisher.Mono;
@ -16,6 +17,13 @@ import java.time.Duration;
*/
public class ActorTypeInformationTest {
/**
* Actor interfaced used in this test only.
*/
@ActorType(name = "MyActorWithAnnotation")
private interface MyActorAnnotated {
}
/**
* Actor interfaced used in this test only.
*/
@ -99,6 +107,27 @@ public class ActorTypeInformationTest {
Assert.assertTrue(info.getInterfaces().contains(MyActor.class));
}
/**
* Checks information for an actor renamed via annotation at interface.
*/
@Test
public void renamedWithAnnotationAtInterface() {
class A extends AbstractActor implements MyActorAnnotated {
A() {
super(null, null);
}
}
ActorTypeInformation info = ActorTypeInformation.create(A.class);
Assert.assertNotNull(info);
Assert.assertEquals("MyActorWithAnnotation", info.getName());
Assert.assertEquals(A.class, info.getImplementationClass());
Assert.assertFalse(info.isAbstractClass());
Assert.assertFalse(info.isRemindable());
Assert.assertEquals(1, info.getInterfaces().size());
Assert.assertTrue(info.getInterfaces().contains(MyActorAnnotated.class));
}
/**
* Checks information for an actor is invalid due to an non-actor parent.
*/

View File

@ -6,6 +6,7 @@
package io.dapr.actors.runtime;
import io.dapr.actors.ActorId;
import io.dapr.actors.ActorType;
import io.dapr.actors.client.ActorProxy;
import io.dapr.actors.client.ActorProxyForTestsImpl;
import io.dapr.actors.client.DaprClientStub;

View File

@ -6,6 +6,7 @@
package io.dapr.actors.runtime;
import io.dapr.actors.ActorId;
import io.dapr.actors.ActorType;
import io.dapr.actors.client.ActorProxy;
import io.dapr.actors.client.ActorProxyForTestsImpl;
import io.dapr.actors.client.DaprClientStub;

View File

@ -9,6 +9,7 @@ import io.dapr.actors.ActorId;
import io.dapr.actors.client.ActorProxy;
import io.dapr.actors.client.ActorProxyBuilder;
import io.dapr.it.BaseIT;
import io.dapr.it.actors.services.springboot.DemoActor;
import io.dapr.it.actors.services.springboot.DemoActorService;
import io.dapr.it.services.EmptyService;
import io.dapr.serializer.DefaultObjectSerializer;
@ -39,30 +40,29 @@ public class ActivationDeactivationIT extends BaseIT {
60000);
final AtomicInteger atomicInteger = new AtomicInteger(1);
String actorType = "DemoActorTest";
logger.debug("Creating proxy builder");
ActorProxyBuilder proxyBuilder = new ActorProxyBuilder(actorType);
ActorProxyBuilder<DemoActor> proxyBuilder = new ActorProxyBuilder(DemoActor.class);
logger.debug("Creating actorId");
ActorId actorId1 = new ActorId(Integer.toString(atomicInteger.getAndIncrement()));
logger.debug("Building proxy");
ActorProxy proxy = proxyBuilder.build(actorId1);
DemoActor proxy = proxyBuilder.build(actorId1);
callWithRetry(() -> {
logger.debug("Invoking Say from Proxy");
String sayResponse = proxy.invokeActorMethod("say", "message", String.class).block();
String sayResponse = proxy.say("message");
logger.debug("asserting not null response: [" + sayResponse + "]");
assertNotNull(sayResponse);
}, 60000);
logger.debug("Retrieving active Actors");
List<String> activeActors = proxy.invokeActorMethod("retrieveActiveActors", null, List.class).block();
List<String> activeActors = proxy.retrieveActiveActors();
logger.debug("Active actors: [" + activeActors.toString() + "]");
assertTrue("Expecting actorId:[" + actorId1.toString() + "]", activeActors.contains(actorId1.toString()));
ActorId actorId2 = new ActorId(Integer.toString(atomicInteger.getAndIncrement()));
ActorProxy proxy2 = proxyBuilder.build(actorId2);
DemoActor proxy2 = proxyBuilder.build(actorId2);
callWithRetry(() -> {
List<String> activeActorsSecondTry = proxy2.invokeActorMethod("retrieveActiveActors", null, List.class).block();
List<String> activeActorsSecondTry = proxy2.retrieveActiveActors();
logger.debug("Active actors: [" + activeActorsSecondTry.toString() + "]");
assertFalse("NOT Expecting actorId:[" + actorId1.toString() + "]", activeActorsSecondTry.contains(actorId1.toString()));
}, 15000);

View File

@ -41,7 +41,7 @@ public class ActorStateIT extends BaseIT {
ActorId actorId = new ActorId(Long.toString(System.currentTimeMillis()));
String actorType = "StatefulActorTest";
logger.debug("Building proxy ...");
ActorProxyBuilder proxyBuilder = new ActorProxyBuilder(actorType);
ActorProxyBuilder<ActorProxy> proxyBuilder = new ActorProxyBuilder(actorType, ActorProxy.class);
ActorProxy proxy = proxyBuilder.build(actorId);
callWithRetry(() -> {

View File

@ -83,7 +83,7 @@ public class ActorTurnBasedConcurrencyIT extends BaseIT {
String actorType="MyActorTest";
logger.debug("Creating proxy builder");
ActorProxyBuilder proxyBuilder = new ActorProxyBuilder(actorType);
ActorProxyBuilder<ActorProxy> proxyBuilder = new ActorProxyBuilder(actorType, ActorProxy.class);
logger.debug("Creating actorId");
ActorId actorId1 = new ActorId(ACTOR_ID);
logger.debug("Building proxy");

View File

@ -6,9 +6,9 @@
package io.dapr.it.actors.app;
import io.dapr.actors.ActorId;
import io.dapr.actors.ActorType;
import io.dapr.actors.runtime.AbstractActor;
import io.dapr.actors.runtime.ActorRuntimeContext;
import io.dapr.actors.runtime.ActorType;
import io.dapr.actors.runtime.Remindable;
import io.dapr.it.actors.MethodEntryTracker;
import reactor.core.publisher.Mono;
@ -16,7 +16,11 @@ import reactor.core.publisher.Mono;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.*;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.TimeZone;
@ActorType(name = "MyActorTest")
public class MyActorImpl extends AbstractActor implements MyActor, Remindable<String> {

View File

@ -5,8 +5,11 @@
package io.dapr.it.actors.services.springboot;
import io.dapr.actors.ActorType;
import java.util.List;
@ActorType(name = "DemoActorTest")
public interface DemoActor {
String say(String something);

View File

@ -8,15 +8,16 @@ package io.dapr.it.actors.services.springboot;
import io.dapr.actors.ActorId;
import io.dapr.actors.runtime.AbstractActor;
import io.dapr.actors.runtime.ActorRuntimeContext;
import io.dapr.actors.runtime.ActorType;
import reactor.core.publisher.Mono;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.*;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.TimeZone;
@ActorType(name = "DemoActorTest")
public class DemoActorImpl extends AbstractActor implements DemoActor {
public static final List<String> ACTIVE_ACTOR = new ArrayList<>();

View File

@ -28,8 +28,7 @@ public class DemoActorService {
ActorRuntime.getInstance().getConfig().setActorScanInterval(Duration.ofSeconds(2));
ActorRuntime.getInstance().getConfig().setDrainOngoingCallTimeout(Duration.ofSeconds(10));
ActorRuntime.getInstance().getConfig().setDrainBalancedActors(true);
ActorRuntime.getInstance().registerActor(
DemoActorImpl.class, new DefaultObjectSerializer(), new DefaultObjectSerializer());
ActorRuntime.getInstance().registerActor(DemoActorImpl.class);
DaprApplication.start(port);
}
}

View File

@ -6,9 +6,9 @@
package io.dapr.it.actors.services.springboot;
import io.dapr.actors.ActorId;
import io.dapr.actors.ActorType;
import io.dapr.actors.runtime.AbstractActor;
import io.dapr.actors.runtime.ActorRuntimeContext;
import io.dapr.actors.runtime.ActorType;
@ActorType(name = "StatefulActorTest")
public class StatefulActorImpl extends AbstractActor implements StatefulActor {