Class Workflow
- java.lang.Object
-
- com.uber.cadence.workflow.Workflow
-
public final class Workflow extends java.lang.Object
Workflow encapsulates the orchestration of activities and child workflows. It can also answer to synchronous queries and receive external events (also known as signals).Workflow Interface
A workflow must define an interface class. All of its methods must have one of the following annotations:- @
WorkflowMethod
indicates an entry point to a workflow. It contains parameters such as timeouts and a task list. Required parameters (likeexecutionStartToCloseTimeoutSeconds
) that are not specified through the annotation must be provided at runtime. - @
SignalMethod
indicates a method that reacts to external signals. It must have avoid
return type. - @
QueryMethod
indicates a method that reacts to synchronous query requests. You can have more than one method with the same annotation.
public interface FileProcessingWorkflow { @WorkflowMethod(executionStartToCloseTimeoutSeconds = 10, taskList = "file-processing") String processFile(Arguments args); @QueryMethod(name="history") List
getHistory(); @QueryMethod(name="status") String getStatus(); @SignalMethod void retryNow(); } Starting workflow executions
SeeWorkflowClient
Implementing Workflows
A workflow implementation implements a workflow interface. Each time a new workflow execution is started, a new instance of the workflow implementation object is created. Then, one of the methods (depending on which workflow type has been started) annotated with @WorkflowMethod
is invoked. As soon as this method returns the workflow, execution is closed. While workflow execution is open, it can receive calls to signal and query methods. No additional calls to workflow methods are allowed. The workflow object is stateful, so query and signal methods can communicate with the other parts of the workflow through workflow object fields.Calling Activities
newActivityStub(Class)
returns a client-side stub that implements an activity interface. It takes activity type and activity options as arguments. Activity options are needed only if some of the required timeouts are not specified through the @ActivityMethod
annotation.Calling a method on this interface invokes an activity that implements this method. An activity invocation synchronously blocks until the activity completes, fails, or times out. Even if activity execution takes a few months, the workflow code still sees it as a single synchronous invocation. Isn't it great? It doesn't matter what happens to the processes that host the workflow. The business logic code just sees a single method call.
If different activities need different options, like timeouts or a task list, multiple client-side stubs can be created with different options.public class FileProcessingWorkflowImpl implements FileProcessingWorkflow { private final FileProcessingActivities activities; public FileProcessingWorkflowImpl() { this.store = Workflow.newActivityStub(FileProcessingActivities.class); } @Override public void processFile(Arguments args) { String localName = null; String processedName = null; try { localName = activities.download(args.getSourceBucketName(), args.getSourceFilename()); processedName = activities.processFile(localName); activities.upload(args.getTargetBucketName(), args.getTargetFilename(), processedName); } finally { if (localName != null) { // File was downloaded. activities.deleteLocalFile(localName); } if (processedName != null) { // File was processed. activities.deleteLocalFile(processedName); } } } ... }
public FileProcessingWorkflowImpl() { ActivityOptions options1 = new ActivityOptions.Builder() .setTaskList("taskList1") .build(); this.store1 = Workflow.newActivityStub(FileProcessingActivities.class, options1); ActivityOptions options2 = new ActivityOptions.Builder() .setTaskList("taskList2") .build(); this.store2 = Workflow.newActivityStub(FileProcessingActivities.class, options2); }
Calling Activities Asynchronously
Sometimes workflows need to perform certain operations in parallel. TheAsync
static methods allow you to invoke any activity asynchronously. The call returns aPromise
result immediately.Promise
is similar to bothFuture
andCompletionStage
. ThePromise.get()
blocks until a result is available. It also exposes thePromise.thenApply(Functions.Func1)
andPromise.handle(Functions.Func2)
methods. See thePromise
documentation for technical details about differences withFuture
.To convert a synchronous call
to asynchronous style, the method reference is passed toString localName = activities.download(sourceBucket, sourceFile);
Async.function(Functions.Func)
orAsync.procedure(Functions.Proc)
followed by activity arguments:
Then to wait synchronously for the result:Promise
localNamePromise = Async.function(activities::download, sourceBucket, sourceFile);
Here is the above example rewritten to call download and upload in parallel on multiple files:String localName = localNamePromise.get();
public void processFile(Arguments args) { List<Promise<String>> localNamePromises = new ArrayList<>(); List<String> processedNames = null; try { // Download all files in parallel. for (String sourceFilename : args.getSourceFilenames()) { Promise<String> localName = Async.function(activities::download, args.getSourceBucketName(), sourceFilename); localNamePromises.add(localName); } // allOf converts a list of promises to a single promise that contains a list of each promise value. Promise<List<String>> localNamesPromise = Promise.allOf(localNamePromises); // All code until the next line wasn't blocking. // The promise get is a blocking call. List<String> localNames = localNamesPromise.get(); processedNames = activities.processFiles(localNames); // Upload all results in parallel. List<Promise<Void>> uploadedList = new ArrayList<>(); for (String processedName : processedNames) { Promise<Void> uploaded = Async.procedure(activities::upload, args.getTargetBucketName(), args.getTargetFilename(), processedName); uploadedList.add(uploaded); } // Wait for all uploads to complete. Promise<?> allUploaded = Promise.allOf(uploadedList); allUploaded.get(); // blocks until all promises are ready. } finally { // Execute deletes even if workflow is cancelled. Workflow.newDetachedCancellationScope( () -> { for (Promise<Sting> localNamePromise : localNamePromises) { // Skip files that haven't completed downloading. if (localNamePromise.isCompleted()) { activities.deleteLocalFile(localNamePromise.get()); } } if (processedNames != null) { for (String processedName : processedNames) { activities.deleteLocalFile(processedName); } } } ).run(); } }
Child Workflows
Besides activities, a workflow can also orchestrate other workflows.newChildWorkflowStub(Class)
returns a client-side stub that implements a child workflow interface. It takes a child workflow type and optional child workflow options as arguments. Workflow options may be needed to override the timeouts and task list if they differ from the ones defined in the @WorkflowMethod
annotation or parent workflow.The first call to the child workflow stub must always be to a method annotated with @
WorkflowMethod
. Similarly to activities, a call can be synchronous or asynchronous usingAsync.function(Functions.Func)
orAsync.procedure(Functions.Proc)
. The synchronous call blocks until a child workflow completes. The asynchronous call returns aPromise
that can be used to wait for the completion. After an async call returns the stub, it can be used to send signals to the child by calling methods annotated with @SignalMethod
. Querying a child workflow by calling methods annotated with @QueryMethod
from within workflow code is not supported. However, queries can be done from activities using theWorkflowClient
provided stub.
Running two children in parallel:public interface GreetingChild { @WorkflowMethod String composeGreeting(String greeting, String name); } public static class GreetingWorkflowImpl implements GreetingWorkflow { @Override public String getGreeting(String name) { GreetingChild child = Workflow.newChildWorkflowStub(GreetingChild.class); // This is a blocking call that returns only after child has completed. return child.composeGreeting("Hello", name ); } }
To start child then return and let child run:public static class GreetingWorkflowImpl implements GreetingWorkflow { @Override public String getGreeting(String name) { // Workflows are stateful, so a new stub must be created for each new child. GreetingChild child1 = Workflow.newChildWorkflowStub(GreetingChild.class); Promise
greeting1 = Async.function(child1::composeGreeting, "Hello", name); // Both children will run concurrently. GreetingChild child2 = Workflow.newChildWorkflowStub(GreetingChild.class); Promise greeting2 = Async.function(child2::composeGreeting, "Bye", name); // Do something else here. ... return "First: " + greeting1.get() + ", second=" + greeting2.get(); } }
To send signal to a child, call a method annotated with @public static class GreetingWorkflowImpl implements GreetingWorkflow { @Override public String getGreeting(String name) { GreetingChild child1 = Workflow.newChildWorkflowStub(GreetingChild.class); Async.function(child1::composeGreeting, "Hello", name); // block until child started, // otherwise child may not start because parent complete first. Promise
childPromise = Workflow.getWorkflowExecution(child); childPromise.get(); return "Parent Greeting"; } } SignalMethod
:
Calling methods annotated with @public interface GreetingChild { @WorkflowMethod String composeGreeting(String greeting, String name); @SignalMethod void updateName(String name); } public static class GreetingWorkflowImpl implements GreetingWorkflow { @Override public String getGreeting(String name) { GreetingChild child = Workflow.newChildWorkflowStub(GreetingChild.class); Promise
greeting = Async.function(child::composeGreeting, "Hello", name); child.updateName("Cadence"); return greeting.get(); } } QueryMethod
is not allowed from within a workflow code.Workflow Implementation Constraints
Cadence uses event sourcing to recover the state of a workflow object including its threads and local variable values. In essence, every time a workflow state has to be restored, its code is re-executed from the beginning. When replaying, side effects (such as activity invocations) are ignored because they are already recorded in the workflow event history. When writing workflow logic, the replay is not visible, so the code should be written as it executes only once. This design puts the following constraints on the workflow implementation:- Do not use any mutable global variables because multiple instances of workflows are executed in parallel.
- Do not call any non deterministic functions like non seeded random or
UUID.randomUUID()
directly form the workflow code. Always do this in activities. - Don’t perform any IO or service calls as they are not usually deterministic. Use activities for this.
- Only use
currentTimeMillis()
to get the current time inside a workflow. - Do not use native Java
Thread
or any other multi-threaded classes likeThreadPoolExecutor
. UseAsync.function(Functions.Func)
orAsync.procedure(Functions.Proc)
to execute code asynchronously. - Don't use any synchronization, locks, and other standard Java blocking concurrency-related classes besides those provided by the Workflow class. There is no need in explicit synchronization because multi-threaded code inside a workflow is executed one thread at a time and under a global lock.
- Call
sleep(Duration)
instead ofThread.sleep(long)
. - Use
Promise
andCompletablePromise
instead ofFuture
andCompletableFuture
. - Use
WorkflowQueue
instead ofBlockingQueue
. - Don't change workflow code when there are open workflows. The ability to do updates through visioning is TBD.
- Don’t access configuration APIs directly from a workflow because changes in the configuration might affect a workflow execution path. Pass it as an argument to a workflow function or use an activity to load it.
Workflow method arguments and return values are serializable to a byte array using the provided
DataConverter
. The default implementation uses the JSON serializer, but any alternative serialization mechanism is pluggable.The values passed to workflows through invocation parameters or returned through a result value are recorded in the execution history. The entire execution history is transferred from the Cadence service to workflow workers with every event that the workflow logic needs to process. A large execution history can thus adversely impact the performance of your workflow. Therefore, be mindful of the amount of data that you transfer via activity invocation parameters or return values. Other than that, no additional limitations exist on activity implementations.
- @
-
-
Field Summary
Fields Modifier and Type Field Description static int
DEFAULT_VERSION
-
Method Summary
All Methods Static Methods Concrete Methods Modifier and Type Method Description static boolean
await(java.time.Duration timeout, java.util.function.Supplier<java.lang.Boolean> unblockCondition)
Block current workflow thread until unblockCondition is evaluated to true or timeoutMillis passes.static void
await(java.util.function.Supplier<java.lang.Boolean> unblockCondition)
Block current thread until unblockCondition is evaluated to true.static void
continueAsNew(java.lang.Object... args)
Continues the current workflow execution as a new run with the same options.static void
continueAsNew(java.util.Optional<java.lang.String> workflowType, java.util.Optional<ContinueAsNewOptions> options, java.lang.Object... args)
Continues the current workflow execution as a new run possibly overriding the workflow type and options.static long
currentTimeMillis()
Must be used to get current time instead ofSystem.currentTimeMillis()
to guarantee determinism.static <R> R
getLastCompletionResult(java.lang.Class<R> resultClass)
GetLastCompletionResult extract last completion result from previous run for this cron workflow.static <R> R
getLastCompletionResult(java.lang.Class<R> resultClass, java.lang.reflect.Type resultType)
GetLastCompletionResult extract last completion result from previous run for this cron workflow.static org.slf4j.Logger
getLogger(java.lang.Class<?> clazz)
Get logger to use inside workflow.static org.slf4j.Logger
getLogger(java.lang.String name)
Get logger to use inside workflow.static com.uber.m3.tally.Scope
getMetricsScope()
Get scope for reporting business metrics in workflow logic.static int
getVersion(java.lang.String changeID, int minSupported, int maxSupported)
getVersion
is used to safely perform backwards incompatible changes to workflow definitions.static Promise<WorkflowExecution>
getWorkflowExecution(java.lang.Object childWorkflowStub)
Extracts workflow execution from a stub created throughnewChildWorkflowStub(Class, ChildWorkflowOptions)
ornewExternalWorkflowStub(Class, String)
.static WorkflowInfo
getWorkflowInfo()
static boolean
isReplaying()
True if workflow code is being replayed.static <R> R
mutableSideEffect(java.lang.String id, java.lang.Class<R> resultClass, java.lang.reflect.Type resultType, java.util.function.BiPredicate<R,R> updated, Functions.Func<R> func)
mutableSideEffect
is similar tosideEffect(Class, Functions.Func)
in allowing calls of non-deterministic functions from workflow code.static <R> R
mutableSideEffect(java.lang.String id, java.lang.Class<R> resultClass, java.util.function.BiPredicate<R,R> updated, Functions.Func<R> func)
mutableSideEffect
is similar tosideEffect(Class, Functions.Func)
in allowing calls of non-deterministic functions from workflow code.static <T> T
newActivityStub(java.lang.Class<T> activityInterface)
Creates client stub to activities that implement given interface.static <T> T
newActivityStub(java.lang.Class<T> activityInterface, ActivityOptions options)
Creates client stub to activities that implement given interface.static CancellationScope
newCancellationScope(Functions.Proc1<CancellationScope> proc)
Wraps a procedure in a CancellationScope.static CancellationScope
newCancellationScope(java.lang.Runnable runnable)
Wraps the Runnable method argument in aCancellationScope
.static <T> T
newChildWorkflowStub(java.lang.Class<T> workflowInterface)
Creates client stub that can be used to start a child workflow that implements the given interface using parent options.static <T> T
newChildWorkflowStub(java.lang.Class<T> workflowInterface, ChildWorkflowOptions options)
Creates client stub that can be used to start a child workflow that implements given interface.static <T> T
newContinueAsNewStub(java.lang.Class<T> workflowInterface)
Creates a client stub that can be used to continue this workflow as a new run.static <T> T
newContinueAsNewStub(java.lang.Class<T> workflowInterface, ContinueAsNewOptions options)
Creates a client stub that can be used to continue this workflow as a new run.static CancellationScope
newDetachedCancellationScope(java.lang.Runnable runnable)
Creates a CancellationScope that is not linked to a parent scope.static <R> R
newExternalWorkflowStub(java.lang.Class<? extends R> workflowInterface, WorkflowExecution execution)
Creates client stub that can be used to communicate to an existing workflow execution.static <R> R
newExternalWorkflowStub(java.lang.Class<? extends R> workflowInterface, java.lang.String workflowId)
Creates client stub that can be used to communicate to an existing workflow execution.static <E> Promise<E>
newFailedPromise(java.lang.Exception failure)
static <T> T
newLocalActivityStub(java.lang.Class<T> activityInterface)
Creates client stub to local activities that implement given interface.static <T> T
newLocalActivityStub(java.lang.Class<T> activityInterface, LocalActivityOptions options)
Creates client stub to local activities that implement given interface.static <E> CompletablePromise<E>
newPromise()
static <E> Promise<E>
newPromise(E value)
static <E> WorkflowQueue<E>
newQueue(int capacity)
static java.util.Random
newRandom()
Replay safe random numbers generator.static Promise<java.lang.Void>
newTimer(java.time.Duration delay)
Create new timer.static ActivityStub
newUntypedActivityStub(ActivityOptions options)
Creates non typed client stub to activities.static ChildWorkflowStub
newUntypedChildWorkflowStub(java.lang.String workflowType)
Creates untyped client stub that can be used to start and signal a child workflow.static ChildWorkflowStub
newUntypedChildWorkflowStub(java.lang.String workflowType, ChildWorkflowOptions options)
Creates untyped client stub that can be used to start and signal a child workflow.static ExternalWorkflowStub
newUntypedExternalWorkflowStub(WorkflowExecution execution)
Creates untyped client stub that can be used to signal or cancel a child workflow.static ExternalWorkflowStub
newUntypedExternalWorkflowStub(java.lang.String workflowId)
Creates untyped client stub that can be used to signal or cancel a child workflow.static ActivityStub
newUntypedLocalActivityStub(LocalActivityOptions options)
Creates non typed client stub to local activities.static java.util.UUID
randomUUID()
Replay safe way to generate UUID.static void
registerQuery(java.lang.Object queryImplementation)
Register query or queries implementation object.static <R> R
retry(RetryOptions options, Functions.Func<R> fn)
Invokes function retrying in case of failures according to retry options.static void
retry(RetryOptions options, Functions.Proc proc)
Invokes function retrying in case of failures according to retry options.static <R> R
sideEffect(java.lang.Class<R> resultClass, Functions.Func<R> func)
Executes the provided function once, records its result into the workflow history.static <R> R
sideEffect(java.lang.Class<R> resultClass, java.lang.reflect.Type resultType, Functions.Func<R> func)
Executes the provided function once, records its result into the workflow history.static void
sleep(long millis)
Must be called instead ofThread.sleep(long)
to guarantee determinism.static void
sleep(java.time.Duration duration)
Must be called instead ofThread.sleep(long)
to guarantee determinism.static java.lang.Exception
unwrap(java.lang.Exception e)
RemovesCheckedExceptionWrapper
from causal exception chain.static void
upsertSearchAttributes(java.util.Map<java.lang.String,java.lang.Object> searchAttributes)
upsertSearchAttributes
is used to add or update workflow search attributes.static java.lang.RuntimeException
wrap(java.lang.Exception e)
If there is a need to return a checked exception from a workflow implementation do not add the exception to a method signature but wrap it using this method before rethrowing.
-
-
-
Field Detail
-
DEFAULT_VERSION
public static final int DEFAULT_VERSION
- See Also:
- Constant Field Values
-
-
Method Detail
-
newActivityStub
public static <T> T newActivityStub(java.lang.Class<T> activityInterface, ActivityOptions options)
Creates client stub to activities that implement given interface.- Parameters:
activityInterface
- interface type implemented by activities.options
- options that together with the properties ofActivityMethod
specify the activity invocation parameters.
-
newActivityStub
public static <T> T newActivityStub(java.lang.Class<T> activityInterface)
Creates client stub to activities that implement given interface. `- Parameters:
activityInterface
- interface type implemented by activities
-
newUntypedActivityStub
public static ActivityStub newUntypedActivityStub(ActivityOptions options)
Creates non typed client stub to activities. Allows executing activities by their string name.- Parameters:
options
- specify the activity invocation parameters.
-
newLocalActivityStub
public static <T> T newLocalActivityStub(java.lang.Class<T> activityInterface, LocalActivityOptions options)
Creates client stub to local activities that implement given interface. A local activity is similar to a regular activity, but with some key differences: 1. Local activity is scheduled and run by the workflow worker locally. 2. Local activity does not need Cadence server to schedule activity task and does not rely on activity worker. 3. Local activity is for short living activities (usually finishes within seconds). 4. Local activity cannot heartbeat.- Parameters:
activityInterface
- interface type implemented by activities.options
- options that together with the properties ofActivityMethod
specify the activity invocation parameters.
-
newLocalActivityStub
public static <T> T newLocalActivityStub(java.lang.Class<T> activityInterface)
Creates client stub to local activities that implement given interface.- Parameters:
activityInterface
- interface type implemented by activities
-
newUntypedLocalActivityStub
public static ActivityStub newUntypedLocalActivityStub(LocalActivityOptions options)
Creates non typed client stub to local activities. Allows executing activities by their string name.- Parameters:
options
- specify the local activity invocation parameters.
-
newChildWorkflowStub
public static <T> T newChildWorkflowStub(java.lang.Class<T> workflowInterface)
Creates client stub that can be used to start a child workflow that implements the given interface using parent options. UsenewExternalWorkflowStub(Class, String)
to get a stub to signal a workflow without starting it.- Parameters:
workflowInterface
- interface type implemented by activities
-
newChildWorkflowStub
public static <T> T newChildWorkflowStub(java.lang.Class<T> workflowInterface, ChildWorkflowOptions options)
Creates client stub that can be used to start a child workflow that implements given interface. UsenewExternalWorkflowStub(Class, String)
to get a stub to signal a workflow without starting it.- Parameters:
workflowInterface
- interface type implemented by activitiesoptions
- options passed to the child workflow.
-
newExternalWorkflowStub
public static <R> R newExternalWorkflowStub(java.lang.Class<? extends R> workflowInterface, java.lang.String workflowId)
Creates client stub that can be used to communicate to an existing workflow execution.- Parameters:
workflowInterface
- interface type implemented by activitiesworkflowId
- id of the workflow to communicate with.
-
newExternalWorkflowStub
public static <R> R newExternalWorkflowStub(java.lang.Class<? extends R> workflowInterface, WorkflowExecution execution)
Creates client stub that can be used to communicate to an existing workflow execution.- Parameters:
workflowInterface
- interface type implemented by activitiesexecution
- execution of the workflow to communicate with.
-
getWorkflowExecution
public static Promise<WorkflowExecution> getWorkflowExecution(java.lang.Object childWorkflowStub)
Extracts workflow execution from a stub created throughnewChildWorkflowStub(Class, ChildWorkflowOptions)
ornewExternalWorkflowStub(Class, String)
. Wrapped in a Promise as child workflow start is asynchronous.
-
newUntypedChildWorkflowStub
public static ChildWorkflowStub newUntypedChildWorkflowStub(java.lang.String workflowType, ChildWorkflowOptions options)
Creates untyped client stub that can be used to start and signal a child workflow.- Parameters:
workflowType
- name of the workflow type to start.options
- options passed to the child workflow.
-
newUntypedChildWorkflowStub
public static ChildWorkflowStub newUntypedChildWorkflowStub(java.lang.String workflowType)
Creates untyped client stub that can be used to start and signal a child workflow. All options are inherited from the parent.- Parameters:
workflowType
- name of the workflow type to start.
-
newUntypedExternalWorkflowStub
public static ExternalWorkflowStub newUntypedExternalWorkflowStub(WorkflowExecution execution)
Creates untyped client stub that can be used to signal or cancel a child workflow.- Parameters:
execution
- execution of the workflow to communicate with.
-
newUntypedExternalWorkflowStub
public static ExternalWorkflowStub newUntypedExternalWorkflowStub(java.lang.String workflowId)
Creates untyped client stub that can be used to signal or cancel a child workflow.- Parameters:
workflowId
- id of the workflow to communicate with.
-
newContinueAsNewStub
public static <T> T newContinueAsNewStub(java.lang.Class<T> workflowInterface, ContinueAsNewOptions options)
Creates a client stub that can be used to continue this workflow as a new run.- Parameters:
workflowInterface
- an interface type implemented by the next run of the workflow
-
newContinueAsNewStub
public static <T> T newContinueAsNewStub(java.lang.Class<T> workflowInterface)
Creates a client stub that can be used to continue this workflow as a new run.- Parameters:
workflowInterface
- an interface type implemented by the next run of the workflow
-
continueAsNew
public static void continueAsNew(java.lang.Object... args)
Continues the current workflow execution as a new run with the same options.- Parameters:
args
- arguments of the next run.- See Also:
newContinueAsNewStub(Class)
-
continueAsNew
public static void continueAsNew(java.util.Optional<java.lang.String> workflowType, java.util.Optional<ContinueAsNewOptions> options, java.lang.Object... args)
Continues the current workflow execution as a new run possibly overriding the workflow type and options.- Parameters:
options
- option overrides for the next run.args
- arguments of the next run.- See Also:
newContinueAsNewStub(Class)
-
getWorkflowInfo
public static WorkflowInfo getWorkflowInfo()
-
newCancellationScope
public static CancellationScope newCancellationScope(java.lang.Runnable runnable)
Wraps the Runnable method argument in aCancellationScope
. TheRunnable.run()
callsRunnable.run()
on the wrapped Runnable. The returned CancellationScope can be used to cancel the wrapped code. The cancellation semantic depends on the operation the code is blocked on. For example activity or child workflow is first cancelled then throws aCancellationException
. The same applies forsleep(long)
operation. When an activity or a child workflow is invoked asynchronously then they get cancelled and aPromise
that contains their result will throw CancellationException whenPromise.get()
is called.The new cancellation scope is linked to the parent one (available as
CancellationScope.current()
. If the parent one is cancelled then all the children scopes are cancelled automatically. The main workflow function (annotated with @WorkflowMethod
is wrapped within a root cancellation scope which gets cancelled when a workflow is cancelled through the Cadence CancelWorkflowExecution API. To perform cleanup operations that require blocking after the current scope is cancelled use a scope created throughnewDetachedCancellationScope(Runnable)
.Example of running activities in parallel and cancelling them after a specified timeout.
List
> results = new ArrayList<>(); CancellationScope scope = Workflow.newDetachedCancellationScope(() -> { Async.function(activities::a1); Async.function(activities::a2); }); scope.run(); // returns immediately as the activities are invoked asynchronously Workflow.sleep(Duration.ofHours(1)); // Cancels any activity in the scope that is still running scope.cancel("one hour passed"); - Parameters:
runnable
- parameter to wrap in a cancellation scope.- Returns:
- wrapped parameter.
-
newCancellationScope
public static CancellationScope newCancellationScope(Functions.Proc1<CancellationScope> proc)
Wraps a procedure in a CancellationScope. The procedure receives the wrapping CancellationScope as a parameter. Useful when cancellation is requested from within the wrapped code. The following example cancels the sibling activity on any failure.Workflow.newCancellationScope( (scope) -> { Promise
p1 = Async.proc(activities::a1).exceptionally(ex-> { scope.cancel("a1 failed"); return null; }); Promise p2 = Async.proc(activities::a2).exceptionally(ex-> { scope.cancel("a2 failed"); return null; }); Promise.allOf(p1, p2).get(); }) .run(); - Parameters:
proc
- code to wrap in the cancellation scope- Returns:
- wrapped proc
-
newDetachedCancellationScope
public static CancellationScope newDetachedCancellationScope(java.lang.Runnable runnable)
Creates a CancellationScope that is not linked to a parent scope.Runnable.run()
must be called to execute the code the scope wraps. The detached scope is needed to execute cleanup code after a workflow is cancelled which cancels the root scope that wraps the @WorkflowMethod invocation. Here is an example usage:try { // workflow logic } catch (CancellationException e) { CancellationScope detached = Workflow.newDetachedCancellationScope(() -> { // cleanup logic }); detached.run(); }
- Parameters:
runnable
- parameter to wrap in a cancellation scope.- Returns:
- wrapped parameter.
- See Also:
newCancellationScope(Runnable)
-
newTimer
public static Promise<java.lang.Void> newTimer(java.time.Duration delay)
Create new timer. Note that Cadence service time resolution is in seconds. So all durations are rounded up to the nearest second.- Returns:
- feature that becomes ready when at least specified number of seconds passes. promise is
failed with
CancellationException
if enclosing scope is cancelled.
-
newQueue
public static <E> WorkflowQueue<E> newQueue(int capacity)
-
newPromise
public static <E> CompletablePromise<E> newPromise()
-
newPromise
public static <E> Promise<E> newPromise(E value)
-
newFailedPromise
public static <E> Promise<E> newFailedPromise(java.lang.Exception failure)
-
registerQuery
public static void registerQuery(java.lang.Object queryImplementation)
Register query or queries implementation object. There is no need to register top level workflow implementation object as it is done implicitly. Only methods annotated with @QueryMethod
are registered.
-
currentTimeMillis
public static long currentTimeMillis()
Must be used to get current time instead ofSystem.currentTimeMillis()
to guarantee determinism.
-
sleep
public static void sleep(java.time.Duration duration)
Must be called instead ofThread.sleep(long)
to guarantee determinism.
-
sleep
public static void sleep(long millis)
Must be called instead ofThread.sleep(long)
to guarantee determinism.
-
await
public static void await(java.util.function.Supplier<java.lang.Boolean> unblockCondition)
Block current thread until unblockCondition is evaluated to true.- Parameters:
unblockCondition
- condition that should return true to indicate that thread should unblock.- Throws:
java.util.concurrent.CancellationException
- if thread (or currentCancellationScope
was cancelled).
-
await
public static boolean await(java.time.Duration timeout, java.util.function.Supplier<java.lang.Boolean> unblockCondition)
Block current workflow thread until unblockCondition is evaluated to true or timeoutMillis passes.- Returns:
- false if timed out.
- Throws:
java.util.concurrent.CancellationException
- if thread (or currentCancellationScope
was cancelled).
-
retry
public static <R> R retry(RetryOptions options, Functions.Func<R> fn)
Invokes function retrying in case of failures according to retry options. Synchronous variant. UseAsync.retry(RetryOptions, Functions.Func)
for asynchronous functions.- Parameters:
options
- retry options that specify retry policyfn
- function to invoke and retry- Returns:
- result of the function or the last failure.
-
retry
public static void retry(RetryOptions options, Functions.Proc proc)
Invokes function retrying in case of failures according to retry options. Synchronous variant. UseAsync.retry(RetryOptions, Functions.Func)
for asynchronous functions.- Parameters:
options
- retry options that specify retry policyproc
- procedure to invoke and retry
-
wrap
public static java.lang.RuntimeException wrap(java.lang.Exception e)
If there is a need to return a checked exception from a workflow implementation do not add the exception to a method signature but wrap it using this method before rethrowing. The library code will unwrap it automatically usingunwrap(Exception)
when propagating exception to a remote caller.RuntimeException
are just returned from this method without modification.The reason for such design is that returning originally thrown exception from a remote call (which child workflow and activity invocations are ) would not allow adding context information about a failure, like activity and child workflow id. So stubs always throw a subclass of
ActivityException
from calls to an activity and subclass ofChildWorkflowException
from calls to a child workflow. The original exception is attached as a cause to these wrapper exceptions. So as exceptions are always wrapped adding checked ones to method signature causes more pain than benefit.try { return someCall(); } catch (Exception e) { throw CheckedExceptionWrapper.wrap(e); }
*- Returns:
- CheckedExceptionWrapper if e is checked or original exception if e extends RuntimeException.
-
unwrap
public static java.lang.Exception unwrap(java.lang.Exception e)
RemovesCheckedExceptionWrapper
from causal exception chain.- Parameters:
e
- exception with causality chain that might contain wrapped exceptions.- Returns:
- exception causality chain with CheckedExceptionWrapper removed.
-
randomUUID
public static java.util.UUID randomUUID()
Replay safe way to generate UUID.Must be used instead of
UUID.randomUUID()
which relies on a random generator, thus leads to non deterministic code which is prohibited inside a workflow.
-
newRandom
public static java.util.Random newRandom()
Replay safe random numbers generator. Seeded differently for each workflow instance.
-
isReplaying
public static boolean isReplaying()
True if workflow code is being replayed. Warning! Never make workflow logic depend on this flag as it is going to break determinism. The only reasonable uses for this flag are deduping external never failing side effects like logging or metric reporting.
-
sideEffect
public static <R> R sideEffect(java.lang.Class<R> resultClass, Functions.Func<R> func)
Executes the provided function once, records its result into the workflow history. The recorded result on history will be returned without executing the provided function during replay. This guarantees the deterministic requirement for workflow as the exact same result will be returned in replay. Common use case is to run some short non-deterministic code in workflow, like getting random number. The only way to fail SideEffect is to panic which causes decision task failure. The decision task after timeout is rescheduled and re-executed giving SideEffect another chance to succeed.Caution: do not use sideEffect function to modify any workflow state. Only use the SideEffect's return value. For example this code is BROKEN:
On replay the provided function is not executed, the random will always be 0, and the workflow could takes a different path breaking the determinism.// Bad example: AtomicInteger random = new AtomicInteger(); Workflow.sideEffect(() -> { random.set(random.nextInt(100)); return null; }); // random will always be 0 in replay, thus this code is non-deterministic if random.get() < 50 { .... } else { .... }
Here is the correct way to use sideEffect:
If function throws any exception it is not delivered to the workflow code. It is wrapped in// Good example: int random = Workflow.sideEffect(Integer.class, () -> random.nextInt(100)); if random < 50 { .... } else { .... }
Error
causing failure of the current decision.- Parameters:
resultClass
- type of the side effectfunc
- function that returns side effect value- Returns:
- value of the side effect
- See Also:
mutableSideEffect(String, Class, BiPredicate, Functions.Func)
-
sideEffect
public static <R> R sideEffect(java.lang.Class<R> resultClass, java.lang.reflect.Type resultType, Functions.Func<R> func)
Executes the provided function once, records its result into the workflow history. The recorded result on history will be returned without executing the provided function during replay. This guarantees the deterministic requirement for workflow as the exact same result will be returned in replay. Common use case is to run some short non-deterministic code in workflow, like getting random number. The only way to fail SideEffect is to panic which causes decision task failure. The decision task after timeout is rescheduled and re-executed giving SideEffect another chance to succeed.Caution: do not use sideEffect function to modify any workflow state. Only use the SideEffect's return value. For example this code is BROKEN:
On replay the provided function is not executed, the random will always be 0, and the workflow could takes a different path breaking the determinism.// Bad example: AtomicInteger random = new AtomicInteger(); Workflow.sideEffect(() -> { random.set(random.nextInt(100)); return null; }); // random will always be 0 in replay, thus this code is non-deterministic if random.get() < 50 { .... } else { .... }
Here is the correct way to use sideEffect:
If function throws any exception it is not delivered to the workflow code. It is wrapped in// Good example: int random = Workflow.sideEffect(Integer.class, () -> random.nextInt(100)); if random < 50 { .... } else { .... }
Error
causing failure of the current decision.- Parameters:
resultClass
- class of the side effectresultType
- type of the side effect. Differs from resultClass for generic types.func
- function that returns side effect value- Returns:
- value of the side effect
- See Also:
mutableSideEffect(String, Class, BiPredicate, Functions.Func)
-
mutableSideEffect
public static <R> R mutableSideEffect(java.lang.String id, java.lang.Class<R> resultClass, java.util.function.BiPredicate<R,R> updated, Functions.Func<R> func)
mutableSideEffect
is similar tosideEffect(Class, Functions.Func)
in allowing calls of non-deterministic functions from workflow code.The difference between
mutableSideEffect
andsideEffect(Class, Functions.Func)
is that every newsideEffect
call in non-replay mode results in a new marker event recorded into the history. However,mutableSideEffect
only records a new marker if a value has changed. During the replay,mutableSideEffect
will not execute the function again, but it will return the exact same value as it was returning during the non-replay run.One good use case of
mutableSideEffect
is to access a dynamically changing config without breaking determinism. Even if called very frequently the config value is recorded only when it changes not causing any performance degradation due to a large history size.Caution: do not use
mutableSideEffect
function to modify any workflow sate. Only use the mutableSideEffect's return value.If function throws any exception it is not delivered to the workflow code. It is wrapped in
Error
causing failure of the current decision.- Parameters:
id
- unique identifier of this side effectupdated
- used to decide if a new value should be recorded. A func result is recorded only if call to updated with stored and a new value as arguments returns true. It is not called for the first value.resultClass
- class of the side effectfunc
- function that produces a value. This function can contain non deterministic code.- See Also:
sideEffect(Class, Functions.Func)
-
mutableSideEffect
public static <R> R mutableSideEffect(java.lang.String id, java.lang.Class<R> resultClass, java.lang.reflect.Type resultType, java.util.function.BiPredicate<R,R> updated, Functions.Func<R> func)
mutableSideEffect
is similar tosideEffect(Class, Functions.Func)
in allowing calls of non-deterministic functions from workflow code.The difference between
mutableSideEffect
andsideEffect(Class, Functions.Func)
is that every newsideEffect
call in non-replay mode results in a new marker event recorded into the history. However,mutableSideEffect
only records a new marker if a value has changed. During the replay,mutableSideEffect
will not execute the function again, but it will return the exact same value as it was returning during the non-replay run.One good use case of
mutableSideEffect
is to access a dynamically changing config without breaking determinism. Even if called very frequently the config value is recorded only when it changes not causing any performance degradation due to a large history size.Caution: do not use
mutableSideEffect
function to modify any workflow sate. Only use the mutableSideEffect's return value.If function throws any exception it is not delivered to the workflow code. It is wrapped in
Error
causing failure of the current decision.- Parameters:
id
- unique identifier of this side effectupdated
- used to decide if a new value should be recorded. A func result is recorded only if call to updated with stored and a new value as arguments returns true. It is not called for the first value.resultClass
- class of the side effectresultType
- type of the side effect. Differs from resultClass for generic types.func
- function that produces a value. This function can contain non deterministic code.- See Also:
sideEffect(Class, Functions.Func)
-
getVersion
public static int getVersion(java.lang.String changeID, int minSupported, int maxSupported)
getVersion
is used to safely perform backwards incompatible changes to workflow definitions. It is not allowed to update workflow code while there are workflows running as it is going to break determinism. The solution is to have both old code that is used to replay existing workflows as well as the new one that is used when it is executed for the first time.\getVersion
returns maxSupported version when is executed for the first time. This version is recorded into the workflow history as a marker event. Even if maxSupported version is changed the version that was recorded is returned on replay. DefaultVersion constant contains version of code that wasn't versioned before.For example initially workflow has the following code:
it should be updated toresult = testActivities.activity1();
The backwards compatible way to execute the update isresult = testActivities.activity2();
Then later if we want to have another change:int version = Workflow.getVersion("fooChange", Workflow.DEFAULT_VERSION, 1); String result; if (version == Workflow.DEFAULT_VERSION) { result = testActivities.activity1(); } else { result = testActivities.activity2(); }
Later when there are no workflow executions running DefaultVersion the correspondent branch can be removed:int version = Workflow.getVersion("fooChange", Workflow.DEFAULT_VERSION, 2); String result; if (version == Workflow.DEFAULT_VERSION) { result = testActivities.activity1(); } else if (version == 1) { result = testActivities.activity2(); } else { result = testActivities.activity3(); }
It is recommended to keep the GetVersion() call even if single branch is left:int version = Workflow.getVersion("fooChange", 1, 2); String result; if (version == 1) { result = testActivities.activity2(); } else { result = testActivities.activity3(); }
The reason to keep it is: 1) it ensures that if there is older version execution still running, it will fail here and not proceed; 2) if you ever need to make more changes for “fooChange”, for example change activity3 to activity4, you just need to update the maxVersion from 2 to 3.Workflow.getVersion("fooChange", 2, 2); result = testActivities.activity3();
Note that, you only need to preserve the first call to GetVersion() for each changeID. All subsequent call to GetVersion() with same changeID are safe to remove. However, if you really want to get rid of the first GetVersion() call as well, you can do so, but you need to make sure: 1) all older version executions are completed; 2) you can no longer use “fooChange” as changeID. If you ever need to make changes to that same part, you would need to use a different changeID like “fooChange-fix2”, and start minVersion from DefaultVersion again.
- Parameters:
changeID
- identifier of a particular change. All calls to getVersion that share a changeID are guaranteed to return the same version number. Use this to perform multiple coordinated changes that should be enabled together.minSupported
- min version supported for the changemaxSupported
- max version supported for the change- Returns:
- version
-
getMetricsScope
public static com.uber.m3.tally.Scope getMetricsScope()
Get scope for reporting business metrics in workflow logic. This should be used instead of creating new metrics scopes as it is able to dedup metrics during replay.The original metrics scope is set through
WorkerOptions
when a worker starts up.
-
getLogger
public static org.slf4j.Logger getLogger(java.lang.Class<?> clazz)
Get logger to use inside workflow. Logs in replay mode are omitted unless enableLoggingInReplay is set to true inWorkerOptions
when a worker starts up.- Parameters:
clazz
- class name to appear in logging.- Returns:
- logger to use in workflow logic.
-
getLogger
public static org.slf4j.Logger getLogger(java.lang.String name)
Get logger to use inside workflow. Logs in replay mode are omitted unless enableLoggingInReplay is set to true inWorkerOptions
when a worker starts up.- Parameters:
name
- name to appear in logging.- Returns:
- logger to use in workflow logic.
-
getLastCompletionResult
public static <R> R getLastCompletionResult(java.lang.Class<R> resultClass)
GetLastCompletionResult extract last completion result from previous run for this cron workflow. This is used in combination with cron schedule. A workflow can be started with an optional cron schedule. If a cron workflow wants to pass some data to next schedule, it can return any data and that data will become available when next run starts.- Parameters:
resultClass
- class of the return data from last run- Returns:
- result of last run
-
getLastCompletionResult
public static <R> R getLastCompletionResult(java.lang.Class<R> resultClass, java.lang.reflect.Type resultType)
GetLastCompletionResult extract last completion result from previous run for this cron workflow. This is used in combination with cron schedule. A workflow can be started with an optional cron schedule. If a cron workflow wants to pass some data to next schedule, it can return any data and that data will become available when next run starts.- Parameters:
resultClass
- class of the return data from last runresultType
- type of the return data from last run. Differs from resultClass for generic types.- Returns:
- result of last run
-
upsertSearchAttributes
public static void upsertSearchAttributes(java.util.Map<java.lang.String,java.lang.Object> searchAttributes)
upsertSearchAttributes
is used to add or update workflow search attributes. The search attributes can be used in query of List/Scan/Count workflow APIs. The key and value type must be registered on cadence server side; The value has to be Json serializable. UpsertSearchAttributes will merge attributes to existing map in workflow, for example workflow code:
will eventually have search attributes as:Map
attr1 = new HashMap<>(); attr1.put("CustomIntField", 1); attr1.put("CustomBoolField", true); Workflow.upsertSearchAttributes(attr1); Map attr2 = new HashMap<>(); attr2.put("CustomIntField", 2); attr2.put("CustomKeywordField", "Seattle"); Workflow.upsertSearchAttributes(attr2); { "CustomIntField": 2, "CustomBoolField": true, "CustomKeywordField": "Seattle", }
- Parameters:
searchAttributes
- map of String to Object value that can be used to search in list APIs
-
-