feat: Support registering activities with custom name (#1431)

* feat: Support register of activities with custom name

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* chore: Apply suggestions

Signed-off-by: Javier Aliaga <javier@diagrid.io>

---------

Signed-off-by: Javier Aliaga <javier@diagrid.io>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com>
This commit is contained in:
Javier Aliaga 2025-08-26 17:33:45 +02:00 committed by GitHub
parent a782438c36
commit 6ee1f59d61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 165 additions and 29 deletions

View File

@ -147,6 +147,29 @@ public class DaprWorkflowsIT {
}
@Test
public void testNamedActivitiesWorkflows() throws Exception {
TestWorkflowPayload payload = new TestWorkflowPayload(new ArrayList<>());
String instanceId = workflowClient.scheduleNewWorkflow(TestNamedActivitiesWorkflow.class, payload);
workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(10), false);
Duration timeout = Duration.ofSeconds(10);
WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, timeout, true);
assertNotNull(workflowStatus);
TestWorkflowPayload workflowOutput = deserialize(workflowStatus.getSerializedOutput());
assertEquals(5, workflowOutput.getPayloads().size());
assertEquals("First Activity", workflowOutput.getPayloads().get(0));
assertEquals("First Activity", workflowOutput.getPayloads().get(1));
assertEquals("Second Activity", workflowOutput.getPayloads().get(2));
assertEquals("Anonymous Activity", workflowOutput.getPayloads().get(3));
assertEquals("Anonymous Activity 2", workflowOutput.getPayloads().get(4));
assertEquals(instanceId, workflowOutput.getWorkflowId());
}
private TestWorkflowPayload deserialize(String value) throws JsonProcessingException {
return OBJECT_MAPPER.readValue(value, TestWorkflowPayload.class);

View File

@ -0,0 +1,53 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.it.testcontainers.workflows;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import org.slf4j.Logger;
public class TestNamedActivitiesWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
Logger logger = ctx.getLogger();
String instanceId = ctx.getInstanceId();
logger.info("Starting Workflow: " + ctx.getName());
logger.info("Instance ID: " + instanceId);
logger.info("Current Orchestration Time: " + ctx.getCurrentInstant());
TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
workflowPayload.setWorkflowId(instanceId);
var payloadAfterA = ctx.callActivity("a", workflowPayload, TestWorkflowPayload.class)
.await();
var payloadAfterB = ctx.callActivity("b", payloadAfterA, TestWorkflowPayload.class)
.await();
var payloadAfterC = ctx.callActivity("c", payloadAfterB, TestWorkflowPayload.class)
.await();
var payloadAfterD = ctx.callActivity("d", payloadAfterC, TestWorkflowPayload.class)
.await();
var payloadAfterE = ctx.callActivity("e", payloadAfterD, TestWorkflowPayload.class)
.await();
ctx.complete(payloadAfterE);
};
}
}

View File

@ -15,8 +15,10 @@ package io.dapr.it.testcontainers.workflows;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.dapr.config.Properties;
import io.dapr.workflows.WorkflowActivityContext;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
import io.dapr.workflows.WorkflowActivity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -56,8 +58,31 @@ public class TestWorkflowsConfiguration {
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder(new Properties(overrides));
builder.registerWorkflow(TestWorkflow.class);
builder.registerWorkflow(TestNamedActivitiesWorkflow.class);
builder.registerActivity(FirstActivity.class);
builder.registerActivity(SecondActivity.class);
builder.registerActivity("a",FirstActivity.class);
builder.registerActivity("b",FirstActivity.class);
builder.registerActivity("c", new SecondActivity());
builder.registerActivity("d", new WorkflowActivity() {
@Override
public Object run(WorkflowActivityContext ctx) {
TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
workflowPayload.getPayloads().add("Anonymous Activity");
return workflowPayload;
}
});
builder.registerActivity("e", new WorkflowActivity() {
@Override
public Object run(WorkflowActivityContext ctx) {
TestWorkflowPayload workflowPayload = ctx.getInput(TestWorkflowPayload.class);
workflowPayload.getPayloads().add("Anonymous Activity 2");
return workflowPayload;
}
});
return builder;
}

View File

