Support ActorMethod to change method's name. (#448)

This commit is contained in:
Artur Souza 2021-01-19 23:34:14 -08:00 committed by GitHub
parent d43272fdc6
commit 446cd17e18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 143 additions and 13 deletions

View File

@ -17,6 +17,7 @@ public interface DemoActor {
void registerReminder();
@ActorMethod(name = "echo_message")
String say(String something);
void clock(String message);

View File

@ -105,6 +105,8 @@ 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()`. The `DemoActor` interface is used by the Actor runtime and also client. See how `DemoActor` interface can be annotated as Dapr Actor.
```java
import io.dapr.actors.ActorMethod;
/**
* Example of implementation of an Actor.
*/
@ -113,6 +115,7 @@ public interface DemoActor {
void registerReminder();
@ActorMethod(name = "echo_message")
String say(String something);
void clock(String message);
@ -123,7 +126,10 @@ public interface DemoActor {
```
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).
The `@ActorType` annotation indicates the Dapr Java SDK that this interface is an Actor Type, allowing a name for the type to be defined.
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:

View File

@ -21,5 +21,13 @@ public @interface ActorMethod {
*
* @return Actor's method return type.
*/
Class returns();
Class returns() default Undefined.class;
/**
* Actor's method name. This is optional and will override the method's default name for actor invocation.
*
* @return Actor's method name.
*/
String name() default "";
}

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
package io.dapr.actors;
/**
* Internal class to represent the undefined value for an optional Class attribute.
*/
final class Undefined {
private Undefined() {
}
}

View File

@ -21,6 +21,8 @@ import java.lang.reflect.Method;
*/
class ActorProxyImpl implements ActorProxy, InvocationHandler {
private static final String UNDEFINED_CLASS_NAME = "io.dapr.actors.Undefined";
/**
* Actor's identifier for this Actor instance.
*/
@ -136,29 +138,33 @@ class ActorProxyImpl implements ActorProxy, InvocationHandler {
throw new UnsupportedOperationException("Actor methods can only have zero or one arguments.");
}
ActorMethod actorMethodAnnotation = method.getDeclaredAnnotation(ActorMethod.class);
String methodName = method.getName();
if ((actorMethodAnnotation != null) && !actorMethodAnnotation.name().isEmpty()) {
methodName = actorMethodAnnotation.name();
}
if (method.getParameterCount() == 0) {
if (method.getReturnType().equals(Mono.class)) {
ActorMethod actorMethodAnnotation = method.getDeclaredAnnotation(ActorMethod.class);
if (actorMethodAnnotation == null) {
return invokeMethod(method.getName());
if ((actorMethodAnnotation == null) || UNDEFINED_CLASS_NAME.equals(actorMethodAnnotation.returns().getName())) {
return invokeMethod(methodName);
}
return invokeMethod(method.getName(), actorMethodAnnotation.returns());
return invokeMethod(methodName, actorMethodAnnotation.returns());
}
return invokeMethod(method.getName(), method.getReturnType()).block();
return invokeMethod(methodName, method.getReturnType()).block();
}
if (method.getReturnType().equals(Mono.class)) {
ActorMethod actorMethodAnnotation = method.getDeclaredAnnotation(ActorMethod.class);
if (actorMethodAnnotation == null) {
return invokeMethod(method.getName(), args[0]);
if ((actorMethodAnnotation == null) || UNDEFINED_CLASS_NAME.equals(actorMethodAnnotation.returns().getName())) {
return invokeMethod(methodName, args[0]);
}
return invokeMethod(method.getName(), args[0], actorMethodAnnotation.returns());
return invokeMethod(methodName, args[0], actorMethodAnnotation.returns());
}
return invokeMethod(method.getName(), args[0], method.getReturnType()).block();
return invokeMethod(methodName, args[0], method.getReturnType()).block();
}
/**

View File

@ -5,6 +5,8 @@
package io.dapr.actors.runtime;
import io.dapr.actors.ActorMethod;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
@ -35,7 +37,12 @@ class ActorMethodInfoMap {
if (methodInfo.getParameterCount() <= 1) {
// If Actor class uses overloading, then one will win.
// Document this behavior, so users know how to write their code.
methods.put(methodInfo.getName(), methodInfo);
String methodName = methodInfo.getName();
ActorMethod actorMethodAnnotation = methodInfo.getAnnotation(ActorMethod.class);
if ((actorMethodAnnotation != null) && !actorMethodAnnotation.name().isEmpty()) {
methodName = actorMethodAnnotation.name();
}
methods.put(methodName, methodInfo);
}
}
}

View File

@ -6,6 +6,7 @@
package io.dapr.actors.runtime;
import io.dapr.actors.ActorId;
import io.dapr.actors.ActorMethod;
import io.dapr.actors.ActorType;
import io.dapr.actors.client.ActorProxy;
import io.dapr.actors.client.ActorProxyForTestsImpl;
@ -43,6 +44,8 @@ public class ActorNoStateTest {
Mono<MyData> classInClassOut(MyData input);
Mono<String> registerBadCallbackName();
String registerTimerAutoName();
@ActorMethod(name = "DotNetMethodASync")
Mono<Void> dotNetMethod();
}
@ActorType(name = "MyActor")
@ -108,6 +111,11 @@ public class ActorNoStateTest {
public String registerTimerAutoName() {
return super.registerActorTimer("", "anything", "state", Duration.ofSeconds(1), Duration.ofSeconds(1)).block();
}
@Override
public Mono<Void> dotNetMethod() {
return Mono.empty();
}
}
static class MyData {
@ -174,6 +182,12 @@ public class ActorNoStateTest {
actorProxy.invokeMethod("stringInVoidOutIntentionallyThrows", "hello world").block();
}
@Test
public void testMethodNameChange() {
MyActor actor = createActorProxy(MyActor.class);
actor.dotNetMethod();
}
@Test
public void classInClassOut() {
ActorProxy actorProxy = createActorProxy();

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
package io.dapr.it.actors;
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.app.MyActor;
import io.dapr.it.actors.app.MyActorService;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.dapr.it.Retry.callWithRetry;
import static org.junit.Assert.assertTrue;
public class ActorMethodNameIT extends BaseIT {
private static Logger logger = LoggerFactory.getLogger(ActorMethodNameIT.class);
@Test
public void actorMethodNameChange() throws Exception {
// The call below will fail if service cannot start successfully.
startDaprApp(
ActorMethodNameIT.class.getSimpleName(),
MyActorService.SUCCESS_MESSAGE,
MyActorService.class,
true,
60000);
logger.debug("Creating proxy builder");
ActorProxyBuilder<MyActor> proxyBuilder = deferClose(new ActorProxyBuilder("MyActorTest", MyActor.class));
logger.debug("Creating actorId");
ActorId actorId1 = new ActorId("1");
logger.debug("Building proxy");
MyActor proxy = proxyBuilder.build(actorId1);
callWithRetry(() -> {
logger.debug("Invoking dotNetMethod from Proxy");
boolean response = proxy.dotNetMethod();
logger.debug("asserting true response: [" + response + "]");
assertTrue(response);
}, 60000);
logger.debug("Creating proxy builder 2");
ActorProxyBuilder<ActorProxy> proxyBuilder2 = deferClose(new ActorProxyBuilder("MyActorTest", ActorProxy.class));
logger.debug("Building proxy 2");
ActorProxy proxy2 = proxyBuilder2.build(actorId1);
callWithRetry(() -> {
logger.debug("Invoking DotNetMethodAsync from Proxy 2");
boolean response = proxy2.invokeMethod("DotNetMethodAsync", boolean.class).block();
logger.debug("asserting true response 2: [" + response + "]");
assertTrue(response);
}, 60000);
}
}

View File

@ -5,6 +5,8 @@
package io.dapr.it.actors.app;
import io.dapr.actors.ActorMethod;
import java.util.ArrayList;
import java.util.List;
@ -26,4 +28,7 @@ public interface MyActor {
ArrayList<String> getCallLog();
String getIdentifier();
@ActorMethod(name = "DotNetMethodAsync")
boolean dotNetMethod();
}

View File

@ -199,6 +199,11 @@ public class MyActorImpl extends AbstractActor implements MyActor, Remindable<St
return System.getenv("DAPR_HTTP_PORT");
}
@Override
public boolean dotNetMethod() {
return true;
}
private void formatAndLog(boolean isEnter, String methodName) {
Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT"));