All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.dapr.workflows.WorkflowContext Maven / Gradle / Ivy

/*
 * Copyright 2023 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.workflows;

import com.microsoft.durabletask.CompositeTaskFailedException;
import com.microsoft.durabletask.Task;
import com.microsoft.durabletask.TaskCanceledException;
import com.microsoft.durabletask.TaskFailedException;
import com.microsoft.durabletask.TaskOptions;
import io.dapr.workflows.saga.SagaContext;
import org.slf4j.Logger;

import javax.annotation.Nullable;

import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

/**
 * Context object used by workflow implementations to perform actions such as scheduling activities,
 * durable timers, waiting for external events, and for getting basic information about the current
 * workflow instance.
 */
public interface WorkflowContext {

  /**
   * Get a logger only when {@code isReplaying} is false.
   * Otherwise, return a NOP (no operation) logger.
   *
   * @return Logger
   */
  Logger getLogger();


  /**
   * Gets the name of the current workflow.
   *
   * @return the name of the current workflow
   */
  String getName();

  /**
   * Gets the instance ID of the current workflow.
   *
   * @return the instance ID of the current workflow
   */
  String getInstanceId();

  /**
   * Gets the current orchestration time in UTC.
   *
   * @return the current orchestration time in UTC
   */
  Instant getCurrentInstant();

  /**
   * Completes the current workflow.
   *
   * @param output the serializable output of the completed Workflow.
   */
  void complete(Object output);

