public final class Workflow
extends java.lang.Object
WorkflowMethod
indicates an entry point to a workflow. It contains
parameters such as timeouts and a task list. Required parameters (like executionStartToCloseTimeoutSeconds
) that are not specified through the annotation must be
provided at runtime.
SignalMethod
indicates a method that reacts to external signals. It
must have a void
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();
}
WorkflowClient
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.
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.
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);
}
}
}
...
}
If different activities need different options, like timeouts or a task list, multiple
client-side stubs can be created with different options.
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);
}
Async
static
methods allow you to invoke any activity asynchronously. The call returns a Promise
result immediately. Promise
is similar to both Future
and
CompletionStage
. The Promise.get()
blocks until a result is
available. It also exposes the Promise.thenApply(Functions.Func1)
and Promise.handle(Functions.Func2)
methods. See the Promise
documentation for technical
details about differences with Future
.
To convert a synchronous call
String localName = activities.download(sourceBucket, sourceFile);
to asynchronous style, the method reference is passed to Async.function(Functions.Func)
or Async.procedure(Functions.Proc)
followed by activity arguments:
Promise localNamePromise = Async.function(activities::download, sourceBucket, sourceFile);
Then to wait synchronously for the result:
String localName = localNamePromise.get();
Here is the above example rewritten to call download and upload in parallel on multiple files:
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();
}
}
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 using Async.function(Functions.Func)
or Async.procedure(Functions.Proc)
. The synchronous call blocks until a child workflow completes.
The asynchronous call returns a Promise
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 the WorkflowClient
provided stub.
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 );
}
}
Running two children in parallel:
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 @SignalMethod
:
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();
}
}
Calling methods annotated with @QueryMethod
is not allowed from within a
workflow code.
UUID.randomUUID()
directly form the workflow code. Always do this in activities.
currentTimeMillis()
to get the current time inside a workflow.
Thread
or any other multi-threaded classes like ThreadPoolExecutor
. Use Async.function(Functions.Func)
or
Async.procedure(Functions.Proc)
to execute code asynchronously.
sleep(Duration)
instead of Thread.sleep(long)
.
Promise
and CompletablePromise
instead of Future
and CompletableFuture
.
WorkflowQueue
instead of BlockingQueue
.
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.
Modifier and Type | Field and Description |
---|---|
static int |
DEFAULT_VERSION |
Modifier and Type | Method and 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 of
System.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 through
newChildWorkflowStub(Class,
ChildWorkflowOptions) or newExternalWorkflowStub(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.util.function.BiPredicate<R,R> updated,
Functions.Func<R> func)
mutableSideEffect is similar to sideEffect(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.lang.reflect.Type resultType,
java.util.function.BiPredicate<R,R> updated,
Functions.Func<R> func)
mutableSideEffect is similar to sideEffect(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 a
CancellationScope . |
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,
java.lang.String workflowId)
Creates client stub that can be used to communicate to an existing workflow execution.
|
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 <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(java.lang.String workflowId)
Creates untyped client stub that can be used to signal or cancel a child workflow.
|
static ExternalWorkflowStub |
newUntypedExternalWorkflowStub(WorkflowExecution execution)
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(java.time.Duration duration)
Must be called instead of
Thread.sleep(long) to guarantee determinism. |
static void |
sleep(long millis)
Must be called instead of
Thread.sleep(long) to guarantee determinism. |
static java.lang.Exception |
unwrap(java.lang.Exception e)
Removes
CheckedExceptionWrapper 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.
|
public static final int DEFAULT_VERSION
public static <T> T newActivityStub(java.lang.Class<T> activityInterface, ActivityOptions options)
activityInterface
- interface type implemented by activities.options
- options that together with the properties of ActivityMethod
specify the activity invocation parameters.public static <T> T newActivityStub(java.lang.Class<T> activityInterface)
activityInterface
- interface type implemented by activitiespublic static ActivityStub newUntypedActivityStub(ActivityOptions options)
options
- specify the activity invocation parameters.public static <T> T newLocalActivityStub(java.lang.Class<T> activityInterface, LocalActivityOptions options)
activityInterface
- interface type implemented by activities.options
- options that together with the properties of ActivityMethod
specify the activity invocation parameters.public static <T> T newLocalActivityStub(java.lang.Class<T> activityInterface)
activityInterface
- interface type implemented by activitiespublic static ActivityStub newUntypedLocalActivityStub(LocalActivityOptions options)
options
- specify the local activity invocation parameters.public static <T> T newChildWorkflowStub(java.lang.Class<T> workflowInterface)
newExternalWorkflowStub(Class, String)
to get a
stub to signal a workflow without starting it.workflowInterface
- interface type implemented by activitiespublic static <T> T newChildWorkflowStub(java.lang.Class<T> workflowInterface, ChildWorkflowOptions options)
newExternalWorkflowStub(Class, String)
to get a stub to signal a workflow without
starting it.workflowInterface
- interface type implemented by activitiesoptions
- options passed to the child workflow.public static <R> R newExternalWorkflowStub(java.lang.Class<? extends R> workflowInterface, java.lang.String workflowId)
workflowInterface
- interface type implemented by activitiesworkflowId
- id of the workflow to communicate with.public static <R> R newExternalWorkflowStub(java.lang.Class<? extends R> workflowInterface, WorkflowExecution execution)
workflowInterface
- interface type implemented by activitiesexecution
- execution of the workflow to communicate with.public static Promise<WorkflowExecution> getWorkflowExecution(java.lang.Object childWorkflowStub)
newChildWorkflowStub(Class,
ChildWorkflowOptions)
or newExternalWorkflowStub(Class, String)
. Wrapped in a Promise
as child workflow start is asynchronous.public static ChildWorkflowStub newUntypedChildWorkflowStub(java.lang.String workflowType, ChildWorkflowOptions options)
workflowType
- name of the workflow type to start.options
- options passed to the child workflow.public static ChildWorkflowStub newUntypedChildWorkflowStub(java.lang.String workflowType)
workflowType
- name of the workflow type to start.public static ExternalWorkflowStub newUntypedExternalWorkflowStub(WorkflowExecution execution)
execution
- execution of the workflow to communicate with.public static ExternalWorkflowStub newUntypedExternalWorkflowStub(java.lang.String workflowId)
workflowId
- id of the workflow to communicate with.public static <T> T newContinueAsNewStub(java.lang.Class<T> workflowInterface, ContinueAsNewOptions options)
workflowInterface
- an interface type implemented by the next run of the workflowpublic static <T> T newContinueAsNewStub(java.lang.Class<T> workflowInterface)
workflowInterface
- an interface type implemented by the next run of the workflowpublic static void continueAsNew(java.lang.Object... args)
args
- arguments of the next run.newContinueAsNewStub(Class)
public static void continueAsNew(java.util.Optional<java.lang.String> workflowType, java.util.Optional<ContinueAsNewOptions> options, java.lang.Object... args)
options
- option overrides for the next run.args
- arguments of the next run.newContinueAsNewStub(Class)
public static WorkflowInfo getWorkflowInfo()
public static CancellationScope newCancellationScope(java.lang.Runnable runnable)
CancellationScope
. The Runnable.run()
calls Runnable.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 a CancellationException
. The same applies for sleep(long)
operation. When an activity or a child workflow is invoked asynchronously then they get
cancelled and a Promise
that contains their result will throw CancellationException
when Promise.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 through newDetachedCancellationScope(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");
runnable
- parameter to wrap in a cancellation scope.public static CancellationScope newCancellationScope(Functions.Proc1<CancellationScope> proc)
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();
proc
- code to wrap in the cancellation scopepublic static CancellationScope newDetachedCancellationScope(java.lang.Runnable runnable)
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();
}
runnable
- parameter to wrap in a cancellation scope.newCancellationScope(Runnable)
public static Promise<java.lang.Void> newTimer(java.time.Duration delay)
CancellationException
if enclosing scope is
cancelled.public static <E> WorkflowQueue<E> newQueue(int capacity)
public static <E> CompletablePromise<E> newPromise()
public static <E> Promise<E> newPromise(E value)
public static <E> Promise<E> newFailedPromise(java.lang.Exception failure)
public static void registerQuery(java.lang.Object queryImplementation)
QueryMethod
are registered.public static long currentTimeMillis()
System.currentTimeMillis()
to guarantee
determinism.public static void sleep(java.time.Duration duration)
Thread.sleep(long)
to guarantee determinism.public static void sleep(long millis)
Thread.sleep(long)
to guarantee determinism.public static void await(java.util.function.Supplier<java.lang.Boolean> unblockCondition)
unblockCondition
- condition that should return true to indicate that thread should
unblock.java.util.concurrent.CancellationException
- if thread (or current CancellationScope
was cancelled).public static boolean await(java.time.Duration timeout, java.util.function.Supplier<java.lang.Boolean> unblockCondition)
java.util.concurrent.CancellationException
- if thread (or current CancellationScope
was cancelled).public static <R> R retry(RetryOptions options, Functions.Func<R> fn)
Async.retry(RetryOptions, Functions.Func)
for asynchronous functions.options
- retry options that specify retry policyfn
- function to invoke and retrypublic static void retry(RetryOptions options, Functions.Proc proc)
Async.retry(RetryOptions, Functions.Func)
for asynchronous functions.options
- retry options that specify retry policyproc
- procedure to invoke and retrypublic static java.lang.RuntimeException wrap(java.lang.Exception e)
unwrap(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 of ChildWorkflowException
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); }*
public static java.lang.Exception unwrap(java.lang.Exception e)
CheckedExceptionWrapper
from causal exception
chain.e
- exception with causality chain that might contain wrapped exceptions.public static java.util.UUID randomUUID()
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.
public static java.util.Random newRandom()
public static boolean isReplaying()
public static <R> R sideEffect(java.lang.Class<R> resultClass, Functions.Func<R> func)
Caution: do not use sideEffect function to modify any workflow state. Only use the SideEffect's return value. For example this code is BROKEN:
// 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 {
....
}
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.
Here is the correct way to use sideEffect:
// Good example:
int random = Workflow.sideEffect(Integer.class, () -> random.nextInt(100));
if random < 50 {
....
} else {
....
}
If function throws any exception it is not delivered to the workflow code. It is wrapped in
Error
causing failure of the current decision.resultClass
- type of the side effectfunc
- function that returns side effect valuemutableSideEffect(String, Class, BiPredicate, Functions.Func)
public static <R> R sideEffect(java.lang.Class<R> resultClass, java.lang.reflect.Type resultType, Functions.Func<R> func)
Caution: do not use sideEffect function to modify any workflow state. Only use the SideEffect's return value. For example this code is BROKEN:
// 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 {
....
}
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.
Here is the correct way to use sideEffect:
// Good example:
int random = Workflow.sideEffect(Integer.class, () -> random.nextInt(100));
if random < 50 {
....
} else {
....
}
If function throws any exception it is not delivered to the workflow code. It is wrapped in
Error
causing failure of the current decision.resultClass
- class of the side effectresultType
- type of the side effect. Differs from resultClass for generic types.func
- function that returns side effect valuemutableSideEffect(String, Class, BiPredicate, Functions.Func)
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 to sideEffect(Class, Functions.Func)
in allowing
calls of non-deterministic functions from workflow code.
The difference between mutableSideEffect
and sideEffect(Class,
Functions.Func)
is that every new sideEffect
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.
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.sideEffect(Class, Functions.Func)
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 to sideEffect(Class, Functions.Func)
in allowing
calls of non-deterministic functions from workflow code.
The difference between mutableSideEffect
and sideEffect(Class,
Functions.Func)
is that every new sideEffect
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.
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.sideEffect(Class, Functions.Func)
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:
result = testActivities.activity1();
it should be updated to
result = testActivities.activity2();
The backwards compatible way to execute the update is
int version = Workflow.getVersion("fooChange", Workflow.DEFAULT_VERSION, 1);
String result;
if (version == Workflow.DEFAULT_VERSION) {
result = testActivities.activity1();
} else {
result = testActivities.activity2();
}
Then later if we want to have another change:
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();
}
Later when there are no workflow executions running DefaultVersion the correspondent branch can
be removed:
int version = Workflow.getVersion("fooChange", 1, 2);
String result;
if (version == 1) {
result = testActivities.activity2();
} else {
result = testActivities.activity3();
}
It is recommended to keep the GetVersion() call even if single branch is left:
Workflow.getVersion("fooChange", 2, 2);
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.
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.
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 changepublic static com.uber.m3.tally.Scope getMetricsScope()
The original metrics scope is set through WorkerOptions
when a worker starts up.
public static org.slf4j.Logger getLogger(java.lang.Class<?> clazz)
WorkerOptions
when a worker starts up.clazz
- class name to appear in logging.public static org.slf4j.Logger getLogger(java.lang.String name)
WorkerOptions
when a worker starts up.name
- name to appear in logging.public static <R> R getLastCompletionResult(java.lang.Class<R> resultClass)
resultClass
- class of the return data from last runpublic static <R> R getLastCompletionResult(java.lang.Class<R> resultClass, java.lang.reflect.Type resultType)
resultClass
- class of the return data from last runresultType
- type of the return data from last run. Differs from resultClass for generic
types.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:
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);
will eventually have search attributes as:
{
"CustomIntField": 2,
"CustomBoolField": true,
"CustomKeywordField": "Seattle",
}
searchAttributes
- map of String to Object value that can be used to search in list APIs