Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## v1.2.0
* Add `thenAccept` and `thenApply` to `Task` interface ([#148](https://github.com/microsoft/durabletask-java/pull/148))


## v1.1.1

### Updates
Expand Down
20 changes: 20 additions & 0 deletions client/src/main/java/com/microsoft/durabletask/Task.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
package com.microsoft.durabletask;

import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* Represents an asynchronous operation in a durable orchestration.
Expand Down Expand Up @@ -54,4 +57,21 @@ public boolean isCancelled() {
* @return the result of the task
*/
public abstract V await();

/**
* Returns a new {@link Task} that, when this Task completes normally,
* is executed with this Task's result as the argument to the supplied function.
* @param fn the function to use to compute the value of the returned Task
* @return the new Task
* @param <U> the function's return type
*/
public abstract <U> Task<U> thenApply(Function<V,U> fn);

/**
*Returns a new {@link Task} that, when this Task completes normally,
* is executed with this Task's result as the argument to the supplied action.
* @param fn the function to use to compute the value of the returned Task
* @return the new Task
*/
public abstract Task<Void> thenAccept(Consumer<V> fn);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;
import java.util.logging.Logger;

final class TaskOrchestrationExecutor {
Expand Down Expand Up @@ -1102,6 +1105,18 @@ public V await() {
"The orchestrator is blocked and waiting for new inputs. This Throwable should never be caught by user code.");
}

@Override
public <U> CompletableTask<U> thenApply(Function<V, U> fn) {
CompletableFuture<U> newFuture = this.future.thenApply(fn);
return new CompletableTask<>(newFuture);
}

@Override
public Task<Void> thenAccept(Consumer<V> fn) {
CompletableFuture<Void> newFuture = this.future.thenAccept(fn);
return new CompletableTask<>(newFuture);
}

protected void handleException(Throwable e) {
if (e instanceof TaskFailedException) {
throw (TaskFailedException)e;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1159,4 +1159,71 @@ private static String getExceptionMessage(String taskName, int expectedTaskId, S
expectedTaskId,
expectedExceptionMessage);
}
}

@Test
void thenApply() throws IOException, InterruptedException, TimeoutException {
final String orchestratorName = "thenApplyActivity";
final String activityName = "Echo";
final String suffix = "-test";
final String input = Instant.now().toString();
DurableTaskGrpcWorker worker = this.createWorkerBuilder()
.addOrchestrator(orchestratorName, ctx -> {
String activityInput = ctx.getInput(String.class);
String output = ctx.callActivity(activityName, activityInput, String.class).thenApply(s -> s + suffix).await();
ctx.complete(output);
})
.addActivity(activityName, ctx -> {
return String.format("Hello, %s!", ctx.getInput(String.class));
})
.buildAndStart();

DurableTaskClient client = new DurableTaskGrpcClientBuilder().build();
try (worker; client) {
String instanceId = client.scheduleNewOrchestrationInstance(orchestratorName, input);
OrchestrationMetadata instance = client.waitForInstanceCompletion(
instanceId,
defaultTimeout,
true);

assertNotNull(instance);
assertEquals(OrchestrationRuntimeStatus.COMPLETED, instance.getRuntimeStatus());
String output = instance.readOutputAs(String.class);
String expected = String.format("Hello, %s!%s", input, suffix);
assertEquals(expected, output);
}
}

@Test
void externalEventThenAccept() throws IOException, InterruptedException, TimeoutException {
final String orchestratorName = "continueAsNewWithExternalEvents";
final String eventName = "MyEvent";
final int expectedEventCount = 10;
DurableTaskGrpcWorker worker = this.createWorkerBuilder().addOrchestrator(orchestratorName, ctx -> {
int receivedEventCount = ctx.getInput(int.class);

if (receivedEventCount < expectedEventCount) {
ctx.waitForExternalEvent(eventName, int.class)
.thenAccept(s -> {
ctx.continueAsNew(receivedEventCount + 1);
return;
})
.await();
} else {
ctx.complete(receivedEventCount);
}
}).buildAndStart();
DurableTaskClient client = new DurableTaskGrpcClientBuilder().build();
try (worker; client) {
String instanceId = client.scheduleNewOrchestrationInstance(orchestratorName, 0);

for (int i = 0; i < expectedEventCount; i++) {
client.raiseEvent(instanceId, eventName, i);
}

OrchestrationMetadata instance = client.waitForInstanceCompletion(instanceId, defaultTimeout, true);
assertNotNull(instance);
assertEquals(OrchestrationRuntimeStatus.COMPLETED, instance.getRuntimeStatus());
assertEquals(expectedEventCount, instance.readOutputAs(int.class));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.functions;

import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.HttpResponseMessage;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
import com.microsoft.durabletask.DurableTaskClient;
import com.microsoft.durabletask.TaskOrchestrationContext;
import com.microsoft.durabletask.azurefunctions.DurableClientContext;
import com.microsoft.durabletask.azurefunctions.DurableClientInput;
import com.microsoft.durabletask.azurefunctions.DurableOrchestrationTrigger;

import java.util.Optional;

public class ThenChain {
@FunctionName("StartOrchestrationThenChain")
public HttpResponseMessage startOrchestrationThenChain(
@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
@DurableClientInput(name = "durableContext") DurableClientContext durableContext,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request.");

DurableTaskClient client = durableContext.getClient();
String instanceId = client.scheduleNewOrchestrationInstance("ThenChain");
context.getLogger().info("Created new Java orchestration with instance ID = " + instanceId);
return durableContext.createCheckStatusResponse(request, instanceId);
}

@FunctionName("ThenChain")
public String thenChain(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
String result = "";
result += ctx.callActivity("Capitalize", "Austin", String.class).thenApply(s -> s + "-test").await();
return result;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ public void basicChain() throws InterruptedException {
assertEquals("Completed", runTimeStatus);
}


@Test
public void continueAsNew() throws InterruptedException {
String startOrchestrationPath = "api/ContinueAsNew";
Expand Down Expand Up @@ -94,4 +93,24 @@ private String waitForCompletion(String statusQueryGetUri) throws InterruptedExc
return runTimeStatus;
}

}
@Test
public void thenChain() throws InterruptedException {
final String expect = "AUSTIN-test";
String startOrchestrationPath = "/api/StartOrchestrationThenChain";
Response response = post(startOrchestrationPath);
JsonPath jsonPath = response.jsonPath();
String statusQueryGetUri = jsonPath.get("statusQueryGetUri");
String runTimeStatus = null;
String output = null;
for (int i = 0; i < 15; i++) {
Response statusResponse = get(statusQueryGetUri);
runTimeStatus = statusResponse.jsonPath().get("runtimeStatus");
output = statusResponse.jsonPath().get("output");
if (!"Completed".equals(runTimeStatus)) {
Thread.sleep(1000);
} else break;
}
assertEquals("Completed", runTimeStatus);
assertEquals(expect, output);
}
}