  /**
   * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is
   * received or is canceled when {@code timeout} expires.
   *
   * 

If the current orchestration is not yet waiting for an event named {@code name}, then the event will be saved in * the orchestration instance state and dispatched immediately when this method is called. This event saving occurs * even if the current orchestrator cancels the wait operation before the event is received. * *

Orchestrators can wait for the same event name multiple times, so waiting for multiple events with the same name * is allowed. Each external event received by an orchestrator will complete just one task returned by this method. * * @param name the case-insensitive name of the event to wait for * @param timeout the amount of time to wait before canceling the returned {@code Task} * @param dataType the expected class type of the event data payload * @param the expected type of the event data payload * @return a new {@link Task} that completes when the external event is received or when {@code timeout} expires * @throws TaskCanceledException if the specified {@code timeout} value expires before the event is received */ Task waitForExternalEvent(String name, Duration timeout, Class dataType) throws TaskCanceledException; /** * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is * received or is canceled when {@code timeout} expires. * *

See {@link #waitForExternalEvent(String, Duration, Class)} for a full description. * * @param name the case-insensitive name of the event to wait for * @param timeout the amount of time to wait before canceling the returned {@code Task} * @param the expected type of the event data payload * @return a new {@link Task} that completes when the external event is received or when {@code timeout} expires * @throws TaskCanceledException if the specified {@code timeout} value expires before the event is received */ Task waitForExternalEvent(String name, Duration timeout) throws TaskCanceledException; /** * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is * received. * *

See {@link #waitForExternalEvent(String, Duration, Class)} for a full description. * * @param name the case-insensitive name of the event to wait for * @param the expected type of the event data payload * @return a new {@link Task} that completes when the external event is received */ Task waitForExternalEvent(String name) throws TaskCanceledException; /** * Waits for an event to be raised named {@code name} and returns a {@link Task} that completes when the event is * received. * *

See {@link #waitForExternalEvent(String, Duration, Class)} for a full description. * * @param name the case-insensitive name of the event to wait for * @param dataType the expected class type of the event data payload * @param the expected type of the event data payload * @return a new {@link Task} that completes when the external event is received */ default Task waitForExternalEvent(String name, Class dataType) { try { return this.waitForExternalEvent(name, null, dataType); } catch (TaskCanceledException e) { // This should never happen because of the max duration throw new RuntimeException("An unexpected exception was throw while waiting for an external event.", e); } } /** * Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task} * that completes when the activity completes. If the activity completes successfully, the returned {@code Task}'s * value will be the activity's output. If the activity fails, the returned {@code Task} will complete exceptionally * with a {@link TaskFailedException}. * * @param name the name of the activity to call * @param input the serializable input to pass to the activity * @param options additional options that control the execution and processing of the activity * @param returnType the expected class type of the activity output * @param the expected type of the activity output * @return a new {@link Task} that completes when the activity completes or fails */ Task callActivity(String name, Object input, TaskOptions options, Class returnType); /** * Asynchronously invokes an activity by name and returns a new {@link Task} that completes when the activity * completes. See {@link #callActivity(String, Object, TaskOptions, Class)} for a complete description. * * @param name the name of the activity to call * @return a new {@link Task} that completes when the activity completes or fails * @see #callActivity(String, Object, TaskOptions, Class) */ default Task callActivity(String name) { return this.callActivity(name, null, null, Void.class); } /** * Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task} * that completes when the activity completes. See {@link #callActivity(String, Object, TaskOptions, Class)} for a * complete description. * * @param name the name of the activity to call * @param input the serializable input to pass to the activity * @return a new {@link Task} that completes when the activity completes or fails */ default Task callActivity(String name, Object input) { return this.callActivity(name, input, null, Void.class); } /** * Asynchronously invokes an activity by name and returns a new {@link Task} that completes when the activity * completes. If the activity completes successfully, the returned {@code Task}'s value will be the activity's * output. See {@link #callActivity(String, Object, TaskOptions, Class)} for a complete description. * * @param name the name of the activity to call * @param returnType the expected class type of the activity output * @param the expected type of the activity output * @return a new {@link Task} that completes when the activity completes or fails */ default Task callActivity(String name, Class returnType) { return this.callActivity(name, null, null, returnType); } /** * Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task} * that completes when the activity completes.If the activity completes successfully, the returned {@code Task}'s * value will be the activity's output. See {@link #callActivity(String, Object, TaskOptions, Class)} for a * complete description. * * @param name the name of the activity to call * @param input the serializable input to pass to the activity * @param returnType the expected class type of the activity output * @param the expected type of the activity output * @return a new {@link Task} that completes when the activity completes or fails */ default Task callActivity(String name, Object input, Class returnType) { return this.callActivity(name, input, null, returnType); } /** * Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task} * that completes when the activity completes. See {@link #callActivity(String, Object, TaskOptions, Class)} for a * complete description. * * @param name the name of the activity to call * @param input the serializable input to pass to the activity * @param options additional options that control the execution and processing of the activity * @return a new {@link Task} that completes when the activity completes or fails */ default Task callActivity(String name, Object input, TaskOptions options) { return this.callActivity(name, input, options, Void.class); } /** * Gets a value indicating whether the workflow is currently replaying a previous execution. * *

Workflow functions are "replayed" after being unloaded from memory to reconstruct local variable state. * During a replay, previously executed tasks will be completed automatically with previously seen values * that are stored in the workflow history. Once the workflow reaches the point where it's no longer * replaying existing history, this method will return {@code false}. * *

You can use this method if you have logic that needs to run only when not replaying. For example, * certain types of application logging may become too noisy when duplicated as part of replay. The * application code could check to see whether the function is being replayed and then issue the log statements * when this value is {@code false}. * * @return {@code true} if the workflow is replaying, otherwise {@code false} */ boolean isReplaying(); /** * Returns a new {@code Task} that is completed when all the given {@code Task}s complete. If any of the given * {@code Task}s complete with an exception, the returned {@code Task} will also complete with an * {@link CompositeTaskFailedException} containing details of the first encountered failure. * The value of the returned {@code Task} is an ordered list of the return values of the given tasks. * If no tasks are provided, returns a {@code Task} completed with value * {@code null}. * *

This method is useful for awaiting the completion of a set of independent tasks before continuing to the next * step in the orchestration, as in the following example: *

{@code
   * Task t1 = ctx.callActivity("MyActivity", String.class);
   * Task t2 = ctx.callActivity("MyActivity", String.class);
   * Task t3 = ctx.callActivity("MyActivity", String.class);
   *
   * List orderedResults = ctx.allOf(List.of(t1, t2, t3)).await();
   * }
* *

Exceptions in any of the given tasks results in an unchecked {@link CompositeTaskFailedException}. * This exception can be inspected to obtain failure details of individual {@link Task}s. *

{@code
   * try {
   *     List orderedResults = ctx.allOf(List.of(t1, t2, t3)).await();
   * } catch (CompositeTaskFailedException e) {
   *     List exceptions = e.getExceptions()
   * }
   * }
* * @param tasks the list of {@code Task} objects * @param the return type of the {@code Task} objects * @return the values of the completed {@code Task} objects in the same order as the source list * @throws CompositeTaskFailedException if the specified {@code timeout} value expires before the event is received */ Task> allOf(List> tasks) throws CompositeTaskFailedException; /** * Returns a new {@code Task} that is completed when any of the tasks in {@code tasks} completes. * See {@link #anyOf(Task[])} for more detailed information. * * @param tasks the list of {@code Task} objects * @return a new {@code Task} that is completed when any of the given {@code Task}s complete * @see #anyOf(Task[]) */ Task> anyOf(List> tasks); /** * Returns a new {@code Task} that is completed when any of the given {@code Task}s complete. The value of the * new {@code Task} is a reference to the completed {@code Task} object. If no tasks are provided, returns a * {@code Task} that never completes. * *

This method is useful for waiting on multiple concurrent tasks and performing a task-specific operation when the * first task completes, as in the following example: *

{@code
   * Task event1 = ctx.waitForExternalEvent("Event1");
   * Task event2 = ctx.waitForExternalEvent("Event2");
   * Task event3 = ctx.waitForExternalEvent("Event3");
   *
   * Task winner = ctx.anyOf(event1, event2, event3).await();
   * if (winner == event1) {
   *     // ...
   * } else if (winner == event2) {
   *     // ...
   * } else if (winner == event3) {
   *     // ...
   * }
   * }
* The {@code anyOf} method can also be used for implementing long-running timeouts, as in the following example: *
{@code
   * Task activityTask = ctx.callActivity("SlowActivity");
   * Task timeoutTask = ctx.createTimer(Duration.ofMinutes(30));
   *
   * Task winner = ctx.anyOf(activityTask, timeoutTask).await();
   * if (winner == activityTask) {
   *     // completion case
   * } else {
   *     // timeout case
   * }
   * }
* * @param tasks the list of {@code Task} objects * @return a new {@code Task} that is completed when any of the given {@code Task}s complete */ default Task> anyOf(Task... tasks) { return this.anyOf(Arrays.asList(tasks)); } /** * Creates a durable timer that expires after the specified delay. * *

Specifying a long delay (for example, a delay of a few days or more) may result in the creation of multiple, * internally-managed durable timers. The orchestration code doesn't need to be aware of this behavior. However, * it may be visible in framework logs and the stored history state. * * @param duration the amount of time before the timer should expire * @return a new {@code Task} that completes after the specified delay */ Task createTimer(Duration duration); /** * Creates a durable timer that expires after the specified timestamp with specific zone. * *

Specifying a long delay (for example, a delay of a few days or more) may result in the creation of multiple, * internally-managed timers. The workflow code doesn't need to be aware of this behavior. However, * it may be visible in framework logs and the stored history state. * * @param zonedDateTime timestamp with specific zone when the timer should expire * @return a new {@code Task} that completes after the specified delay */ default Task createTimer(ZonedDateTime zonedDateTime) { throw new UnsupportedOperationException("This method is not implemented."); } /** * Gets the deserialized input of the current task orchestration. * * @param targetType the {@link Class} object associated with {@code V} * @param the expected type of the workflow input * @return the deserialized input as an object of type {@code V} or {@code null} if no input was provided. */ V getInput(Class targetType); /** * Asynchronously invokes another workflow as a sub-workflow and returns a {@link Task} that completes * when the sub-workflow completes. * *

See {@link #callSubWorkflow(String, Object, String, TaskOptions, Class)} for a full description. * * @param name the name of the workflow to invoke * @return a new {@link Task} that completes when the sub-workflow completes or fails * @see #callSubWorkflow(String, Object, String, TaskOptions, Class) */ default Task callSubWorkflow(String name) { return this.callSubWorkflow(name, null); } /** * Asynchronously invokes another workflow as a sub-workflow and returns a {@link Task} that completes * when the sub-workflow completes. * *

See {@link #callSubWorkflow(String, Object, String, TaskOptions, Class)} for a full description. * * @param name the name of the workflow to invoke * @param input the serializable input to send to the sub-workflow * @return a new {@link Task} that completes when the sub-workflow completes or fails */ default Task callSubWorkflow(String name, Object input) { return this.callSubWorkflow(name, input, null); } /** * Asynchronously invokes another workflow as a sub-workflow and returns a {@link Task} that completes * when the sub-workflow completes. * *

See {@link #callSubWorkflow(String, Object, String, TaskOptions, Class)} for a full description. * * @param name the name of the workflow to invoke * @param input the serializable input to send to the sub-workflow * @param returnType the expected class type of the sub-workflow output * @param the expected type of the sub-workflow output * @return a new {@link Task} that completes when the sub-workflow completes or fails */ default Task callSubWorkflow(String name, Object input, Class returnType) { return this.callSubWorkflow(name, input, null, returnType); } /** * Asynchronously invokes another workflow as a sub-workflow and returns a {@link Task} that completes * when the sub-workflow completes. * *

See {@link #callSubWorkflow(String, Object, String, TaskOptions, Class)} for a full description. * * @param name the name of the workflow to invoke * @param input the serializable input to send to the sub-workflow * @param instanceID the unique ID of the sub-workflow * @param returnType the expected class type of the sub-workflow output * @param the expected type of the sub-workflow output * @return a new {@link Task} that completes when the sub-workflow completes or fails */ default Task callSubWorkflow(String name, Object input, String instanceID, Class returnType) { return this.callSubWorkflow(name, input, instanceID, null, returnType); } /** * Asynchronously invokes another workflow as a sub-workflow and returns a {@link Task} that completes * when the sub-workflow completes. * *

See {@link #callSubWorkflow(String, Object, String, TaskOptions, Class)} for a full description. * * @param name the name of the workflow to invoke * @param input the serializable input to send to the sub-workflow * @param instanceID the unique ID of the sub-workflow * @param options additional options that control the execution and processing of the activity * @return a new {@link Task} that completes when the sub-workflow completes or fails */ default Task callSubWorkflow(String name, Object input, String instanceID, TaskOptions options) { return this.callSubWorkflow(name, input, instanceID, options, Void.class); } /** * Asynchronously invokes another workflow as a sub-workflow and returns a {@link Task} that completes * when the sub-workflow completes. If the sub-workflow completes successfully, the returned * {@code Task}'s value will be the activity's output. If the sub-workflow fails, the returned {@code Task} * will complete exceptionally with a {@link TaskFailedException}. * *

A sub-workflow has its own instance ID, history, and status that is independent of the parent workflow * that started it. There are many advantages to breaking down large orchestrations into sub-workflows: *

    *
  • * Splitting large orchestrations into a series of smaller sub-workflows can make code more maintainable. *
  • *
  • * Distributing orchestration logic across multiple compute nodes concurrently is useful if * orchestration logic otherwise needs to coordinate a lot of tasks. *
  • *
  • * Memory usage and CPU overhead can be reduced by keeping the history of parent orchestrations smaller. *
  • *
* The disadvantage is that there is overhead associated with starting a sub-workflow and processing its * output. This is typically only an issue for very small orchestrations. * *

Because sub-workflows are independent of their parents, terminating a parent orchestration does not affect * any sub-workflows. sub-workflows must be terminated independently using their unique instance ID, * which is specified using the {@code instanceID} parameter * * @param name the name of the workflow to invoke * @param input the serializable input to send to the sub-workflow * @param instanceID the unique ID of the sub-workflow * @param options additional options that control the execution and processing of the activity * @param returnType the expected class type of the sub-workflow output * @param the expected type of the sub-workflow output * @return a new {@link Task} that completes when the sub-workflow completes or fails */ Task callSubWorkflow(String name, @Nullable Object input, @Nullable String instanceID, @Nullable TaskOptions options, Class returnType); /** * Restarts the orchestration with a new input and clears its history. See {@link #continueAsNew(Object, boolean)} * for a full description. * * @param input the serializable input data to re-initialize the instance with */ default void continueAsNew(Object input) { this.continueAsNew(input, true); } /** * Restarts the orchestration with a new input and clears its history. * *

This method is primarily designed for eternal orchestrations, which are orchestrations that * may not ever complete. It works by restarting the orchestration, providing it with a new input, * and truncating the existing orchestration history. It allows an orchestration to continue * running indefinitely without having its history grow unbounded. The benefits of periodically * truncating history include decreased memory usage, decreased storage volumes, and shorter orchestrator * replays when rebuilding state. * *

The results of any incomplete tasks will be discarded when an orchestrator calls {@code continueAsNew}. * For example, if a timer is scheduled and then {@code continueAsNew} is called before the timer fires, the timer * event will be discarded. The only exception to this is external events. By default, if an external event is * received by an orchestration but not yet processed, the event is saved in the orchestration state unit it is * received by a call to {@link #waitForExternalEvent}. These events will remain in memory * even after an orchestrator restarts using {@code continueAsNew}. This behavior can be disabled by specifying * {@code false} for the {@code preserveUnprocessedEvents} parameter value. * *

Orchestrator implementations should complete immediately after calling the{@code continueAsNew} method. * * @param input the serializable input data to re-initialize the instance with * @param preserveUnprocessedEvents {@code true} to push unprocessed external events into the new orchestration * history, otherwise {@code false} */ void continueAsNew(Object input, boolean preserveUnprocessedEvents); /** * Create a new UUID that is safe for replay within a workflow. * *

* The default implementation of this method creates a name-based UUID * using the algorithm from RFC 4122 §4.3. The name input used to generate * this value is a combination of the workflow instance ID and an * internally managed sequence number. *

* @return a deterministic UUID */ default UUID newUuid() { throw new RuntimeException("No implementation found."); } /** * get saga context. * * @return saga context * @throws UnsupportedOperationException if saga is not enabled. */ SagaContext getSagaContext(); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy