rename cross app -> multi app (#1555)

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
This commit is contained in:
Cassie Coyle 2025-09-11 17:32:30 -05:00 committed by GitHub
parent dffa92a15a
commit ac073e209d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 86 additions and 86 deletions

View File

@ -53,7 +53,7 @@ Those examples contain the following workflow patterns:
4. [External Event Pattern](#external-event-pattern)
5. [Child-workflow Pattern](#child-workflow-pattern)
6. [Compensation Pattern](#compensation-pattern)
7. [Cross-App Pattern](#cross-app-pattern)
7. [Multi-App Pattern](#multi-app-pattern)
### Chaining Pattern
In the chaining pattern, a sequence of activities executes in a specific order.
@ -682,30 +682,30 @@ Key Points:
4. Each activity simulates work with a short delay for demonstration purposes
### Cross-App Pattern
### Multi-App Pattern
The cross-app pattern allows workflows to call activities that are hosted in different Dapr applications. This is useful for microservices architectures allowing multiple applications to host activities that can be orchestrated by Dapr Workflows.
The multi-app pattern allows workflows to call activities that are hosted in different Dapr applications. This is useful for microservices architectures allowing multiple applications to host activities that can be orchestrated by Dapr Workflows.
The `CrossAppWorkflow` class defines the workflow. It demonstrates calling activities in different apps using the `appId` parameter in `WorkflowTaskOptions`. See the code snippet below:
The `MultiAppWorkflow` class defines the workflow. It demonstrates calling activities in different apps using the `appId` parameter in `WorkflowTaskOptions`. See the code snippet below:
```java
public class CrossAppWorkflow implements Workflow {
public class MultiAppWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
var logger = ctx.getLogger();
logger.info("=== WORKFLOW STARTING ===");
logger.info("Starting CrossAppWorkflow: {}", ctx.getName());
logger.info("Starting MultiAppWorkflow: {}", ctx.getName());
logger.info("Workflow name: {}", ctx.getName());
logger.info("Workflow instance ID: {}", ctx.getInstanceId());
String input = ctx.getInput(String.class);
logger.info("CrossAppWorkflow received input: {}", input);
logger.info("MultiAppWorkflow received input: {}", input);
logger.info("Workflow input: {}", input);
// Call an activity in another app by passing in an active appID to the WorkflowTaskOptions
logger.info("Calling cross-app activity in 'app2'...");
logger.info("About to call cross-app activity in app2...");
String crossAppResult = ctx.callActivity(
logger.info("Calling multi-app activity in 'app2'...");
logger.info("About to call multi-app activity in app2...");
String multiAppResult = ctx.callActivity(
App2TransformActivity.class.getName(),
input,
new WorkflowTaskOptions("app2"),
@ -713,18 +713,18 @@ public class CrossAppWorkflow implements Workflow {
).await();
// Call another activity in a different app
logger.info("Calling cross-app activity in 'app3'...");
logger.info("About to call cross-app activity in app3...");
logger.info("Calling multi-app activity in 'app3'...");
logger.info("About to call multi-app activity in app3...");
String finalResult = ctx.callActivity(
App3FinalizeActivity.class.getName(),
crossAppResult,
multiAppResult,
new WorkflowTaskOptions("app3"),
String.class
).await();
logger.info("Final cross-app activity result: {}", finalResult);
logger.info("Final cross-app activity result: {}", finalResult);
logger.info("Final multi-app activity result: {}", finalResult);
logger.info("Final multi-app activity result: {}", finalResult);
logger.info("CrossAppWorkflow finished with: {}", finalResult);
logger.info("MultiAppWorkflow finished with: {}", finalResult);
logger.info("=== WORKFLOW COMPLETING WITH: {} ===" , finalResult);
ctx.complete(finalResult);
};
@ -784,31 +784,31 @@ public class App3FinalizeActivity implements WorkflowActivity {
**Important Limitations:**
- **Cross-app calls are currently supported for activities only**
- **Child workflow cross-app calls (suborchestration) are NOT supported**
- **Child workflow multi-app calls (suborchestration) are NOT supported**
- The app ID must match the Dapr application ID of the target service
**Running the Cross-App Example:**
This example requires running multiple Dapr applications simultaneously. You'll need to run the following commands in separate terminals:
1. **Start the main workflow worker (crossapp-worker):**
1. **Start the main workflow worker (multiapp-worker):**
```sh
dapr run --app-id crossapp-worker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.CrossAppWorker
dapr run --app-id multiapp-worker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.multiapp.MultiAppWorker
```
2. **Start app2 worker (handles App2TransformActivity):**
```sh
dapr run --app-id app2 --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.App2Worker
dapr run --app-id app2 --resources-path ./components/workflows --dapr-grpc-port 50002 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.multiapp.App2Worker
```
3. **Start app3 worker (handles App3FinalizeActivity):**
```sh
dapr run --app-id app3 --resources-path ./components/workflows --dapr-grpc-port 50003 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.App3Worker
dapr run --app-id app3 --resources-path ./components/workflows --dapr-grpc-port 50003 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.multiapp.App3Worker
```
4. **Run the workflow client:**
```sh
java -Djava.util.logging.ConsoleHandler.level=FINE -Dio.dapr.durabletask.level=FINE -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.crossapp.CrossAppWorkflowClient "Hello World"
java -Djava.util.logging.ConsoleHandler.level=FINE -Dio.dapr.durabletask.level=FINE -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.multiapp.MultiAppWorkflowClient "Hello World"
```
**Expected Output:**
@ -819,15 +819,15 @@ The client will show:
Input: Hello World
Created DaprWorkflowClient successfully
Attempting to start new workflow...
Started a new cross-app workflow with instance ID: 001113f3-b9d9-438c-932a-a9a9b70b9460
Started a new multi-app workflow with instance ID: 001113f3-b9d9-438c-932a-a9a9b70b9460
Waiting for workflow completion...
Workflow instance with ID: 001113f3-b9d9-438c-932a-a9a9b70b9460 completed with result: HELLO WORLD [TRANSFORMED BY APP2] [FINALIZED BY APP3]
```
The workflow demonstrates:
1. The workflow starts in the main worker (crossapp-worker)
2. Calls an activity in 'app2' using cross-app functionality
3. Calls an activity in 'app3' using cross-app functionality
1. The workflow starts in the main worker (multiapp-worker)
2. Calls an activity in 'app2' using multi-app functionality
3. Calls an activity in 'app3' using multi-app functionality
4. The workflow completes with the final result from all activities
This pattern is particularly useful for:

View File

@ -11,7 +11,7 @@
limitations under the License.
*/
package io.dapr.examples.workflows.crossapp;
package io.dapr.examples.workflows.multiapp;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;

View File

@ -11,7 +11,7 @@
limitations under the License.
*/
package io.dapr.examples.workflows.crossapp;
package io.dapr.examples.workflows.multiapp;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;

View File

@ -11,7 +11,7 @@
limitations under the License.
*/
package io.dapr.examples.workflows.crossapp;
package io.dapr.examples.workflows.multiapp;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;

View File

@ -11,7 +11,7 @@
limitations under the License.
*/
package io.dapr.examples.workflows.crossapp;
package io.dapr.examples.workflows.multiapp;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;

View File

@ -11,21 +11,21 @@
limitations under the License.
*/
package io.dapr.examples.workflows.crossapp;
package io.dapr.examples.workflows.multiapp;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
public class CrossAppWorker {
public class MultiAppWorker {
public static void main(String[] args) throws Exception {
// Register the Workflow with the builder
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
.registerWorkflow(CrossAppWorkflow.class);
.registerWorkflow(MultiAppWorkflow.class);
// Build and start the workflow runtime
try (WorkflowRuntime runtime = builder.build()) {
System.out.println("CrossAppWorker started - registered CrossAppWorkflow only");
System.out.println("MultiAppWorker started - registered MultiAppWorkflow only");
runtime.start();
}
}

View File

@ -11,7 +11,7 @@
limitations under the License.
*/
package io.dapr.examples.workflows.crossapp;
package io.dapr.examples.workflows.multiapp;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
@ -21,24 +21,24 @@ import io.dapr.workflows.WorkflowTaskOptions;
* Example workflow that demonstrates cross-app activity calls.
* This workflow calls activities in different apps using the appId parameter.
*/
public class CrossAppWorkflow implements Workflow {
public class MultiAppWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
var logger = ctx.getLogger();
logger.info("=== WORKFLOW STARTING ===");
logger.info("Starting CrossAppWorkflow: {}", ctx.getName());
logger.info("Starting MultiAppWorkflow: {}", ctx.getName());
logger.info("Workflow name: {}", ctx.getName());
logger.info("Workflow instance ID: {}", ctx.getInstanceId());
String input = ctx.getInput(String.class);
logger.info("CrossAppWorkflow received input: {}", input);
logger.info("MultiAppWorkflow received input: {}", input);
logger.info("Workflow input: {}", input);
// Call an activity in another app by passing in an active appID to the WorkflowTaskOptions
logger.info("Calling cross-app activity in 'app2'...");
logger.info("About to call cross-app activity in app2...");
String crossAppResult = ctx.callActivity(
logger.info("Calling multi-app activity in 'app2'...");
logger.info("About to call multi-app activity in app2...");
String multiAppResult = ctx.callActivity(
App2TransformActivity.class.getName(),
input,
new WorkflowTaskOptions("app2"),
@ -46,17 +46,17 @@ public class CrossAppWorkflow implements Workflow {
).await();
// Call another activity in a different app
logger.info("Calling cross-app activity in 'app3'...");
logger.info("About to call cross-app activity in app3...");
logger.info("Calling multi-app activity in 'app3'...");
logger.info("About to call multi-app activity in app3...");
String finalResult = ctx.callActivity(
App3FinalizeActivity.class.getName(),
crossAppResult,
multiAppResult,
new WorkflowTaskOptions("app3"),
String.class
).await();
logger.info("Final cross-app activity result: {}", finalResult);
logger.info("Final multi-app activity result: {}", finalResult);
logger.info("CrossAppWorkflow finished with: {}", finalResult);
logger.info("MultiAppWorkflow finished with: {}", finalResult);
logger.info("=== WORKFLOW COMPLETING WITH: {} ===", finalResult);
ctx.complete(finalResult);
};

View File

@ -11,7 +11,7 @@
limitations under the License.
*/
package io.dapr.examples.workflows.crossapp;
package io.dapr.examples.workflows.multiapp;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
@ -25,17 +25,17 @@ import java.util.concurrent.TimeoutException;
* 2. Start a new workflow instance
* 3. Wait for completion and get results
*/
public class CrossAppWorkflowClient {
public class MultiAppWorkflowClient {
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("Usage: CrossAppWorkflowClientExample <input>");
System.out.println("Example: CrossAppWorkflowClientExample \"Hello World\"");
System.out.println("Usage: MultiAppWorkflowClientExample <input>");
System.out.println("Example: MultiAppWorkflowClientExample \"Hello World\"");
return;
}
String input = args[0];
System.out.println("=== Starting Cross-App Workflow Client ===");
System.out.println("=== Starting Multi-App Workflow Client ===");
System.out.println("Input: " + input);
try (DaprWorkflowClient client = new DaprWorkflowClient()) {
@ -43,7 +43,7 @@ public class CrossAppWorkflowClient {
// Start a new workflow instance
System.out.println("Attempting to start new workflow...");
String instanceId = client.scheduleNewWorkflow(CrossAppWorkflow.class, input);
String instanceId = client.scheduleNewWorkflow(MultiAppWorkflow.class, input);
System.out.printf("Started a new cross-app workflow with instance ID: %s%n", instanceId);
// Wait for the workflow to complete

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
package io.dapr.it.testcontainers.workflows.crossapp;
package io.dapr.it.testcontainers.workflows.multiapp;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;

View File

@ -11,14 +11,14 @@
* limitations under the License.
*/
package io.dapr.it.testcontainers.workflows.crossapp;
package io.dapr.it.testcontainers.workflows.multiapp;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
/**
* App2Worker - registers the App2TransformActivity.
* This app will handle cross-app activity calls from the main workflow.
* This app will handle multi-app activity calls from the main workflow.
*/
public class App2Worker {

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
package io.dapr.it.testcontainers.workflows.crossapp;
package io.dapr.it.testcontainers.workflows.multiapp;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;

View File

@ -11,14 +11,14 @@
* limitations under the License.
*/
package io.dapr.it.testcontainers.workflows.crossapp;
package io.dapr.it.testcontainers.workflows.multiapp;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
/**
* App3Worker - registers the App3FinalizeActivity.
* This app will handle cross-app activity calls from the main workflow.
* This app will handle multi-app activity calls from the main workflow.
*/
public class App3Worker {

View File

@ -11,27 +11,27 @@
* limitations under the License.
*/
package io.dapr.it.testcontainers.workflows.crossapp;
package io.dapr.it.testcontainers.workflows.multiapp;
import io.dapr.workflows.runtime.WorkflowRuntime;
import io.dapr.workflows.runtime.WorkflowRuntimeBuilder;
/**
* CrossAppWorker - registers only the CrossAppWorkflow.
* MultiAppWorker - registers only the MultiAppWorkflow.
* This is the main workflow orchestrator that will call activities in other apps.
*/
public class CrossAppWorker {
public class MultiAppWorker {
public static void main(String[] args) throws Exception {
System.out.println("=== Starting CrossAppWorker (Workflow Orchestrator) ===");
System.out.println("=== Starting MultiAppWorker (Workflow Orchestrator) ===");
// Register the Workflow with the builder
WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder()
.registerWorkflow(CrossAppWorkflow.class);
.registerWorkflow(MultiAppWorkflow.class);
// Build and start the workflow runtime
try (WorkflowRuntime runtime = builder.build()) {
System.out.println("CrossAppWorker started - registered CrossAppWorkflow only");
System.out.println("MultiAppWorker started - registered MultiAppWorkflow only");
System.out.println("Waiting for workflow orchestration requests...");
runtime.start();
}

View File

@ -11,27 +11,27 @@
* limitations under the License.
*/
package io.dapr.it.testcontainers.workflows.crossapp;
package io.dapr.it.testcontainers.workflows.multiapp;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import io.dapr.workflows.WorkflowTaskOptions;
import org.slf4j.Logger;
public class CrossAppWorkflow implements Workflow {
public class MultiAppWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
Logger logger = ctx.getLogger();
String instanceId = ctx.getInstanceId();
logger.info("Starting CrossAppWorkflow: {}", ctx.getName());
logger.info("Starting MultiAppWorkflow: {}", ctx.getName());
logger.info("Instance ID: {}", instanceId);
String input = ctx.getInput(String.class);
logger.info("Workflow input: {}", input);
// Call App2TransformActivity in app2
logger.info("Calling cross-app activity in 'app2'...");
logger.info("Calling multi-app activity in 'app2'...");
String transformedByApp2 = ctx.callActivity(
App2TransformActivity.class.getName(),
input,
@ -40,7 +40,7 @@ public class CrossAppWorkflow implements Workflow {
).await();
// Call App3FinalizeActivity in app3
logger.info("Calling cross-app activity in 'app3'...");
logger.info("Calling multi-app activity in 'app3'...");
String finalizedByApp3 = ctx.callActivity(
App3FinalizeActivity.class.getName(),
transformedByApp2,
@ -48,7 +48,7 @@ public class CrossAppWorkflow implements Workflow {
String.class
).await();
logger.info("Final cross-app activity result: {}", finalizedByApp3);
logger.info("Final multi-app activity result: {}", finalizedByApp3);
ctx.complete(finalizedByApp3);
};
}

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
package io.dapr.it.testcontainers.workflows.crossapp;
package io.dapr.it.testcontainers.workflows.multiapp;
import io.dapr.testcontainers.Component;
import io.dapr.testcontainers.DaprContainer;
@ -42,17 +42,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* Cross-App Pattern integration test.
* Multi-App Pattern integration test.
*
* This test demonstrates the cross-app pattern by:
* 1. Starting 3 Dapr containers (crossapp-worker, app2, app3)
* This test demonstrates the multi-app pattern by:
* 1. Starting 3 Dapr containers (multiapp-worker, app2, app3)
* 2. Launching Java processes that register workflows/activities in separate apps
* 3. Executing a cross-app workflow
* 3. Executing a multi-app workflow
* 4. Asserting successful completion
*/
@Testcontainers
@Tag("testcontainers")
public class WorkflowsCrossAppCallActivityIT {
public class WorkflowsMultiAppCallActivityIT {
private static final Network DAPR_NETWORK = Network.newNetwork();
@ -71,7 +71,7 @@ public class WorkflowsCrossAppCallActivityIT {
// Main workflow orchestrator container
@Container
private final static DaprContainer MAIN_WORKFLOW_SIDECAR = new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
.withAppName("crossapp-worker")
.withAppName("multiapp-worker")
.withNetwork(DAPR_NETWORK)
.withNetworkAliases("main-workflow-sidecar")
.withPlacementContainer(sharedPlacementContainer)
@ -113,18 +113,18 @@ public class WorkflowsCrossAppCallActivityIT {
// TestContainers for each app
@Container
private static GenericContainer<?> crossappWorker = new GenericContainer<>("openjdk:17-jdk-slim")
private static GenericContainer<?> multiappWorker = new GenericContainer<>("openjdk:17-jdk-slim")
.withCopyFileToContainer(MountableFile.forHostPath("target"), "/app")
.withWorkingDirectory("/app")
.withCommand("java", "-cp", "test-classes:classes:dependency/*:*",
"-Ddapr.app.id=crossapp-worker",
"-Ddapr.app.id=multiapp-worker",
"-Ddapr.grpc.endpoint=main-workflow-sidecar:50001",
"-Ddapr.http.endpoint=main-workflow-sidecar:3500",
"io.dapr.it.testcontainers.workflows.crossapp.CrossAppWorker")
"io.dapr.it.testcontainers.workflows.multiapp.MultiAppWorker")
.withNetwork(DAPR_NETWORK)
.dependsOn(MAIN_WORKFLOW_SIDECAR)
.waitingFor(Wait.forLogMessage(".*CrossAppWorker started.*", 1))
.withLogConsumer(outputFrame -> System.out.println("CrossAppWorker: " + outputFrame.getUtf8String()));
.waitingFor(Wait.forLogMessage(".*MultiAppWorker started.*", 1))
.withLogConsumer(outputFrame -> System.out.println("MultiAppWorker: " + outputFrame.getUtf8String()));
@Container
private final static GenericContainer<?> app2Worker = new GenericContainer<>("openjdk:17-jdk-slim")
@ -134,7 +134,7 @@ public class WorkflowsCrossAppCallActivityIT {
"-Ddapr.app.id=app2",
"-Ddapr.grpc.endpoint=app2-sidecar:50001",
"-Ddapr.http.endpoint=app2-sidecar:3500",
"io.dapr.it.testcontainers.workflows.crossapp.App2Worker")
"io.dapr.it.testcontainers.workflows.multiapp.App2Worker")
.withNetwork(DAPR_NETWORK)
.dependsOn(APP2_SIDECAR)
.waitingFor(Wait.forLogMessage(".*App2Worker started.*", 1))
@ -148,14 +148,14 @@ public class WorkflowsCrossAppCallActivityIT {
"-Ddapr.app.id=app3",
"-Ddapr.grpc.endpoint=app3-sidecar:50001",
"-Ddapr.http.endpoint=app3-sidecar:3500",
"io.dapr.it.testcontainers.workflows.crossapp.App3Worker")
"io.dapr.it.testcontainers.workflows.multiapp.App3Worker")
.withNetwork(DAPR_NETWORK)
.dependsOn(APP3_SIDECAR)
.waitingFor(Wait.forLogMessage(".*App3Worker started.*", 1))
.withLogConsumer(outputFrame -> System.out.println("App3Worker: " + outputFrame.getUtf8String()));
@Test
public void testCrossAppWorkflow() throws Exception {
public void testMultiAppWorkflow() throws Exception {
// TestContainers wait strategies ensure all containers are ready before this test runs
String input = "Hello World";
@ -173,7 +173,7 @@ public class WorkflowsCrossAppCallActivityIT {
DaprWorkflowClient workflowClient = new DaprWorkflowClient(clientProperties);
try {
String instanceId = workflowClient.scheduleNewWorkflow(CrossAppWorkflow.class, input);
String instanceId = workflowClient.scheduleNewWorkflow(MultiAppWorkflow.class, input);
assertNotNull(instanceId, "Workflow instance ID should not be null");
workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(30), false);