@ -30,19 +30,28 @@ public class WorkflowActivityClassWrapper<T extends WorkflowActivity> implements
/**
* Constructor for WorkflowActivityWrapper.
*
* @param name Name of the activity to wrap.
* @param clazz Class of the activity to wrap.
*/
public WorkflowActivityClassWrapper(Class<T> clazz) {
this.name = clazz.getCanonicalName();
public WorkflowActivityClassWrapper(String name, Class<T> clazz) {
this.name = name;
try {
this.activityConstructor = clazz.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new RuntimeException(
String.format("No constructor found for activity class '%s'.", this.name), e
);
String.format("No constructor found for activity class '%s'.", this.name), e);
}
}
/**
* Constructor for WorkflowActivityWrapper.
*
* @param clazz Class of the activity to wrap.
*/
public WorkflowActivityClassWrapper(Class<T> clazz) {
this(clazz.getCanonicalName(), clazz);
}
@Override
public String getName() {
return name;
@ -53,13 +62,12 @@ public class WorkflowActivityClassWrapper<T extends WorkflowActivity> implements
return ctx -> {
Object result;
T activity;
try {
activity = this.activityConstructor.newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(
String.format("Unable to instantiate instance of activity class '%s'", this.name), e
);
String.format("Unable to instantiate instance of activity class '%s'", this.name), e);
}
result = activity.run(new DefaultWorkflowActivityContext(ctx));

View File

@ -24,14 +24,24 @@ public class WorkflowActivityInstanceWrapper<T extends WorkflowActivity> impleme
private final T activity;
private final String name;
/**
* Constructor for WorkflowActivityWrapper.
*
* @param name Name of the activity to wrap.
* @param instance Instance of the activity to wrap.
*/
public WorkflowActivityInstanceWrapper(String name, T instance) {
this.name = name;
this.activity = instance;
}
/**
* Constructor for WorkflowActivityWrapper.
*
* @param instance Instance of the activity to wrap.
*/
public WorkflowActivityInstanceWrapper(T instance) {
this.name = instance.getClass().getCanonicalName();
this.activity = instance;
this(instance.getClass().getCanonicalName(), instance);
}
@Override

View File

@ -149,11 +149,23 @@ public class WorkflowRuntimeBuilder {
* @return the WorkflowRuntimeBuilder
*/
public <T extends WorkflowActivity> WorkflowRuntimeBuilder registerActivity(Class<T> clazz) {
this.builder.addActivity(new WorkflowActivityClassWrapper<>(clazz));
this.activitySet.add(clazz.getCanonicalName());
this.activities.add(clazz.getSimpleName());
return registerActivity(clazz.getCanonicalName(), clazz);
}
this.logger.info("Registered Activity: {}", clazz.getSimpleName());
/**
* Registers an Activity object.
*
* @param <T> any WorkflowActivity type
* @param name Name of the activity to register.
* @param clazz Class of the activity to register.
* @return the WorkflowRuntimeBuilder
*/
public <T extends WorkflowActivity> WorkflowRuntimeBuilder registerActivity(String name, Class<T> clazz) {
this.builder.addActivity(new WorkflowActivityClassWrapper<>(name, clazz));
this.activitySet.add(name);
this.activities.add(name);
this.logger.info("Registered Activity: {}", name);
return this;
}
@ -166,13 +178,23 @@ public class WorkflowRuntimeBuilder {
* @return the WorkflowRuntimeBuilder
*/
public <T extends WorkflowActivity> WorkflowRuntimeBuilder registerActivity(T instance) {
Class<T> clazz = (Class<T>) instance.getClass();
return this.registerActivity(instance.getClass().getCanonicalName(), instance);
}
this.builder.addActivity(new WorkflowActivityInstanceWrapper<>(instance));
this.activitySet.add(clazz.getCanonicalName());
this.activities.add(clazz.getSimpleName());
/**
* Registers an Activity object.
*
* @param <T> any WorkflowActivity type
* @param name Name of the activity to register.
* @param instance the class instance being registered
* @return the WorkflowRuntimeBuilder
*/
public <T extends WorkflowActivity> WorkflowRuntimeBuilder registerActivity(String name, T instance) {
this.builder.addActivity(new WorkflowActivityInstanceWrapper<>(name, instance));
this.activitySet.add(name);
this.activities.add(name);
this.logger.info("Registered Activity: {}", clazz.getSimpleName());
this.logger.info("Registered Activity: {}", name);
return this;
}

View File

@ -6,10 +6,7 @@ import io.dapr.workflows.WorkflowActivityContext;
import org.junit.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
public class WorkflowActivityClassWrapperTest {
public static class TestActivity implements WorkflowActivity {

View File

@ -19,15 +19,13 @@ import io.dapr.workflows.WorkflowStub;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
public class WorkflowRuntimeBuilderTest {
public static class TestWorkflow implements Workflow {
@Override
@ -94,6 +92,6 @@ public class WorkflowRuntimeBuilderTest {
.info(eq("Registered Workflow: {}"), eq("TestWorkflow"));
verify(testLogger, times(1))
.info(eq("Registered Activity: {}"), eq("TestActivity"));
.info(eq("Registered Activity: {}"), eq("io.dapr.workflows.runtime.WorkflowRuntimeBuilderTest.TestActivity"));
}
}