AWS Flow Framework For Java: Developer Guide API Version 2012-01-25
AWS Flow Framework For Java: Developer Guide API Version 2012-01-25
Developer Guide
API Version 2012-01-25
AWS Flow Framework for Java Developer Guide
Amazon's trademarks and trade dress may not be used in connection with any product or service that is not
Amazon's, in any manner that is likely to cause confusion among customers, or in any manner that disparages or
discredits Amazon. All other trademarks not owned by Amazon are the property of their respective owners, who may
or may not be affiliated with, connected to, or sponsored by Amazon.
AWS Flow Framework for Java Developer Guide
Table of Contents
What is the AWS Flow Framework for Java? .......................................................................................... 1
What's in this Guide? .................................................................................................................. 1
About Amazon Web Services ....................................................................................................... 2
Getting Started .................................................................................................................................. 3
Setting up the Framework ........................................................................................................... 3
Installing for Maven ........................................................................................................... 3
Installing for Eclipse ........................................................................................................... 4
HelloWorld Application ............................................................................................................. 12
HelloWorld Activities Implementation ................................................................................. 12
HelloWorld Workflow Worker ............................................................................................. 13
HelloWorld Workflow Starter ............................................................................................. 14
HelloWorldWorkflow Application ................................................................................................ 14
HelloWorldWorkflow Activities Worker ................................................................................ 16
HelloWorldWorkflow Workflow Worker ............................................................................... 17
HelloWorldWorkflow Workflow and Activities Implementation ............................................... 20
HelloWorldWorkflow Starter .............................................................................................. 22
HelloWorldWorkflowAsync Application ........................................................................................ 25
HelloWorldWorkflowAsync Activities Implementation ............................................................ 26
HelloWorldWorkflowAsync Workflow Implementation ........................................................... 27
HelloWorldWorkflowAsync Workflow and Activities Host and Starter ...................................... 28
HelloWorldWorkflowDistributed Application ................................................................................ 29
HelloWorldWorkflowParallel Application ..................................................................................... 30
HelloWorldWorkflowParallel Activities Worker ...................................................................... 31
HelloWorldWorkflowParallel Workflow Worker ..................................................................... 32
HelloWorldWorkflowParallel Workflow and Activities Host and Starter .................................... 33
How AWS Flow Framework for Java Works .......................................................................................... 34
Application Structure ................................................................................................................ 34
Role of the Activity Worker ............................................................................................... 36
Role of the Workflow Worker ............................................................................................ 36
Role of the Workflow Starter ............................................................................................. 36
How Amazon SWF Interacts with Your Application ................................................................ 36
For More Information ....................................................................................................... 37
Reliable Execution .................................................................................................................... 37
Providing Reliable Communication ..................................................................................... 37
Ensuring that Results are Not Lost ..................................................................................... 37
Handling Failed Distributed Components ............................................................................. 38
Distributed Execution ................................................................................................................ 38
Replaying Workflows ........................................................................................................ 38
Replay and Asynchronous Workflow Methods ...................................................................... 39
Replay and Workflow Implementation ................................................................................ 40
Task Lists and Task Execution .................................................................................................... 40
Scalable Applications ................................................................................................................ 41
Data Exchange Between Activities and Workflows ........................................................................ 42
The Promise<T> Type ....................................................................................................... 42
Data Converters and Marshaling ......................................................................................... 43
Data Exchange Between Applications and Workflow Executions ...................................................... 43
Timeout Types ......................................................................................................................... 44
Timeouts in Workflow and Decision Tasks ........................................................................... 44
Timeouts in Activity Tasks ................................................................................................. 45
Best Practices .................................................................................................................................. 47
Making Changes to Decider Code ............................................................................................... 47
The Replay Process and Code Changes ................................................................................ 47
Example Scenario ............................................................................................................. 47
Solutions ......................................................................................................................... 52
By using the AWS Flow Framework, you can focus on implementing your workflow logic, while leaving
the details of communication and coordination with Amazon SWF to the framework. Behind the scenes,
the framework uses Amazon SWF to manage your workflow's execution and make it scalable, reliable,
and auditable. AWS Flow Framework-based workflows are highly concurrent, and workflows can be
distributed across multiple components—each of which can run as separate processes on separate
computers and can be scaled independently. The workflow will continue to run if any of its components
are running, making it highly fault tolerant.
Topics
• What's in this Guide? (p. 1)
• About Amazon Web Services (p. 2)
Getting Started with the AWS Flow Framework for Java (p. 3)
If you are just starting out with the AWS Flow Framework for Java, you should first read through the
Getting Started with the AWS Flow Framework for Java (p. 3) section. It will guide you through
downloading and installing the AWS Flow Framework for Java, how to set up your development
environment, and lead you through a simple example of creating a workflow using the Flow
Framework.
How AWS Flow Framework for Java Works (p. 34)
Introduces basic Amazon SWF and Flow Framework concepts, describing the basic structure of a
Flow Framework application and how data is exchanged between parts of a distributed workflow.
AWS Flow Framework for Java Programming Guide (p. 56)
This chapter provides basic programming guidance for developing workflow applications with the
AWS Flow Framework for Java, including how to register activity and workflow types, implement
workflow clients, create child workflows, handle errors, and more.
Under the Hood (p. 119)
This chapter provides a more in-depth look at the way the AWS Flow Framework for Java works,
providing you with additional information about the order of execution of asynchronous workflows
and a logical step-through of a standard workflow execution.
This chapter provides information about common errors that you can use to troubleshoot your
workflows, or that you can use to learn to avoid common errors.
AWS Flow Framework for Java Reference (p. 127)
This chapter is a reference to the Annotations, Exceptions and Packages that the AWS Flow
Framework for Java adds to the SDK for Java.
Document History (p. 136)
This chapter provides details about major changes to the documentation. New sections and topics as
well as significantly revised topics are listed here.
The following is a brief description of the example applications. They include complete source code so
you can implement and run the applications yourself. Before starting, you should first configure your
development environment and create an AWS Flow Framework for Java project, like in Setting up the
AWS Flow Framework for Java (p. 3).
• HelloWorld Application (p. 12) introduces workflow applications by implementing Hello World as a
standard Java application, but structuring it like a workflow application.
• HelloWorldWorkflow Application (p. 14) uses the AWS Flow Framework for Java to convert
HelloWorld into an Amazon SWF workflow.
• HelloWorldWorkflowAsync Application (p. 25) modifies HelloWorldWorkflow to use an
asynchronous workflow method.
• HelloWorldWorkflowDistributed Application (p. 29) modifies HelloWorldWorkflowAsync so that
the workflow and activity workers can run on separate systems.
• HelloWorldWorkflowParallel Application (p. 30) modifies HelloWorldWorkflow to run two
activities in parallel.
This topic provides information about additional steps required to use the AWS Flow Framework for Java.
Steps are provided for Eclipse and Maven.
Topics
• Installing for Maven (p. 3)
• Installing for Eclipse (p. 4)
To set up the flow framework for Maven, add the following dependency to your project's pom.xml file:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-swf-build-tools</artifactId>
<version>1.0</version>
</dependency>
The Amazon SWF build tools are open source—to view or download the code or to build the tools
yourself, visit the repository at https://github.com/aws/aws-swf-build-tools.
Topics
• Installing the AWS Toolkit for Eclipse (p. 4)
• Creating an AWS Flow Framework for Java Project (p. 5)
If you installed all of the available packages (by choosing the AWS Toolkit for Eclipse top-level
node, or choosing Select All), both of these packages were automatically selected and installed
for you.
1. Launch Eclipse.
2. To select the Java perspective, choose Window, Open Perspective, Java.
3. Choose File, New, AWS Java Project.
After creating your AWS Java project, enable annotation processing for the project. The AWS Flow
Framework for Java includes an annotation processor that generates several key classes based on
annotated source code.
Note
You will need to rebuild your project after enabling annotation processing.
Topics
• Prerequisites (p. 8)
• Configuring AspectJ Load-Time Weaving (p. 8)
• AspectJ Compile-Time Weaving (p. 9)
• Working around issues with AspectJ and Eclipse (p. 11)
Prerequisites
Before configuring AspectJ, you need the AspectJ version that matches your Java version:
• If you are using Java 8, download the latest AspectJ 1.8.X release.
• If you are using Java 7, download the latest AspectJ 1.7.X release.
• If you are using Java 6, download the latest AspectJ 1.6.X release.
You can download either of these versions of AspectJ from the Eclipse download page.
After you have finished downloading AspectJ, execute the downloaded .jar file to install AspectJ.
The AspectJ installation will ask you where you would like to install the binaries, and on the final
screen, will provide recommended steps for completing the installation. Remember the location of the
aspectjweaver.jar file; you'll need it to configure AspectJ in Eclipse.
To configure AspectJ load-time weaving for your AWS Flow Framework for Java project, first designate
the AspectJ JAR file as a Java agent, and then configure it by adding an aop.xml file to your project.
-javaagent:/your_path/aspectj/lib/aspectjweaver.jar
-javaagent:C:\your_path\aspectj\lib\aspectjweaver.jar
To configure AspectJ for AWS Flow Framework for Java, add an aop.xml file to the project.
<aspectj>
<aspects>
<aspect
name="com.amazonaws.services.simpleworkflow.flow.aspectj.AsynchronousAspect"/>
<aspect
name="com.amazonaws.services.simpleworkflow.flow.aspectj.ExponentialRetryAspect"/>
</aspects>
<weaver options="-verbose">
<include within="MySimpleWorkflow.*"/>
</weaver>
</aspectj>
The value of <include within=""/> depends on how you name your project's packages. The
above example assumes that the project's packages followed the pattern MySimpleWorkflow.*. Use a
value appropriate for your own project's packages.
To enable and configure AspectJ compile-time weaving, you must first install the AspectJ developer tools
for Eclipse, which are available from http://www.eclipse.org/aspectj/downloads.php.
Important
Be sure that the AspectJ version matches your Eclipse version, or installation of AspectJ will
fail.
3. Choose Add to add the location. Once the location is added, the AspectJ developer tools will be
listed.
4. Choose Select All to select all of the AspectJ developer tools, then choose Next to install them.
Note
You will need to restart Eclipse to complete the installation.
1. In Project Explorer, right-click your project and select Configure > Convert to AspectJ Project.
The AspectJ Eclipse plug-in has an issue that can prevent generated code from being compiled. The
fastest way to force generated code to be recognized after you recompile it is to change the order of the
source directory that contains the generated code on the Order and Export tab of the Java Build Path
settings page (for example, you can set the default to apt/java).
HelloWorld Application
To introduce the way Amazon SWF applications are structured, we'll create a Java application that
behaves like a workflow, but that runs locally in a single process. No connection to Amazon Web Services
will be needed.
Note
The HelloWorldWorkflow (p. 14) example builds upon this one, connecting to Amazon SWF
to handle management of the workflow.
• An activities worker supports a set of activities, each of which is a method that executes independently
to perform a particular task.
• A workflow worker orchestrates the activities' execution and manages data flow. It is a programmatic
realization of a workflow topology, which is basically a flow chart that defines when the various
activities execute, whether they execute sequentially or concurrently, and so on.
• A workflow starter starts a workflow instance, called an execution, and can interact with it during
execution.
HelloWorld is implemented as three classes and two related interfaces, which are described in the
following sections. Before starting, you should set up your development environment and create a new
AWS Java project as described in Setting up the AWS Flow Framework for Java (p. 3). The packages
used for the following walkthroughs are all named helloWorld.XYZ. To use those names, set the
within attribute in aop.xml as follows:
...
<weaver options="-verbose">
<include within="helloWorld..*"/>
</weaver>
To implement HelloWorld, create a new Java package in your AWS SDK project named
helloWorld.HelloWorld and add the following files:
The details are discussed in the following sections and include the complete code for each component,
which you can add to the appropriate file.
@Override
public String getGreeting(String name) {
return "Hello " + name + "!";
}
@Override
public void say(String what) {
System.out.println(what);
}
}
Activities are independent of each other and can often be used by different workflows. For example,
any workflow can use the say activity to print a string to the console. Workflows can also have multiple
activity implementations, each performing a different set of tasks.
The three activities execute in sequence, and the data flows from one activity to the next.
The HelloWorld workflow worker has a single method, the workflow's entry point, which is defined in the
GreeterWorkflow interface, as follows:
GreeterMain creates an instance of GreeterWorkflowImpl and calls greet to run the workflow
worker. Run GreeterMain as a Java application and you should see "Hello World!" in the console output.
HelloWorldWorkflow Application
Although the basic HelloWorld (p. 12) example is structured like a workflow, it differs from an Amazon
SWF workflow in several key respects:
Runs locally as a single process. Runs as multiple processes that can be distributed across
multiple systems, including Amazon EC2 instances, private
data centers, client computers, and so on. They don't even
have to run the same operating system.
Activities are synchronous methods, Activities are represented by asynchronous methods, which
which block until they complete. return immediately and allow the workflow to perform
other tasks while waiting for the activity to complete.
The workflow worker interacts with Workflow workers interact with activities workers by
an activities worker by calling the using HTTP requests, with Amazon SWF acting as an
appropriate method. intermediary.
The workflow starter interacts with Workflow starters interact with workflow workers by
workflow worker by calling the using HTTP requests, with Amazon SWF acting as an
appropriate method. intermediary.
You could implement a distributed asynchronous workflow application from scratch, for example, by
having your workflow worker interact with an activities worker directly through web services calls.
However, you must then implement all the complicated code required to manage the asynchronous
execution of multiple activities, handle the data flow, and so on. The AWS Flow Framework for Java and
Amazon SWF take care of all those details, which allows you to focus on implementing the business
logic.
HelloWorldWorkflow is a modified version of HelloWorld that runs as an Amazon SWF workflow. The
following figure summarizes how the two applications work.
HelloWorld runs as a single process and the starter, workflow worker, and activities worker interact
by using conventional method calls. With HelloWorldWorkflow, the starter, workflow worker,
and activities worker are distributed components that interact through Amazon SWF by using HTTP
requests. Amazon SWF manages the interaction by maintaining lists of workflow and activities tasks,
which it dispatches to the respective components. This section describes how the framework works for
HelloWorldWorkflow.
HelloWorldWorkflow is implemented by using the AWS Flow Framework for Java API, which handles the
sometimes complicated details of interacting with Amazon SWF in the background and simplifies the
development process considerably. You can use the same project that you did for HelloWorld, which is
already configured for AWS Flow Framework for Java applications. However, to run the application, you
must set up an Amazon SWF account, as follows:
• Sign up for an AWS account, if you don't already have one, at Amazon Web Services.
• Assign your account's Access ID and secret ID to the AWS_ACCESS_KEY_ID and AWS_SECRET_KEY
environment variables, respectively. It's a good practice to not expose the literal key values in your
code. Storing them in environment variables is a convenient way to handle the issue.
• Sign up for Amazon SWF account at Amazon Simple Workflow Service.
• Log into the AWS Management Console and select the Amazon SWF service.
• Choose Manage Domains in the upper right corner and register a new Amazon SWF domain. A
domain is a logical container for your application resources, such as workflow and activity types,
and workflow executions. You can use any convenient domain name, but the walkthroughs use
"helloWorldWalkthrough".
For more details about how to manage Amazon SWF workflows, see Getting Set Up.
• The activity methods—which perform the actual tasks—are defined in an interface and implemented
in a related class.
• An ActivityWorker class manages the interaction between the activity methods and Amazon SWF.
• An activities host application registers and starts the activities worker, and handles cleanup.
This section discusses the activity methods; the other two classes are discussed later.
import com.amazonaws.services.simpleworkflow.flow.annotations.Activities;
import com.amazonaws.services.simpleworkflow.flow.annotations.ActivityRegistrationOptions;
@ActivityRegistrationOptions(defaultTaskScheduleToStartTimeoutSeconds = 300,
defaultTaskStartToCloseTimeoutSeconds = 10)
@Activities(version="1.0")
This interface wasn't strictly necessary for HelloWorld, but it is for an AWS Flow Framework for Java
application. Notice that the interface definition itself hasn't changed. However, you must apply two AWS
Flow Framework for Java annotations, @ActivityRegistrationOptions (p. 128) and @Activities (p. 127),
to the interface definition. The annotations provide configuration information and direct the AWS Flow
Framework for Java annotation processor to use the interface definition to generate an activities client
class, which is discussed later.
@ActivityRegistrationOptions has several named values that are used to configure the activities'
behavior. HelloWorldWorkflow specifies two timeouts:
These timeouts ensure that the activity completes its task in a reasonable amount of time. If either
timeout is exceeded, the framework generates an error and the workflow worker must decide how to
handle the issue. For a discussion of how to handle such errors, see Error Handling (p. 101).
@Activities has several values, but typically it just specifies the activities' version number,
which allows you to keep track of different generations of activity implementations. If you change
an activity interface after you have registered it with Amazon SWF, including changing the
@ActivityRegistrationOptions values, you must use a new version number.
return "World";
}
@Override
public String getGreeting(String name) {
return "Hello " + name;
}
@Override
public void say(String what) {
System.out.println(what);
}
}
Notice that the code is identical to the HelloWorld implementation. At its core, an AWS Flow Framework
activity is just a method that executes some code and perhaps returns a result. The difference between
a standard application and an Amazon SWF workflow application lies in how the workflow executes the
activities, where the activities execute, and how the results are returned to the workflow worker.
This section discusses the workflow implementation and activities client; the WorkflowWorker class is
discussed later.
import com.amazonaws.services.simpleworkflow.flow.annotations.Execute;
import com.amazonaws.services.simpleworkflow.flow.annotations.Workflow;
import com.amazonaws.services.simpleworkflow.flow.annotations.WorkflowRegistrationOptions;
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 3600)
public interface GreeterWorkflow {
@Execute(version = "1.0")
public void greet();
}
This interface also isn't strictly necessary for HelloWorld but is essential for an AWS Flow Framework for
Java application. You must apply two AWS Flow Framework for Java annotations, @Workflow (p. 131)
and @WorkflowRegistrationOptions (p. 131), to the workflow interface definition. The annotations
provide configuration information and also direct the AWS Flow Framework for Java annotation
processor to generate a workflow client class based on the interface, as discussed later.
@Workflow has one optional parameter, dataConverter, which is often used with its default value,
NullDataConverter, which indicates that JsonDataConverter should be used.
The GreeterWorkflow interface definition differs from HelloWorld in one important way, the
@Execute (p. 129) annotation. Workflow interfaces specify the methods that can be called by
applications such as the workflow starter and are limited to a handful of methods, each with a particular
role. The framework doesn't specify a name or parameter list for workflow interface methods; you use a
name and parameter list that is suitable for your workflow and apply an AWS Flow Framework for Java
annotation to identify the method's role.
• It identifies greet as the workflow's entry point—the method that the workflow starter calls to start
the workflow. In general, an entry point can take one or more parameters, which allows the starter to
initialize the workflow, but this example doesn't require initialization.
• It specifies the workflow's version number, which allows you to keep track of different generations of
workflow implementations. To change a workflow interface after you have registered it with Amazon
SWF, including changing the timeout values, you must use a new version number.
For information about the other methods that can be included in a workflow interface, see Workflow and
Activity Contracts (p. 57).
import com.amazonaws.services.simpleworkflow.flow.core.Promise;
• How to execute an activity method that might be running in a different process, perhaps on a different
system.
• How to execute an activity method asynchronously.
• How to manage activities' input and return values. For example, if the Activity A return value is an
input to Activity B, you must ensure that Activity B doesn't execute until Activity A is complete.
You can implement a variety of workflow topologies through the application's control flow by using
familiar Java flow control combined with the activities client and the Promise<T>.
Activities Client
GreeterActivitiesClientImpl is basically a proxy for GreeterActivitiesImpl that allows a
workflow implementation to execute the GreeterActivitiesImpl methods asynchronously.
A workflow worker executes an activity by calling the corresponding client method. The method is
asynchronous and immediately returns a Promise<T> object, where T is the activity's return type.
The returned Promise<T> object is basically a placeholder for the value that the activity method will
eventually return.
• When the activities client method returns, the Promise<T> object is initially in an unready state,
which indicates that the object doesn't yet represent a valid return value.
• When the corresponding activity method completes its task and returns, the framework assigns the
return value to the Promise<T> object and puts it in the ready state.
Promise<T> Type
The primary purpose of Promise<T> objects is to manage data flow between asynchronous
components and control when they execute. It relieves your application of the need to explicitly manage
synchronization or depend on mechanisms such as timers to ensure that asynchronous components
don't execute prematurely. When you call an activities client method, it immediately returns but the
framework defers executing the corresponding activity method until any input Promise<T> objects are
ready and represent valid data.
From GreeterWorkflowImpl perspective, all three activities client methods return immediately. From
the GreeterActivitiesImpl perspective, the framework doesn't call getGreeting until name
completes, and doesn't call say until getGreeting completes.
By using Promise<T> to pass data from one activity to the next, HelloWorldWorkflow not only
ensures that activity methods don't attempt to use invalid data, it also controls when the activities
execute and implicitly defines the workflow topology. Passing each activity's Promise<T> return value
to the next activity requires the activities to execute in sequence, defining the linear topology discussed
earlier. With AWS Flow Framework for Java, you don't need to use any special modeling code to define
even complex topologies, just standard Java flow control and Promise<T>. For an example of how to
implement a simple parallel topology, see HelloWorldWorkflowParallel Activities Worker (p. 31).
Note
When an activity method such as say doesn't return a value, the corresponding client method
returns a Promise<Void> object. The object doesn't represent data, but it is initially unready
and becomes ready when the activity completes. You can therefore pass a Promise<Void>
object to other activities client methods to ensure that they defer execution until the original
activity completes.
Promise<T> allows a workflow implementation to use activities client methods and their return values
much like synchronous methods. However, you must be careful about accessing a Promise<T> object's
value. Unlike the Java Future<T> type, the framework handles synchronization for Promise<T>, not the
application. If you call Promise<T>.get and the object isn't ready, get throws an exception. Notice that
HelloWorldWorkflow never accesses a Promise<T> object directly; it simply passes the objects from
one activity to the next. When an object becomes ready, the framework extracts the value and passes it
to the activity method as a standard type.
Promise<T> objects should be accessed only by asynchronous code, where the framework guarantees
that the object is ready and represents a valid value. HelloWorldWorkflow deals with this issue
by passing Promise<T> objects only to activities client methods. You can access a Promise<T>
object's value in your workflow implementation by passing the object to an asynchronous workflow
method, which behaves much like an activity. For an example, see HelloWorldWorkflowAsync
Application (p. 25).
To associate the activity and workflow implementations with the corresponding worker objects, you
implement one or more worker applications which:
If you want to run the workflow and activities as separate processes, you must implement separate
workflow and activities worker hosts. For an example, see HelloWorldWorkflowDistributed
Application (p. 29). For simplicity, HelloWorldWorkflow implements a single worker host that runs
activities and workflow workers in the same process, as follows:
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient;
import com.amazonaws.services.simpleworkflow.flow.ActivityWorker;
import com.amazonaws.services.simpleworkflow.flow.WorkflowWorker;
aw.addActivitiesImplementation(new GreeterActivitiesImpl());
aw.start();
GreeterWorker has no HelloWorld counterpart, so you must add a Java class named GreeterWorker
to the project and copy the example code to that file.
The first step is to create and configure an AmazonSimpleWorkflowClient object, which invokes the
underlying Amazon SWF service methods. To do so, GreeterWorker:
1. Creates a ClientConfiguration object and specifies a socket timeout of 70 seconds. This value specifies
long to wait for data to be transferred over an established open connection before closing the socket.
2. Creates a BasicAWSCredentials object to identify the Amazon AWS account and passes the account
keys to the constructor. For convenience, and to avoid exposing them as plain text in the code, the
keys are stored as environment variables.
3. Creates an AmazonSimpleWorkflowClient object to represent the workflow, and passes the
BasicAWSCredentials and ClientConfiguration objects to the constructor.
4. Sets the client object's service endpoint URL. Amazon SWF is currently available in all AWS regions.
• domain is the workflow's Amazon SWF domain name, which you created when you set up your
Amazon SWF account. HelloWorldWorkflow assumes that you are running the workflow in the
"helloWorldWalkthrough" domain.
• taskListToPoll is the name of the task lists that Amazon SWF uses to manage communication
between the workflow and activities workers. You can set the name to any convenient string.
HelloWorldWorkflow uses "HelloWorldList" for both workflow and activity task lists. Behind the scenes,
the names end up in different namespaces, so the two task lists are distinct.
GreeterWorker uses the string constants and the AmazonSimpleWorkflowClient object to create
worker objects, which manage the interaction between the activities and worker implementations and
Amazon SWF. In particular, the worker objects handle the task of polling the appropriate task list for
tasks.
You can run GreeterWorker successfully at this point. It registers the workflow and activities
with Amazon SWF and starts the worker objects polling their respective task lists. To verify this,
run GreeterWorker and go to the Amazon SWF console and select helloWorldWalkthrough
from the list of domains. If you choose Workflow Types in the Navigation pane, you should see
GreeterWorkflow.greet:
However, if you choose Workflow Executions, you will not see any active executions. Although the
workflow and activities workers are polling for tasks, we have not yet started a workflow execution.
HelloWorldWorkflow Starter
The final piece of the puzzle is to implement a workflow starter, which is an application that starts the
workflow execution. The execution state is stored by Amazon SWF, so that you can view its history and
execution status. HelloWorldWorkflow implements a workflow starter by modifying the GreeterMain
class, as follows:
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient;
1. Create an external client factory object and pass the AmazonSimpleWorkflowClient object and
Amazon SWF domain name to the constructor. The client factory object is created by the framework's
annotation processor, which creates the object name by simply appending "ClientExternalFactoryImpl"
to the workflow interface name.
2. Create an external client object by calling the factory object's getClient method, which creates the
object name by appending "ClientExternal" to the workflow interface name. You can optionally pass
getClient a string which Amazon SWF will use to identify this instance of the workflow. Otherwise,
Amazon SWF represents a workflow instance by using a generated GUID.
The client returned from the factory will only create workflows that are named with the string passed
into the getClient method, (the client returned from the factory already has state in Amazon SWF). To
run a workflow with a different id, you need to go back to the factory and create a new client with the
different id specified.
The workflow client exposes a greet method that GreeterMain calls to begin the workflow, as
greet() was the method specified with the @Execute annotation.
Note
The annotation processor also creates an internal client factory object that is used to create
child workflows. For details, see Child Workflow Executions (p. 85).
Shut down GreeterWorker for the moment if it is still running, and run GreeterMain. You should now
see someID on the Amazon SWF console's list of active workflow executions:.
If you choose someID and choose the Events tab, the events are displayed:
Note
If you started GreeterWorker earlier, and it is still running, you will see a longer event list for
reasons discussed shortly. Stop GreeterWorker and try running GreaterMain again.
The reason that the workflow is blocked at the first decision task is that the workflow is distributed
across two applications, GreeterMain and GreeterWorker. GreeterMain started the workflow
execution, but GreeterWorker isn't running, so the workers aren't polling the lists and executing tasks.
You can run either application independently, but you need both for workflow execution to proceed
beyond the first decision task. If you now run GreeterWorker, the workflow and activity workers will
start polling and the various tasks will be completed rapidly. If you now check the Events tab, the first
batch of events is displayed.
You can choose individual events to get more information. By the time you've finished looking, the
workflow should have printed "Hello World!" to your console.
After the workflow completes, it no longer appears on the list of active executions. However, if you want
to review it, choose the Closed execution status button and then choose List Executions. This displays all
the completed workflow instances in the specified domain (helloWorldWalkthrough) that have not
exceeded their retention time, which you specified when you created the domain.
Notice that each workflow instance has a unique Run ID value. You can use the same Execution ID for
different workflow instances, but only for one active execution at a time.
HelloWorldWorkflowAsync Application
Sometimes, it's preferable to have a workflow perform certain tasks locally instead of using an
activity. However, workflow tasks often involve processing the values represented by Promise<T>
objects. If you pass a Promise<T> object to a synchronous workflow method, the method executes
immediately but it can't access the Promise<T> object's value until the object is ready. You could poll
Promise<T>.isReady until it returns true, but that's inefficient and the method might block for a
long time. A better approach is to use an asynchronous method.
Note
Because of the way that the AWS Flow Framework for Java executes the workflow, asynchronous
methods typically execute multiple times, so you should use them only for quick low-overhead
tasks. You should use activities to perform lengthy tasks such as large computations. For details,
see AWS Flow Framework Basic Concepts: Distributed Execution (p. 38).
The following sections describe how to modify the original HelloWorldWorkflow code to use an
asynchronous method.
import com.amazonaws.services.simpleworkflow.flow.annotations.Activities;
import com.amazonaws.services.simpleworkflow.flow.annotations.ActivityRegistrationOptions;
@Activities(version="2.0")
@ActivityRegistrationOptions(defaultTaskScheduleToStartTimeoutSeconds = 300,
defaultTaskStartToCloseTimeoutSeconds = 10)
public interface GreeterActivities {
public String getName();
public void say(String what);
}
This interface is similar to the one used by HelloWorldWorkflow, with the following exceptions:
• It omits the getGreeting activity; that task is now handled by an asynchronous method.
• The version number is set to 2.0. After you have registered an activities interface with Amazon SWF,
you can't modify it unless you change the version number.
The remaining activity method implementations are identical to HelloWorldWorkflow. Just delete
getGreeting from GreeterActivitiesImpl.
HelloWorldWorkflowAsync Workflow
Implementation
HelloWorldWorkflowAsync defines the workflow interface as follows:
import com.amazonaws.services.simpleworkflow.flow.annotations.Execute;
import com.amazonaws.services.simpleworkflow.flow.annotations.Workflow;
import com.amazonaws.services.simpleworkflow.flow.annotations.WorkflowRegistrationOptions;
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 3600)
public interface GreeterWorkflow {
@Execute(version = "2.0")
public void greet();
}
The interface is identical to HelloWorldWorkflow apart from a new version number. As with activities, if
you want to change a registered workflow, you must change its version.
import com.amazonaws.services.simpleworkflow.flow.annotations.Asynchronous;
import com.amazonaws.services.simpleworkflow.flow.core.Promise;
@Override
public void greet() {
Promise<String> name = operations.getName();
Promise<String> greeting = getGreeting(name);
operations.say(greeting);
}
@Asynchronous
private Promise<String> getGreeting(Promise<String> name) {
String returnString = "Hello " + name.get() + "!";
return Promise.asPromise(returnString);
}
}
1. Execute the getName activity, which immediately returns a Promise<String> object, name, that
represents the name.
2. Call the getGreeting asynchronous method and pass it the name object. getGreeting immediately
returns a Promise<String> object, greeting, that represents the greeting.
3. Execute the say activity and pass it the greeting object.
4. When getName completes, name becomes ready and getGreeting uses its value to construct the
greeting.
5. When getGreeting completes, greeting becomes ready and say prints the string to the console.
The difference is that, instead of calling the activities client to execute a getGreeting activity, greet
calls the asynchronous getGreeting method. The net result is the same, but the getGreeting method
works somewhat differently than the getGreeting activity.
• The workflow worker uses standard function call semantics to execute getGreeting. However, the
asynchronous execution of the activity is mediated by Amazon SWF.
• getGreeting runs in the workflow implementation's process.
• getGreeting returns a Promise<String> object rather than a String object. To get the String
value held by the Promise, you call its get() method. However, since the activity is being run
asynchronously, its return value might not be ready immediately; get() will raise an exception until
the return value of the asynchronous method is available.
For more information about how Promise works, see AWS Flow Framework Basic Concepts: Data
Exchange Between Activities and Workflows (p. 42).
getGreeting creates a return value by passing the greeting string to the static Promise.asPromise
method. This method creates a Promise<T> object of the appropriate type, sets the value, and puts it in
the ready state.
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient;
import com.amazonaws.services.simpleworkflow.flow.ActivityWorker;
import com.amazonaws.services.simpleworkflow.flow.WorkflowWorker;
To execute the workflow, run GreeterWorker and GreeterMain, just as with HelloWorldWorkflow.
HelloWorldWorkflowDistributed Application
With HelloWorldWorkflow and HelloWorldWorkflowAsync, Amazon SWF mediates the interaction
between the workflow and activities implementations, but they run locally as a single process.
GreeterMain is in a separate process, but it still runs on the same system.
A key feature of Amazon SWF is that it supports distributed applications. For example, you could run the
workflow worker on an Amazon EC2 instance, the workflow starter on a data center computer, and the
activities on a client desktop computer. You can even run different activities on different systems.
• The workflow and workflow starter run as separate processes on one system.
• The activities run on a separate system.
You don't need to change the workflow or activities implementations to run them on separate systems,
not even the version numbers. You also don't need to modify GreeterMain. All you need to change is
the activities and workflow host.
With HelloWorldWorkflowAsync, a single application serves as the workflow and activity host. To run the
workflow and activity implementations on separate systems, you must implement separate applications.
Delete GreeterWorker from the project and add two new class files, GreeterWorkflowWorker and
GreeterActivitiesWorker.
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient;
import com.amazonaws.services.simpleworkflow.flow.ActivityWorker;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient;
import com.amazonaws.services.simpleworkflow.flow.WorkflowWorker;
Other than the fact that the activities are running on a different system than the workflow worker and
workflow starter, the workflow works in exactly the same way as HelloWorldAsync. However, because
println call that prints "Hello World!" to the console is in the say activity, the output will appear on the
system that is running the activities worker.
HelloWorldWorkflowParallel Application
The preceding versions of Hello World! all use a linear workflow topology. However, Amazon SWF
isn't limited to linear topologies. The HelloWorldWorkflowParallel application is a modified version of
HelloWorldWorkflow that uses a parallel topology, as shown in the following figure.
With HelloWorldWorkflowParallel, getName and getGreeting run in parallel and each return part of
the greeting. say then merges the two strings into a greeting, and prints it to the console.
import com.amazonaws.services.simpleworkflow.flow.annotations.Activities;
import com.amazonaws.services.simpleworkflow.flow.annotations.ActivityRegistrationOptions;
@Activities(version="5.0")
@ActivityRegistrationOptions(defaultTaskScheduleToStartTimeoutSeconds = 300,
defaultTaskStartToCloseTimeoutSeconds = 10)
public interface GreeterActivities {
public String getName();
public String getGreeting();
public void say(String greeting, String name);
}
@Override
public String getName() {
return "World!";
}
@Override
public String getGreeting() {
return "Hello ";
}
@Override
public void say(String greeting, String name) {
System.out.println(greeting + name);
}
}
getName and getGreeting now simply return half of the greeting string. say concatenates the two
pieces to produce the complete phrase, and prints it to the console.
import com.amazonaws.services.simpleworkflow.flow.annotations.Execute;
import com.amazonaws.services.simpleworkflow.flow.annotations.Workflow;
import com.amazonaws.services.simpleworkflow.flow.annotations.WorkflowRegistrationOptions;
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 3600)
public interface GreeterWorkflow {
@Execute(version = "5.0")
public void greet();
}
The class is identical to the HelloWorldWorkflow version, except that the version number has been
changed to match the activities worker.
import com.amazonaws.services.simpleworkflow.flow.core.Promise;
At a glance, this implementation looks very similar to HelloWorldWorkflow; the three activities client
methods execute in sequence. However, the activities don't.
The say activity takes both greeting and name as input parameters. Because they are Promise<T>
objects, say defers execution until both activities complete, and then constructs and prints the greeting.
Notice that HelloWorldWorkflowParallel doesn't use any special modeling code to define the workflow
topology. It does it implicitly by using standard Java flow control and taking advantage of the properties
of Promise<T> objects. AWS Flow Framework for Java applications can implement even complex
topologies simply by using Promise<T> objects in conjunction with conventional Java control flow
constructs.
To execute the workflow, run GreeterWorker and GreeterMain, just as with HelloWorldWorkflow.
Topics
• AWS Flow Framework Basic Concepts: Application Structure (p. 34)
• AWS Flow Framework Basic Concepts: Reliable Execution (p. 37)
• AWS Flow Framework Basic Concepts: Distributed Execution (p. 38)
• AWS Flow Framework Basic Concepts: Task Lists and Task Execution (p. 40)
• AWS Flow Framework Basic Concepts: Scalable Applications (p. 41)
• AWS Flow Framework Basic Concepts: Data Exchange Between Activities and Workflows (p. 42)
• AWS Flow Framework Basic Concepts: Data Exchange Between Applications and Workflow
Executions (p. 43)
• Amazon SWF Timeout Types (p. 44)
Note
Implementing these components in three separate applications is convenient conceptually, but
you can create applications to implement this functionality in a variety of ways. For example,
you can use a single host application for the activity and workflow workers, or use separate
activity and workflow hosts. You can also have multiple activity workers, each handling a
different set of activities on separate hosts, and so on.
The three AWS Flow Framework components interact indirectly by sending HTTP requests to Amazon
SWF, which manages the requests. Amazon SWF does the following:
• Maintains one or more decision task lists, which determine the next step to be performed by a
workflow worker.
• Maintains one or more activities task lists, which determine which tasks will be performed by an
activity worker.
• Maintains a detailed step-by-step history of the workflow's execution.
With the AWS Flow Framework, your application code doesn't need to deal directly with many of the
details shown in the figure, such as sending HTTP requests to Amazon SWF. You simply call AWS Flow
Framework methods and the framework handles the details behind the scenes.
• The activities implementation, which includes a set of activity methods that perform particular tasks
for the workflow.
• An ActivityWorker object, which uses HTTP long poll requests to poll Amazon SWF for activity tasks
to be performed. When a task is needed, Amazon SWF responds to the request by sending the
information required to perform the task. The ActivityWorker object then calls the appropriate activity
method, and returns the results to Amazon SWF.
• The workflow implementation, which includes the activity orchestration logic, handles failed activities,
and so on.
• An activities client, which serves as a proxy for the activity worker and enables the workflow worker to
schedule activities to be executed asynchronously.
• A WorkflowWorker object, which uses HTTP long poll requests to poll Amazon SWF for decision
tasks. If there are tasks on the workflow task list, Amazon SWF responds to the request by returning
the information that is required to perform the task. The framework then executes the workflow to
perform the task and returns the results to Amazon SWF.
The workflow starter uses a workflow client to start the workflow execution, interacts with the
workflow as needed during execution, and handles cleanup. The workflow starter could be a locally-run
application, a web application, the AWS CLI or even the AWS Management Console.
• If the request is from a worker, polling for available tasks, Amazon SWF responds directly to the
worker if a task is available. For more information about how polling works, see Polling for Tasks in the
Amazon Simple Workflow Service Developer Guide.
• If the request is a notification from an activity worker that a task is complete, Amazon SWF records the
information in the execution history and adds a task to the decision task list to inform the workflow
worker that the task is complete, allowing it to proceed to the next step.
• If the request is from the workflow worker to execute an activity, Amazon SWF records the information
in the execution history and adds a task to the activities task list to direct an activity worker to execute
the appropriate activity method.
This approach allows workers to run on any system with an Internet connection, including Amazon EC2
instances, corporate data centers, client computers, and so on. They don't even have to be running the
same operating system. Because the HTTP requests originate with the workers, there is no need for
externally visible ports; workers can run behind a firewall.
• How to provide reliable communication between asynchronous distributed components, such as long-
running components on remote systems.
• How to ensure that results are not lost if a component fails or is disconnected, especially for long-
running applications.
• How to handle failed distributed components.
Applications can rely on the AWS Flow Framework and Amazon SWF to manage these issues. We'll
explore how Amazon SWF provides mechanisms to ensure that your workflows operate reliably and
in a predictable way, even when they are long-running and depend on asynchronous tasks carried out
computationally and with human interaction.
• Amazon SWF durably stores scheduled activity and workflow tasks and guarantees that they will be
performed at most once.
• Amazon SWF guarantees that an activity task will either complete successfully and return a valid result
or it will notify the workflow worker that the task failed.
• Amazon SWF durably stores each completed activity's result or, for failed activities, it stores relevant
error information.
The AWS Flow Framework then uses the activity results from Amazon SWF to determine how to proceed
with the workflow's execution.
To accommodate scenarios such as these, AWS Flow Framework workflows and activities can take
arbitrarily long to complete: up to a limit of one year for a workflow execution. Reliably executing long
running processes requires a mechanism to durably store the workflow's execution history on an ongoing
basis.
The AWS Flow Framework handles this by depending on Amazon SWF, which maintains a running history
of each workflow instance. The workflow's history provides a complete and authoritative record of
the workflow's progress, including all the workflow and activity tasks that have been scheduled and
completed, and the information returned by completed or failed activities.
AWS Flow Framework applications usually don't need to interact with the workflow history directly,
although they can access it if necessary. For most purposes, applications can simply let the framework
interact with the workflow history behind the scenes. For a full discussion of workflow history, see
Workflow History in the Amazon Simple Workflow Service Developer Guide.
Stateless Execution
The execution history allows workflow workers to be stateless. If you have multiple instances of
a workflow or activity worker, any worker can perform any task. The worker receives all the state
information that it needs to perform the task from Amazon SWF.
This approach makes workflows more reliable. For example, if an activity worker fails, you don't have to
restart the workflow. Just restart the worker and it will start polling the task list and processing whatever
tasks are on the list, regardless of when the failure occurred. You can make your overall workflow fault-
tolerant by using two or more workflow and activity workers, perhaps on separate systems. Then, if
one of the workers fails, the others will continue to handle scheduled tasks without any interruption in
workflow progress.
Replaying Workflows
Because activities can be long-running, it's undesirable to have the workflow simply block until it
completes. Instead, the AWS Flow Framework manages workflow execution by using a replay mechanism,
which relies on the workflow history maintained by Amazon SWF to execute the workflow in episodes.
Each episode replays the workflow logic in a way that executes each activity only once, and ensures that
activities and asynchronous methods don't execute until their Promise (p. 42) objects are ready.
The workflow starter initiates the first replay episode when it starts the workflow execution. The
framework calls the workflow's entry point method and:
1. Executes all workflow tasks that don't depend on activity completion, including calling all activity
client methods.
2. Gives Amazon SWF a list of activities tasks to be scheduled for execution. For the first episode, this list
consists of only those activities that don't depend on a Promise and can be executed immediately.
3. Notifies Amazon SWF that the episode is complete.
Amazon SWF stores the activity tasks in the workflow history and schedules them for execution by
placing them on the activity task list. The activity workers poll the task list and execute the tasks.
When an activity worker completes a task, it returns the result to Amazon SWF, which records it in the
workflow execution history and schedules a new workflow task for the workflow worker by placing it on
the workflow task list. The workflow worker polls the task list and when it receives the task, it runs the
next replay episode, as follows:
1. The framework runs the workflow's entry point method again and:
• Executes all workflow tasks that don't depend on activity completion, including calling all activity
client methods. However, the framework checks the execution history and doesn't schedule
duplicate activity tasks.
• Checks the history to see which activity tasks have completed and executes any asynchronous
workflow methods that depend on those activities.
2. When all workflow tasks that can be executed have completed, the framework reports back to
Amazon SWF:
• It gives Amazon SWF a list of any activities whose input Promise<T> objects have become ready
since the last episode and can be scheduled for execution.
• If the episode generated no additional activity tasks but there are still uncompleted activities, the
framework notifies Amazon SWF that the episode is complete. It then waits for another activity to
complete, initiating the next replay episode.
• If the episode generated no additional activity tasks and all activities have completed, the
framework notifies Amazon SWF that the workflow execution is complete.
For examples of replay behavior, see AWS Flow Framework for Java Replay Behavior (p. 116).
• Replay doesn't guarantee that an asynchronous method will execute only once. It defers execution on
an asynchronous method until its input Promise objects are ready, but it then executes that method
for all subsequent episodes.
• When an asynchronous method completes, it doesn't start a new episode.
An example of replaying an asynchronous workflow is provided in AWS Flow Framework for Java Replay
Behavior (p. 116).
• Do not use workflow methods to perform long-running tasks, because replay will repeat that task
multiple times. Even asynchronous workflow methods typically run more than once. Instead, use
activities for long running tasks; replay executes activities only once.
• Your workflow logic must be completely deterministic; every episode must take the same control
flow path. For example, the control flow path should not depend on the current time. For a detailed
description of replay and the determinism requirement, see Nondeterminism (p. 122).
The following excerpt from the HelloWorldWorkflow host application creates a new activity worker
and assigns it to the HelloWorldList activities task list.
By default, Amazon SWF schedules the worker's tasks on the HelloWorldList list. Then the worker
polls that list for tasks. You can assign any name to a task list. You can even use the same name for
both workflow and activity lists. Internally, Amazon SWF puts workflow and activity task list names in
different namespaces, so the two lists will be distinct.
If you don't specify a task list, the AWS Flow Framework specifies a default list when the worker
registers the type with Amazon SWF. For more information, see Workflow and Activity Type
Registration (p. 59).
Sometimes it's useful to have a specific worker or group of workers perform certain tasks. For example,
an image processing workflow might use one activity to download an image and another activity to
process the image. It's more efficient to perform both tasks on the same system, and avoid the overhead
of transferring large files over the network.
To support such scenarios, you can explicitly specify a task list when you call an activity client method by
using an overload that includes a schedulingOptions parameter. You specify the task list by passing
the method an appropriately configured ActivitySchedulingOptions object.
For example, suppose that the say activity of the HelloWorldWorkflow application is hosted by an
activity worker different from getName and getGreeting. The following example shows how to ensure
that say uses the same task list as getName and getGreeting, even if they were originally assigned to
different lists.
The asynchronous runSay method gets the getGreeting task list from its client object. Then it creates
and configures an ActivitySchedulingOptions object that ensures that say polls the same task list
as getGreeting.
Note
When you pass a schedulingOptions parameter to an activity client method, it overrides the
original task list only for that activity execution. If you call the activities client method again
without specifying a task list, Amazon SWF assigns the task to the original list, and the activity
worker will poll that list.
• A complete workflow execution history, which allows you to implement a stateless application.
• Task scheduling that is loosely coupled to task execution, which makes it easy to scale your application
to meet current demands.
Amazon SWF schedules tasks by posting them to dynamically allocated task lists, not by communicating
directly with workflow and activity workers. Instead, the workers use HTTP requests to poll their
respective lists for tasks. This approach loosely couples task scheduling to task execution and allows
workers to run on any suitable system, including Amazon EC2 instances, corporate data centers, client
computers, and so on. Since the HTTP requests originate with the workers, there is no need for externally
visible ports, which enables workers to even run behind a firewall.
The long-polling mechanism that workers use to poll for tasks ensures that workers don't get
overloaded. Even if there is a spike in scheduled tasks, workers pull tasks at their own pace. However,
because workers are stateless, you can dynamically scale an application to meet increased load by
starting additional worker instances. Even if they are running on different systems, each instance polls
the same task list and the first available worker instance executes each task, regardless of where the
worker is located or when it started. When the load declines, you can reduce the number of workers
accordingly.
Even if an activity method has no return value, you can still use the Promise for managing workflow
execution. If you pass a returned Promise to an activity client method or an asynchronous workflow
method, it defers execution until the object is ready.
If you pass one or more Promises to an activity client method, the framework queues the task but defers
scheduling it until all the objects are ready. It then extracts the data from each Promise and marshals it
across the internet to the activity worker, which passes it to the activity method as a standard type.
Note
If you need to transfer large amounts of data between workflow and activity workers, the
preferred approach is to store the data in a convenient location and just pass the retrieval
information. For example, you can store the data in an Amazon S3 bucket and pass the
associated URL.
The primary purpose of Promise<T> is to manage data flow from one activity to another. It ensures that
an activity doesn't execute until the input data is valid. In many cases, workflow workers don't need to
access Promise<T> objects directly; they simply pass the objects from one activity to another and let
the framework and the activity workers handle the details. To access a Promise<T> object's value in a
workflow worker, you must be certain that the object is ready before calling its get method.
• The preferred approach is to pass the Promise<T> object to an asynchronous workflow method and
process the values there. An asynchronous method defers execution until all of its input Promise<T>
objects are ready, which guarantees that you can safely access their values.
• Promise<T> exposes an isReady method that returns true if the object is ready. Using isReady to
poll a Promise<T> object isn't recommended, but isReady is useful in certain circumstances. For an
example, see AWS Flow Framework Recipes.
The AWS Flow Framework for Java also includes a Settable<T> type, which is derived from
Promise<T> and has similar behavior. The difference is that the framework usually sets the value of a
Promise<T> object and the workflow worker is responsible for setting the value of a Settable<T> For
an example, see AWS Flow Framework Recipes
There are some circumstance where a workflow worker needs to create a Promise<T> object and set its
value. For example, an asynchronous method that returns a Promise<T> object needs to create a return
value.
• To create an object that represents a typed value, call the static Promise.asPromise method, which
creates a Promise<T> object of the appropriate type, sets its value, and puts it in the ready state.
• To create a Promise<Void> object, call the static Promise.Void method.
Note
Promise<T> can represent any valid type. However, if the data must be marshaled across the
internet, the type must be compatible with the data converter. See the next section for details.
Amazon SWF allows workflows to implement a signal method, which allows applications such as the
workflow starter to pass data to the workflow at any time. A signal method can have any convenient
name and parameters. You designate it as a signal method by including it in your workflow interface
definition, and applying a @Signal annotation to the method declaration.
The following example shows an order processing workflow interface that declares a signal method,
changeOrder, which allows the workflow starter to change the original order after the workflow has
started.
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 300)
public interface WaitForSignalWorkflow {
@Execute(version = "1.0")
public void placeOrder(int amount);
@Signal
public void changeOrder(int amount);
}
The framework's annotation processor creates a workflow client method with the same name as the
signal method and the workflow starter calls the client method to pass data to the workflow. For an
example, see AWS Flow Framework Recipes
For timeouts related to decision tasks and activity tasks, Amazon SWF adds an event to the workflow
execution history. The attributes of the event provide information about what type of timeout occurred
and which decision task or activity task was affected. Amazon SWF also schedules a decision task.
When the decider receives the new decision task, it will see the timeout event in the history and take an
appropriate action by calling the RespondDecisionTaskCompleted action.
A task is considered open from the time that it is scheduled until it is closed. Therefore a task is reported
as open while a worker is processing it. A task is closed when a worker reports it as completed, canceled,
or failed. A task may also be closed by Amazon SWF as the result of a timeout.
There are two timeout types that are relevant to workflow and decision tasks:
history. The event attributes will include the IDs for the events that correspond to when this decision
task was scheduled (scheduledEventId) and when it was started (startedEventId). In addition
to adding the event, Amazon SWF also schedules a new decision task to alert the decider that this
decision task timed out. After this timeout occurs, an attempt to complete the timed-out decision task
using RespondDecisionTaskCompleted will fail.
There are four timeout types that are relevant to activity tasks:
• Activity Task Start to Close (timeoutType: START_TO_CLOSE) – This timeout specifies the
maximum time that an activity worker can take to process a task after the worker has received
the task. Attempts to close a timed out activity task using RespondActivityTaskCanceled,
RespondActivityTaskCompleted, and RespondActivityTaskFailed will fail.
• Activity Task Heartbeat (timeoutType: HEARTBEAT) – This timeout specifies the maximum time
that a task can run before providing its progress through the RecordActivityTaskHeartbeat
action.
• Activity Task Schedule to Start (timeoutType: SCHEDULE_TO_START) – This timeout specifies how
long Amazon SWF waits before timing out the activity task if no workers are available to perform the
task. Once timed out, the expired task will not be assigned to another worker.
• Activity Task Schedule to Close (timeoutType: SCHEDULE_TO_CLOSE) – This timeout specifies how
long the task can take from the time it is scheduled to the time it is complete. As a best practice, this
value should not be greater than the sum of the task schedule-to-start timeout and the task start-to-
close timeout.
Note
Each of the timeout types has a default value, which is generally set to NONE (infinite). The
maximum time for any activity execution is limited to one year, however.
You set default values for these during activity type registration, but you can override them with new
values when you schedule the activity task. When one of these timeouts occurs, Amazon SWF will add
an event of type ActivityTaskTimedOut to the workflow history. The timeoutType value attribute
of this event will specify which of these timeouts occurred. For each of the timeouts, the value of
timeoutType is shown in parentheses. The event attributes will also include the IDs for the events that
correspond to when the activity task was scheduled (scheduledEventId) and when it was started
(startedEventId). In addition to adding the event, Amazon SWF also schedules a new decision task to
alert the decider that the timeout occurred.
Best Practices
Use these best practices to make the most of the AWS Flow Framework for Java.
Topics
• Making Changes to Decider Code: Versioning and Feature Flags (p. 47)
Before you try these solutions, familiarize yourself with the Example Scenario (p. 47) section which
explains the causes and effects of backwards-incompatible decider changes.
The replay process re-executes the decider code from the beginning, while simultaneously going through
the history of events that have already occurred. Going through the event history allows the framework
to react to signals or task completion and unblock Promise objects in the code.
When the framework executes the decider code, it assigns an ID to each scheduled task (an
activity, Lambda function, timer, child workflow, or outgoing signal) by incrementing a counter.
The framework communicates this ID to Amazon SWF, and adds the ID to history events, such as
ActivityTaskCompleted.
For the replay process to succeed, it is important for the decider code to be deterministic, and to
schedule the same tasks in the same order for every decision in every workflow execution. If you
don't adhere to this requirement, the framework might, for example, fail to match the ID in an
ActivityTaskCompleted event to an existing Promise object.
Example Scenario
There is a class of code changes considered to be backwards-incompatible. These changes include
updates that modify the number, type, or order of the scheduled tasks. Consider the following example:
You write decider code to schedule two timer tasks. You start an execution and run a decision. As a result,
two timer tasks are scheduled, with IDs 1 and 2.
If you update the decider code to schedule only one timer before the next decision to be executed,
during the next decision task the framework will fail to replay the second TimerFired event, because ID
2 doesn't match any timer tasks that the code has produced.
Scenario Outline
The following outline shows the steps of this scenario. The final goal of the scenario is to migrate to
a system that schedules only one timer but doesn't cause failures in executions started before the
migration.
The following sections include examples of Java code that show how to implement this scenario. The
code examples in the Solutions (p. 52) section show various ways to fix backwards-incompatible
changes.
Note
You can use the latest version of the AWS SDK for Java to run this code.
Common Code
The following Java code doesn't change between the examples in this scenario.
SampleBase.java
package sample;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClientBuilder;
import com.amazonaws.services.simpleworkflow.flow.JsonDataConverter;
import com.amazonaws.services.simpleworkflow.model.DescribeWorkflowExecutionRequest;
import com.amazonaws.services.simpleworkflow.model.DomainAlreadyExistsException;
import com.amazonaws.services.simpleworkflow.model.RegisterDomainRequest;
import com.amazonaws.services.simpleworkflow.model.Run;
import com.amazonaws.services.simpleworkflow.model.StartWorkflowExecutionRequest;
import com.amazonaws.services.simpleworkflow.model.TaskList;
import com.amazonaws.services.simpleworkflow.model.WorkflowExecution;
import com.amazonaws.services.simpleworkflow.model.WorkflowExecutionDetail;
import com.amazonaws.services.simpleworkflow.model.WorkflowType;
{
try {
AmazonSimpleWorkflowClientBuilder.defaultClient().registerDomain(new
RegisterDomainRequest().withName(domain).withDescription("desc").withWorkflowExecutionRetentionPeriodI
} catch (DomainAlreadyExistsException e) {
}
}
Input.java
package sample;
public Input() {
}
InitialDecider.java
package sample.v1;
import com.amazonaws.services.simpleworkflow.flow.DecisionContext;
import com.amazonaws.services.simpleworkflow.flow.DecisionContextProviderImpl;
import com.amazonaws.services.simpleworkflow.flow.WorkflowClock;
import com.amazonaws.services.simpleworkflow.flow.annotations.Execute;
import com.amazonaws.services.simpleworkflow.flow.annotations.Workflow;
import com.amazonaws.services.simpleworkflow.flow.annotations.WorkflowRegistrationOptions;
import sample.Input;
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 60,
defaultTaskStartToCloseTimeoutSeconds = 5)
public interface Foo {
@Execute(version = "1")
public void sample(Input input);
@Override
public void sample(Input input) {
System.out.println("Decision (V1) WorkflowId: " +
decisionContext.getWorkflowContext().getWorkflowExecution().getWorkflowId());
clock.createTimer(5);
clock.createTimer(5);
}
}
}
ModifiedDecider.java
package sample.v1.modified;
import com.amazonaws.services.simpleworkflow.flow.DecisionContext;
import com.amazonaws.services.simpleworkflow.flow.DecisionContextProviderImpl;
import com.amazonaws.services.simpleworkflow.flow.WorkflowClock;
import com.amazonaws.services.simpleworkflow.flow.annotations.Execute;
import com.amazonaws.services.simpleworkflow.flow.annotations.Workflow;
import com.amazonaws.services.simpleworkflow.flow.annotations.WorkflowRegistrationOptions;
import sample.Input;
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 60,
defaultTaskStartToCloseTimeoutSeconds = 5)
public interface Foo {
@Execute(version = "1")
public void sample(Input input);
@Override
public void sample(Input input) {
System.out.println("Decision (V1 modified) WorkflowId: " +
decisionContext.getWorkflowContext().getWorkflowExecution().getWorkflowId());
clock.createTimer(5);
}
}
}
The following Java code allows you to simulate the problem of making backwards-incompatible changes
by running the modified decider.
RunModifiedDecider.java
package sample;
import com.amazonaws.services.simpleworkflow.flow.WorkflowWorker;
// At this point, three executions are still open, with more decisions to make
printExecutionResults();
}
When you run the program, the three executions that fail are those that started under the initial version
of the decider and continued after the migration.
Solutions
You can use the following solutions to avoid backwards-incompatible changes. For more information, see
Making Changes to Decider Code (p. 47) and Example Scenario (p. 47).
Using Versioning
In this solution, you copy the decider to a new class, modify the decider, and then register the decider
under a new workflow version.
VersionedDecider.java
package sample.v2;
import com.amazonaws.services.simpleworkflow.flow.DecisionContext;
import com.amazonaws.services.simpleworkflow.flow.DecisionContextProviderImpl;
import com.amazonaws.services.simpleworkflow.flow.WorkflowClock;
import com.amazonaws.services.simpleworkflow.flow.annotations.Execute;
import com.amazonaws.services.simpleworkflow.flow.annotations.Workflow;
import com.amazonaws.services.simpleworkflow.flow.annotations.WorkflowRegistrationOptions;
import sample.Input;
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 60,
defaultTaskStartToCloseTimeoutSeconds = 5)
public interface Foo {
@Execute(version = "2")
public void sample(Input input);
@Override
public void sample(Input input) {
System.out.println("Decision (V2) WorkflowId: " +
decisionContext.getWorkflowContext().getWorkflowExecution().getWorkflowId());
clock.createTimer(5);
}
In the updated Java code, the second decider worker runs both versions of the workflow, allowing in-
flight executions to continue to execute independently of the changes in version 2.
RunVersionedDecider.java
package sample;
import com.amazonaws.services.simpleworkflow.flow.WorkflowWorker;
// At this point, three executions are still open, with more decisions to make
// Start a worker with both the previous version of the decider (workflow version
1)
// and the modified code (workflow version 2)
WorkflowWorker after = new WorkflowWorker(service, domain, taskList);
after.addWorkflowImplementationType(sample.v1.Foo.Impl.class);
after.addWorkflowImplementationType(sample.v2.Foo.Impl.class);
after.start();
printExecutionResults();
}
When you take this approach, you add fields to (or modify existing fields of) your input objects every
time you introduce sensitive changes. For executions that start before the migration, the input object
won't have the field (or will have a different value). Thus, you don't have to increase the version number.
Note
If you add new fields, ensure that the JSON deserialization process is backwards-compatible.
Objects serialized before the introduction of the field should still successfully deserialize after
the migration. Because JSON sets a null value whenever a field is missing, always use boxed
types (Boolean instead of boolean) and handle the cases where the value is null.
FeatureFlagDecider.java
package sample.v1.featureflag;
import com.amazonaws.services.simpleworkflow.flow.DecisionContext;
import com.amazonaws.services.simpleworkflow.flow.DecisionContextProviderImpl;
import com.amazonaws.services.simpleworkflow.flow.WorkflowClock;
import com.amazonaws.services.simpleworkflow.flow.annotations.Execute;
import com.amazonaws.services.simpleworkflow.flow.annotations.Workflow;
import com.amazonaws.services.simpleworkflow.flow.annotations.WorkflowRegistrationOptions;
import sample.Input;
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 60,
defaultTaskStartToCloseTimeoutSeconds = 5)
public interface Foo {
@Execute(version = "1")
public void sample(Input input);
@Override
public void sample(Input input) {
System.out.println("Decision (V1 feature flag) WorkflowId: " +
decisionContext.getWorkflowContext().getWorkflowExecution().getWorkflowId());
clock.createTimer(5);
if (!input.getSkipSecondTimer()) {
clock.createTimer(5);
}
}
}
}
In the updated Java code, the code for both versions of the workflow is still registered for version 1.
However, after the migration, new executions start with the skipSecondTimer field of the input data
set to true.
RunFeatureFlagDecider.java
package sample;
import com.amazonaws.services.simpleworkflow.flow.WorkflowWorker;
// At this point, three executions are still open, with more decisions to make
// Start a few more executions and enable the new feature through the input data
startFiveExecutions("Foo.sample", "1", new Input().setSkipSecondTimer(true));
printExecutionResults();
}
Topics
• Implementing Workflow Applications with the AWS Flow Framework (p. 56)
• Workflow and Activity Contracts (p. 57)
• Workflow and Activity Type Registration (p. 59)
• Activity and Workflow Clients (p. 61)
• Workflow Implementation (p. 71)
• Activity Implementation (p. 74)
• Implementing AWS Lambda Tasks (p. 76)
• Running Programs Written with the AWS Flow Framework for Java (p. 80)
• Execution Context (p. 83)
• Child Workflow Executions (p. 85)
• Continuous Workflows (p. 87)
• Setting Task Priority (p. 88)
• DataConverters (p. 89)
• Passing Data to Asynchronous Methods (p. 90)
• Testability and Dependency Injection (p. 92)
• Error Handling (p. 101)
• Retry Failed Activities (p. 107)
• Daemon Tasks (p. 115)
• AWS Flow Framework for Java Replay Behavior (p. 116)
1. Define activity and workflow contracts. Analyze your application's requirements, then determine
the required activities and the workflow topology. The activities handle the required processing
tasks, while the workflow topology defines the workflow's basic structure and business logic.
For example, a media processing application might need to download a file, process it, and then
upload the processed file to an Amazon Simple Storage Service (S3) bucket. This can broken down
into four activity tasks:
This workflow would have an entry point method and would implement a simple linear topology
that runs the activities in sequence, much like the HelloWorldWorkflow Application (p. 14).
2. Implement activity and workflow interfaces. The workflow and activity contracts are defined by
Java interfaces, making their calling conventions predictable by SWF, and providing you flexibility
when implementing your workflow logic and activity tasks. The various parts of your program can
act as consumers of each others' data, yet don't need to be aware of much of the implementation
details of any of the other parts.
For example, you can define a FileProcessingWorkflow interface and provide different workflow
implementations for video encoding, compression, thumbnails, and so on. Each of those workflows
can have different control flows and can call different activity methods; your workflow starter
doesn't need to know. By using interfaces, it is also simple to test your workflows by using mock
implementations that can be replaced later with working code.
3. Generate activity and workflow clients. The AWS Flow Framework eliminates the need for you to
implement the details of managing asynchronous execution, sending HTTP requests, marshaling
data, and so forth. Instead, the workflow starter executes a workflow instance by calling a method
on the workflow client, and the workflow implementation executes activities by calling methods on
the activities client. The framework handles the details of these interactions in the background.
If you are using Eclipse and you have configured your project, like in Setting up the AWS Flow
Framework for Java (p. 3), the AWS Flow Framework annotation processor uses the interface
definitions to automatically generate workflow and activities clients that expose the same set of
methods as the corresponding interface.
4. Implement activity and workflow host applications. Your workflow and activity implementations
must be embedded in host applications that poll Amazon SWF for tasks, marshal any data, and call
the appropriate implementation methods. AWS Flow Framework for Java includes WorkflowWorker
and ActivityWorker classes that make implementing host applications straightforward and easy to
do.
5. Test your workflow. AWS Flow Framework for Java provides JUnit integration that you can use to
test your workflows inline and locally.
6. Deploy the workers. You can deploy your workers as appropriate—for example, you can deploy
them to Amazon EC2 instances or to computers in your data center. Once deployed and started, the
workers start polling Amazon SWF for tasks and handle them as required.
7. Start executions. An application starts a workflow instance by using the workflow client to call the
workflow's entry point. You can also start workflows by using the Amazon SWF console. Regardless
of how you start a workflow instance, you can use Amazon SWF console to monitor running
workflow instance and examine the workflow history for running, completed, and failed instances.
The AWS SDK for Java includes a set of AWS Flow Framework for Java samples that you can browse and
run by following the instructions in the readme.html file in the root directory. There are also a set of
recipes —simple applications — that show how to handle a variety of specific programming issue, which
are available from AWS Flow Framework Recipes.
@Workflow
@WorkflowRegistrationOptions(
defaultExecutionStartToCloseTimeoutSeconds = 60,
defaultTaskStartToCloseTimeoutSeconds = 10)
public interface MyWorkflow
{
@Execute(version = "1.0")
void startMyWF(int a, String b);
@Signal
void signal1(int a, int b, String c);
@GetState
MyWorkflowState getState();
}
In the example above, the workflow interface MyWorkflow contains a method, startMyWF, for starting
a new execution. This method is annotated with the @Execute annotation and must have a return type
of void or Promise<>. In a given workflow interface, at most one method can be annotated with this
annotation. This method is the entry point of the workflow logic, and the framework calls this method to
execute the workflow logic when a decision task is received.
The workflow interface also defines the signals that may be sent to the workflow. The signal method
gets invoked when a signal with a matching name is received by the workflow execution. For example,
the MyWorkflow interface declares a signal method, signal1, annotated with the @Signal annotation.
The @Signal annotation is required on signal methods. The return type of a signal method must be
void. A workflow interface may have zero or more signal methods defined in it. You may declare a
workflow interface without an @Execute method and some @Signal methods to generate clients that
can't start their execution but can send signals to running executions.
Methods annotated with @Execute and @Signal annotations may have any number of parameters of
any type other than Promise<T> or its derivatives. This allows you to pass strongly typed inputs to a
workflow execution at start and while it is running. The return type of the @Execute method must be
void or Promise<>.
Additionally, you may also declare a method in the workflow interface to report the latest state of a
workflow execution, for instance, the getState method in the previous example. This state isn't the
entire application state of the workflow. The intended use of this feature is to allow you to store up
to 32 KB of data to indicate the latest status of the execution. For example, in an order processing
workflow, you may store a string that indicates that the order has been received, processed, or canceled.
This method is called by the framework every time a decision task is completed to get the latest state.
The state is stored in Amazon Simple Workflow Service (Amazon SWF) and can be retrieved using the
generated external client. This allows you to check the latest state of a workflow execution. Methods
annotated with @GetState must not take any arguments and must not have a void return type.
You can return any type, which fits your needs, from this method. In the above example, an object
of MyWorkflowState (see definition below) is returned by the method that is used to store a string
state and a numeric percent complete. The method is expected to perform read-only access of the
workflow implementation object and is invoked synchronously, which disallows use of any asynchronous
operations like calling methods annotated with @Asynchronous. At most one method in a workflow
interface can be annotated with @GetState annotation.
Similarly, a set of activities are defined using an interface annotated with @Activities annotation.
Each method in the interface corresponds to an activity—for example:
@Activities(version = "1.0")
@ActivityRegistrationOptions(
defaultTaskScheduleToStartTimeoutSeconds = 300,
defaultTaskStartToCloseTimeoutSeconds = 3600)
public interface MyActivities {
// Overrides values from annotation found on the interface
@ActivityRegistrationOptions(description = "This is a sample activity",
defaultTaskScheduleToStartTimeoutSeconds = 100,
defaultTaskStartToCloseTimeoutSeconds = 60)
int activity1();
The interface allows you to group together a set of related activities. You can define any number of
activities within an activities interface, and you can define as many activities interfaces as you want.
Similar to @Execute and @Signal methods, activity methods can take any number of arguments of any
type other than Promise<T> or its derivatives. The return type of an activity must not be Promise<T>
or its derivatives.
Note that Amazon SWF doesn't allow you to re-register or modify the type once it has been registered.
The framework will try to register all types, but if the type is already registered it will not be re-
registered and no error will be reported.
If you need to modify registered settings, you must register a new version of the type. You can also
override registered settings when starting a new execution or when calling an activity that uses the
generated clients.
The registration requires a type name and some other registration options. The default implementation
determines these as follows:
The workflow version is specified using the version parameter of the @Execute annotation. There is no
default for version and it must be explicitly specified; versionis a free form string, and you are free to
use your own versioning scheme.
Signal Name
The name of the signal can be specified using the name parameter of the @Signal annotation. If not
specified, it is defaulted to the name of the signal method.
The activity version is specified using the version parameter of the @Activities annotation. This
version is used as the default for all activities defined in the interface and can be overridden on a per-
activity basis using the @Activity annotation.
If you want to have complete control over type registration, see Worker Extensibility (p. 82).
Workflow Clients
The generated artifacts for the workflow contain three client-side interfaces and the classes that
implement them. The generated clients include:
For example, the generated client interfaces for the example MyWorkflow interface are:
Promise<Void> startMyWF(
int a, String b,
Promise<?>... waitFor);
Promise<Void> startMyWF(
int a, String b,
StartWorkflowOptions optionsOverride,
Promise<?>... waitFor);
Promise<Void> startMyWF(
Promise<Integer> a,
Promise<String> b);
Promise<Void> startMyWF(
Promise<Integer> a,
Promise<String> b,
Promise<?>... waitFor);
Promise<Void> startMyWF(
Promise<Integer> a,
Promise<String> b,
StartWorkflowOptions optionsOverride,
Promise<?>... waitFor);
void signal1(
int a, int b, String c);
}
void startMyWF(
int a, String b,
StartWorkflowOptions optionsOverride);
void signal1(
int a, int b, String c);
MyWorkflowState getState();
}
void startMyWF(
int a, String b,
Promise<?>... waitFor);
void startMyWF(
int a, String b,
StartWorkflowOptions optionsOverride,
Promise<?>... waitFor);
void startMyWF(
Promise<Integer> a,
Promise<String> b);
void startMyWF(
Promise<Integer> a,
Promise<String> b,
Promise<?>... waitFor);
void startMyWF(
Promise<Integer> a,
Promise<String> b,
StartWorkflowOptions optionsOverride,
Promise<?>... waitFor);
The interfaces have overloaded methods corresponding to each method in the @Workflow interface that
you declared.
The external client mirrors the methods on the @Workflow interface with one additional overload of the
@Execute method that takes StartWorkflowOptions. You can use this overload to pass additional
options when starting a new workflow execution. These options allow you to override the default task
list, timeout settings, and associate tags with the workflow execution.
On the other hand, the asynchronous client has methods that allow asynchronous invocation of the
@Execute method. The following method overloads are generated in the client interface for the
@Execute method in the workflow interface:
1. An overload that takes the original arguments as is. The return type of this overload will be
Promise<Void> if the original method returned void; otherwise, it will be the Promise<> as
declared on the original method. For example:
Original method:
Generated method:
This overload should be used when all the arguments of the workflow are available and don't need to
be waited for.
2. An overload that takes the original arguments as is and additional variable arguments of type
Promise<?>. The return type of this overload will be Promise<Void> if the original method
returned void; otherwise, it will be the Promise<> as declared on the original method. For example:
Original method:
Generated method:
This overload should be used when all the arguments of the workflow are available and don't need to
be waited for, but you want to wait for some other promises to become ready. The variable argument
can be used to pass such Promise<?> objects that were not declared as arguments, but you want to
wait for before executing the call.
3. An overload that takes the original arguments as is, an additional argument of type
StartWorkflowOptions and additional variable arguments of type Promise<?>. The return type of
this overload will be Promise<Void> if the original method returned void; otherwise, it will be the
Promise<> as declared on the original method. For example:
Original method:
Generated method:
API Version 2012-01-25
63
AWS Flow Framework for Java Developer Guide
Workflow Clients
Promise<void> startMyWF(
int a,
String b,
StartWorkflowOptions optionOverrides,
Promise<?>...waitFor);
This overload should be used when all the arguments of the workflow are available and don't need
to be waited for, when you want to override default settings used to start the workflow execution,
or when you want to wait for some other promises to become ready. The variable argument can be
used to pass such Promise<?> objects that were not declared as arguments, but you want to wait for
before executing the call.
4. An overload with each argument in the original method replaced with a Promise<> wrapper. The
return type of this overload will be Promise<Void> if the original method returned void; otherwise,
it will be the Promise<> as declared on the original method. For example:
Original method:
Generated method:
Promise<Void> startMyWF(
Promise<Integer> a,
Promise<String> b);
This overload should be used when the arguments to be passed to the workflow execution are to be
evaluated asynchronously. A call to this method overload will not execute until all arguments passed
to it become ready.
If some of the arguments are already ready, then convert them to a Promise that is already in ready
state through the Promise.asPromise(value) method. For example:
Promise<Integer> a = getA();
String b = getB();
startMyWF(a, Promise.asPromise(b));
5. An overload with each argument in the original method is replaced with a Promise<> wrapper. The
overload also has additional variable arguments of type Promise<?>. The return type of this overload
will be Promise<Void> if the original method returned void; otherwise, it will be the Promise<> as
declared on the original method. For example:
Original method:
Generated method:
Promise<Void> startMyWF(
Promise<Integer> a,
Promise<String> b,
Promise<?>...waitFor);
This overload should be used when the arguments to be passed to the workflow execution are to be
evaluated asynchronously and you want to wait for some other promises to become ready as well. A
call to this method overload will not execute until all arguments passed to it become ready.
6. An overload with each argument in the original method replaced with a Promise<?> wrapper. The
overload also has an additional argument of type StartWorkflowOptions and variable arguments
of type Promise<?>. The return type of this overload will be Promise<Void> if the original method
returned void; otherwise, it will be the Promise<> as declared on the original method. For example:
Original method:
Generated method:
Promise<Void> startMyWF(
Promise<Integer> a,
Promise<String> b,
StartWorkflowOptions optionOverrides,
Promise<?>...waitFor);
Use this overload when the arguments to be passed to the workflow execution will be evaluated
asynchronously and you want to override default settings used to start the workflow execution. A call
to this method overload will not execute until all arguments passed to it become ready.
A method is also generated corresponding to each signal in the workflow interface—for example:
Original method:
Generated method:
The asynchronous client doesn't contain a method corresponding to the method annotated with
@GetState in the original interface. Since retrieval of state requires a web service call, it is not suitable
for use within a workflow. Hence, it is provided only through the external client.
The self client is intended to be used from within a workflow to start a new execution on completion of
the current execution. The methods on this client are similar to the ones on the asynchronous client, but
return void. This client doesn't have methods corresponding to methods annotated with @Signal and
@GetState. For more details, see the Continuous Workflows (p. 87).
The generated clients derive from base interfaces: WorkflowClient and WorkflowClientExternal,
respectively, which provide methods that you can use to cancel or terminate the workflow execution. For
more details about these interfaces, see the AWS SDK for Java documentation.
The generated clients allow you to interact with workflow executions in a strongly typed fashion. Once
created, an instance of a generated client is tied to a specific workflow execution and can be used only
for that execution. In addition, the framework also provides dynamic clients that are not specific to a
workflow type or execution. The generated clients rely on this client under the covers. You may also
directly use these clients. See the section on Dynamic Clients (p. 70).
The framework also generates factories for creating the strongly typed clients. The generated client
factories for the example MyWorkflow interface are:
You should use these factories to create instances of the client. The factory allows you to configure the
generic client (the generic client should be used for providing custom client implementation) and the
DataConverter used by the client to marshal data, as well as the options used to start the workflow
execution. For more details, see the DataConverters (p. 89) and Child Workflow Executions (p. 85)
sections. The StartWorkflowOptions contains settings that you can use to override the defaults—for
example, timeouts—specified at registration time. For more details about the StartWorkflowOptions
class, see the AWS SDK for Java documentation.
The external client can be used to start workflow executions from outside of the scope of a workflow
while the asynchronous client can be used to start a workflow execution from code within a workflow. In
order to start an execution, you simply use the generated client to call the method that corresponds to
the method annotated with @Execute in the workflow interface.
The framework also generates implementation classes for the client interfaces. These clients create and
send requests to Amazon SWF to perform the appropriate action. The client version of the @Execute
method either starts a new workflow execution or creates a child workflow execution using Amazon SWF
APIs. Similarly, the client version of the @Signal method uses Amazon SWF APIs to send a signal.
Note
The external workflow client must be configured with the Amazon SWF client and domain. You
can either use the client factory constructor that takes these as parameters or pass in a generic
client implementation that is already configured with the Amazon SWF client and domain.
The framework walks the type hierarchy of the workflow interface and also generates client
interfaces for parent workflow interfaces and derives from them.
Activity Clients
Similar to the workflow client, a client is generated for each interface annotated with @Activities.
The generated artifacts include a client side interface and a client class. The generated interface for the
example @Activities interface above (MyActivities) is as follows:
The interface contains a set of overloaded methods corresponding to each activity method in the
@Activities interface. These overloads are provided for convenience and allow calling activities
asynchronously. For each activity method in the @Activities interface, the following method
overloads are generated in the client interface:
1. An overload that takes the original arguments as is. The return type of this overload is Promise<T>,
where T is the return type of the original method. For example:
Original method:
Generated method:
This overload should be used when all the arguments of the workflow are available and don't need to
be waited for.
2. An overload that takes the original arguments as is, an argument of type
ActivitySchedulingOptions and additional variable arguments of type Promise<?>. The return
type of this overload is Promise<T>, where T is the return type of the original method. For example:
Original method:
Generated method:
Promise<Void> activity2(
int foo,
ActivitySchedulingOptions optionsOverride,
Promise<?>... waitFor);
This overload should be used when all the arguments of the workflow are available and don't need to
be waited for, when you want to override the default settings, or when you want to wait for additional
Promises to become ready. The variable arguments can be used to pass such additional Promise<?>
objects that were not declared as arguments, but you want to wait for before executing the call.
3. An overload with each argument in the original method replaced with a Promise<> wrapper. The
return type of this overload is Promise<T>, where T is the return type of the original method. For
example:
Original method:
Generated method:
This overload should be used when the arguments to be passed to the activity will be evaluated
asynchronously. A call to this method overload will not execute until all arguments passed to it
become ready.
4. An overload with each argument in the original method replaced with a Promise<> wrapper. The
overload also has an additional argument of type ActivitySchedulingOptions and variable
arguments of type Promise<?>. The return type of this overload is Promise<T>, where T is the
return type of the original method. For example:
Original method:
Generated method:
Promise<Void> activity2(
Promise<Integer> foo,
ActivitySchedulingOptions optionsOverride,
Promise<?>...waitFor);
This overload should be used when the arguments to be passed to the activity will be evaluated
asynchronously, when you want to override the default settings registered with the type, or when
you want to wait for additional Promises to become ready. A call to this method overload will not
execute until all arguments passed to it become ready. The generated client class implements this
interface. The implementation of each interface method creates and sends a request to Amazon SWF
to schedule an activity task of the appropriate type using Amazon SWF APIs.
5. An overload that takes the original arguments as is and additional variable arguments of type
Promise<?>. The return type of this overload is Promise<T>, where T is the return type of the
original method. For example:
Original method:
Generated method:
This overload should be used when all the activity's arguments are available and don't need to be
waited for, but you want to wait for other Promise objects to become ready.
6. An overload with each argument in the original method replaced with a Promise wrapper and
additional variable arguments of type Promise<?>. The return type of this overload is Promise<T>,
where T is the return type of the original method. For example:
Original method:
Generated method:
Promise<Void> activity2(
Promise<Integer> foo,
Promise<?>... waitFor);
This overload should be used when all the arguments of the activity will be waited for asynchronously
and you also want to wait for some other Promises to become ready. A call to this method overload
will execute asynchronously when all Promise objects passed become ready.
The generated activity client also has a protected method corresponding to each activity method, named
{activity method name}Impl(), that all activity overloads call into. You can override this method
to create mock client implementations. This method takes as arguments: all the arguments to the
original method in Promise<> wrappers, ActivitySchedulingOptions, and variable arguments of
type Promise<?>. For example:
Original method:
Generated method:
Promise<Void> activity2Impl(
Promise<Integer> foo,
ActivitySchedulingOptions optionsOverride,
Promise<?>...waitFor);
Scheduling Options
The generated activity client allows you to pass in ActivitySchedulingOptions as an argument.
The ActivitySchedulingOptions structure contains settings that determine the configuration of
the activity task that the framework schedules in Amazon SWF. These settings override the defaults
that are specified as registration options. To specify scheduling options dynamically, create an
OrderProcessingActivitiesClient activitiesClient
= new OrderProcessingActivitiesClientImpl();
activitiesClient.shipOrder(order,
schedulingOptions,
paymentProcessed);
}
}
Dynamic Clients
In addition to the generated clients, the framework also provides general purpose clients
—DynamicWorkflowClient and DynamicActivityClient—that you can use to dynamically start
workflow executions, send signals, schedule activities, etc. For instance, you may want to schedule
an activity whose type isn't known at design time. You can use the DynamicActivityClient for
scheduling such an activity task. Similarly, you can dynamically schedule a child workflow execution by
using the DynamicWorkflowClient. In the following example, the workflow looks up the activity from
a database and uses the dynamic activity client to schedule it:
//Workflow entrypoint
@Override
public void start() {
MyActivitiesClient client = new MyActivitiesClientImpl();
Promise<ActivityType> activityType
= client.lookUpActivityFromDB();
Promise<String> input = client.getInput(activityType);
scheduleDynamicActivity(activityType,
input);
}
@Asynchronous
void scheduleDynamicActivity(Promise<ActivityType> type,
Promise<String> input){
Promise<?>[] args = new Promise<?>[1];
args[0] = input;
DynamicActivitiesClient activityClient
= new DynamicActivitiesClientImpl();
activityClient.scheduleActivity(type.get(),
args,
null,
Void.class);
}
For more details, see the AWS SDK for Java documentation.
In the following example, the workflow looks up the execution to send a signal to from a database and
sends the signal dynamically using the dynamic workflow client.
//Workflow entrypoint
public void start()
{
MyActivitiesClient client = new MyActivitiesClientImpl();
Promise<WorkflowExecution> execution = client.lookUpExecutionInDB();
Promise<String> signalName = client.getSignalToSend();
Promise<String> input = client.getInput(signalName);
sendDynamicSignal(execution, signalName, input);
}
@Asynchronous
void sendDynamicSignal(
Promise<WorkflowExecution> execution,
Promise<String> signalName,
Promise<String> input)
{
DynamicWorkflowClient workflowClient
= new DynamicWorkflowClientImpl(execution.get());
Object[] args = new Promise<?>[1];
args[0] = input.get();
workflowClient.signalWorkflowExecution(signalName.get(), args);
}
Workflow Implementation
In order to implement a workflow, you write a class that implements the desired @Workflow interface.
For instance, the example workflow interface (MyWorkflow) can be implemented like so:
The @Execute method in this class is the entry point of the workflow logic. Since the framework uses
replay to reconstruct the object state when a decision task is to be processed, a new object is created for
each decision task.
The use of Promise<T> as a parameter is disallowed in the @Execute method within a @Workflow
interface. This is done because making an asynchronous call is purely a decision of the caller. The
workflow implementation itself doesn't depend on whether the invocation was synchronous or
asynchronous. Therefore, the generated client interface has overloads that take Promise<T> parameters
so that these methods can be called asynchronously.
The return type of an @Execute method can only be void or Promise<T>. Note that a return type of
the corresponding external client is void and not Promise<>. Since the external client isn't intended
to be used from the asynchronous code, the external client doesn't return Promise objects. For getting
results of workflow executions stated externally, you can design the workflow to update state in
an external data store through an activity. Amazon SWF's visibility APIs can also be used to retrieve
the result of a workflow for diagnostic purposes. It isn't recommended that you use the visibility
APIs to retrieve results of workflow executions as a general practice since these API calls may get
throttled by Amazon SWF. The visibility APIs require you to identify the workflow execution using a
WorkflowExecution structure. You can get this structure from the generated workflow client by calling
the getWorkflowExecution method. This method will return the WorkflowExecution structure
corresponding to the workflow execution that the client is bound to. See the Amazon Simple Workflow
Service API Reference for more details about the visibility APIs.
When calling activities from your workflow implementation, you should use the generated activities
client. Similarly, to send signals, use the generated workflow clients.
Decision Context
The framework provides an ambient context anytime workflow code is executed by the framework. This
context provides context-specific functionality that you may access in your workflow implementation,
such as creating a timer. See the section on Execution Context (p. 83) for more information.
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 60,
defaultTaskStartToCloseTimeoutSeconds = 10)
public interface PeriodicWorkflow {
@Execute(version = "1.0")
void periodicWorkflow();
@GetState
String getState();
}
@Activities(version = "1.0")
@ActivityRegistrationOptions(defaultTaskScheduleToStartTimeoutSeconds = 300,
defaultTaskStartToCloseTimeoutSeconds = 3600)
public interface PeriodicActivity {
void activity1();
@Override
public void periodicWorkflow() {
state = "Just Started";
callPeriodicActivity(0);
}
@Asynchronous
private void callPeriodicActivity(int count,
Promise<?>... waitFor)
{
if(count == 100) {
state = "Finished Processing";
return;
}
// call activity
activityClient.activity1();
@Override
public String getState() {
return state;
}
}
The generated external client can be used to retrieve the latest state of the workflow execution at any
time.
PeriodicWorkflowClientExternal client
= new PeriodicWorkflowClientExternalFactoryImpl().getClient();
System.out.println(client.getState());
In the above example, the execution state is reported at various stages. When the workflow
instance starts, periodicWorkflow reports the initial state as 'Just Started'. Each call to
callPeriodicActivity then updates the workflow state. Once activity1 has been called 100
times, the method returns and the workflow instance completes.
Workflow Locals
Sometimes, you may have a need for the use of static variables in your workflow implementation. For
example, you may want to store a counter that is to be accessed from various places (possibly different
classes) in the implementation of the workflow. However, you can't rely on static variables in your
workflows because static variables are shared across threads, which is problematic because a worker
may process different decision tasks on different threads at the same time. Alternatively, you may store
such state in a field on the workflow implementation, but then you will need to pass the implementation
object around. To address this need, the framework provides a WorkflowExecutionLocal<?> class.
Any state that needs to have static variable like semantics should be kept as an instance local using
WorkflowExecutionLocal<?>. You can declare and use a static variable of this type. For example, in
the following snippet, a WorkflowExecutionLocal<String> is used to store a user name.
@Override
public void start(String username){
this.username.set(username);
Processor p = new Processor();
p.updateLastLogin();
p.greetUser();
}
Activity Implementation
Activities are implemented by providing an implementation of the @Activities interface. The AWS
Flow Framework for Java uses the activity implementation instances configured on the worker to
process activity tasks at run time. The worker automatically looks up the activity implementation of the
appropriate type.
You can use properties and fields to pass resources to activity instances, such as database connections.
Since the activity implementation object may be accessed from multiple threads, shared resources must
be thread safe.
Note that the activity implementation doesn't take parameters of type Promise<> or return objects of
that type. This is because the implementation of the activity should not depend on how it was invoked
(synchronously or asynchronously).
@Override
@ManualActivityCompletion
public int activity1(){
//implementation
}
@Override
public void activity2(int foo){
//implementation
}
}
A thread local context is available to the activity implementation that can be used to retrieve the
task object, data converter object being used, etc. The current context can be accessed through
ActivityExecutionContextProvider.getActivityExecutionContext(). For more details,
see the AWS SDK for Java documentation for ActivityExecutionContext and the section Execution
Context (p. 83).
By default, the framework considers the activity completed when your activity method returns.
This means that the activity worker reports activity task completion to Amazon SWF and provides
it with the results (if any). However, there are use cases where you don't want the activity task to
be marked completed when the activity method returns. This is especially useful when you are
modeling human tasks. For example, the activity method may send an email to a person who must
complete some work before the activity task is completed. In such cases, you can annotate the
activity method with @ManualActivityCompletion annotation to tell the activity worker that
it should not complete the activity automatically. In order to complete the activity manually, you
can either use the ManualActivityCompletionClient provided in the framework or use the
RespondActivityTaskCompleted method on the Amazon SWF Java client provided in the Amazon
SWF SDK. For more details, see the AWS SDK for Java documentation.
In order to complete the activity task, you need to provide a task token. The task
token is used by Amazon SWF to uniquely identify tasks. You can access this token
from the ActivityExecutionContext in your activity implementation. You
must pass this token to the party that is responsible for completing the task.
This token can be retrieved from the ActivityExecutionContext by calling
ActivityExecutionContextProvider.getActivityExecutionContext().getTaskToken().
The getName activity of the Hello World example can be implemented to send an email asking someone
to provide a greeting message:
@ManualActivityCompletion
@Override
public String getName() throws InterruptedException {
ActivityExecutionContext executionContext
= contextProvider.getActivityExecutionContext();
String taskToken = executionContext.getTaskToken();
sendEmail("[email protected]",
"Please provide a name for the greeting message and close task with token: " +
taskToken);
return "This will not be returned to the caller";
}
The following code snippet can be used to provide the greeting and close the task by using the
ManualActivityCompletionClient. Alternatively, you can also fail the task:
AmazonSimpleWorkflow swfClient
= new AmazonSimpleWorkflowClient(...); // use AWS access keys
ManualActivityCompletionClientFactory manualCompletionClientFactory
= new ManualActivityCompletionClientFactoryImpl(swfClient);
ManualActivityCompletionClient manualCompletionClient
= manualCompletionClientFactory.getClient(taskToken);
String result = "Hello World!";
manualCompletionClient.complete(result);
}
Amazon Simple Workflow Service provides a Lambda task so that you can run Lambda functions in place
of, or alongside traditional Amazon SWF activities.
Important
Your AWS account will be charged for Lambda executions (requests) executed by Amazon SWF
on your behalf. For details about Lambda pricing, see http://aws.amazon.com/lambda/pricing/.
• Lambda tasks don’t need to be registered or versioned like Amazon SWF activity types.
• You can use any existing Lambda functions that you've already defined in your workflows.
• Lambda functions are called directly by Amazon SWF; there is no need for you to implement a worker
program to execute them as you must do with traditional activities.
• Lambda provides you with metrics and logs for tracking and analyzing your function executions.
There are also a number of limitations regarding Lambda tasks that you should be aware of:
• Lambda tasks can only be run in AWS regions that provide support for Lambda. See Lambda Regions
and Endpoints in the Amazon Web Services General Reference for details about the currently-supported
regions for Lambda.
• Lambda tasks are currently supported only by the base SWF HTTP API and in the AWS Flow Framework
for Java. There is currently no support for Lambda tasks in the AWS Flow Framework for Ruby.
• A Lambda function to execute. You can use any Lambda function that you've defined. For more
information about how to create Lambda functions, see the AWS Lambda Developer Guide.
• An IAM role that provides access to execute Lambda functions from your Amazon SWF workflows.
• Code to schedule the Lambda task from within your workflow.
• choose a pre-defined role, AWSLambdaRole, to give your workflows permission to invoke any Lambda
function associated with your account.
• define your own policy and associated role to give workflows permission to invoke particular Lambda
functions, specified by their Amazon Resource Names (ARNs).
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
"arn:aws:lambda:us-east-1:111111000000:function:hello_lambda_function"
]
}
]
}
Note
For a complete description of how to specify resources in an IAM role, see Overview of IAM
Policies in Using IAM.
5. Choose Create Policy to finish creating your policy.
You can then select this policy when creating a new IAM role, and use that role to give invoke access to
your Amazon SWF workflows. This procedure is very similar to creating a role with the AWSLambdaRole
policy. instead, choose your own policy when creating the role.
Note
This process is fully demonstrated by the HelloLambda sample (p. 79) in the AWS SDK for
Java.
// Schedule the Lambda function for execution, using your IAM role for access.
String lambda_function_name = "The name of your Lambda function.";
String lambda_function_input = "Input data for your Lambda task.";
lambdaClient.scheduleLambdaFunction(lambda_function_name, lambda_function_input);
3. In your workflow execution starter, add the IAM lambda role to your default workflow options by
using StartWorkflowOptions.withLambdaRole(), and then pass the options when starting the
workflow.
// Workflow client classes are generated for you when you use the @Workflow
// annotation on your workflow interface declaration.
MyWorkflowClientExternalFactory clientFactory =
new MyWorkflowClientExternalFactoryImpl(sdk_swf_client, swf_domain);
// Give the ARN of an IAM role that allows SWF to invoke Lambda functions on
// your behalf.
String lambda_iam_role = "arn:aws:iam::111111000000:role/swf_lambda_role";
StartWorkflowOptions workflow_options =
new StartWorkflowOptions().withLambdaRole(lambda_iam_role);
A full description of how to build and run the HelloLambda sample is provided in the README file
provided with the AWS Flow Framework for Java samples.
The framework provides worker classes to initialize the AWS Flow Framework for Java runtime and
communicate with Amazon SWF. In order to implement a workflow or an activity worker, you must create
and start an instance of a worker class. These worker classes are responsible for managing ongoing
asynchronous operations, invoking asynchronous methods that become unblocked, and communicating
with Amazon SWF. They can be configured with workflow and activity implementations, the number of
threads, the task list to poll, and so on.
The framework comes with two worker classes, one for activities and one for workflows. In order to run
the workflow logic, you use the WorkflowWorker class. Similarly for activities the ActivityWorker
class is used. These classes automatically poll Amazon SWF for activity tasks and invoke the appropriate
methods in your implementation.
The following example shows how to instantiate a WorkflowWorker and start polling for tasks:
// Start worker
worker.start();
The basic steps to create an instance of the ActivityWorker and starting polling for tasks are as
follows:
AmazonSimpleWorkflow swfClient
= new AmazonSimpleWorkflowClient(awsCredentials);
ActivityWorker worker = new ActivityWorker(swfClient,
"domain1",
"tasklist1");
worker.addActivitiesImplementation(new MyActivitiesImpl());
// Start worker
worker.start();
When you want to shut down an activity or decider, your application should shut down the instances of
the worker classes being used as well as the Amazon SWF Java client instance. This will ensure that all
resources used by the worker classes are properly released.
worker.shutdown();
worker.awaitTermination(1, TimeUnit.MINUTES);
In order to start an execution, simply create an instance of the generated external client and call the
@Execute method.
WorkflowWorker
As the name suggests, this worker class is intended for use by the workflow implementation. It is
configured with a task list and the workflow implementation type. The worker class runs a loop to poll
for decision tasks in the specified task list. When a decision task is received, it creates an instance of the
workflow implementation and calls the @Execute method to process the task.
ActivityWorker
For implementing activity workers, you can use the ActivityWorker class to conveniently poll a task
list for activity tasks. You configure the activity worker with activity implementation objects. This worker
class runs a loop to poll for activity tasks in the specified task list. When an activity task is received, it
looks up the appropriate implementation that you provided and calls the activity method to process the
task. Unlike the WorkflowWorker, which calls the factory to create a new instance for every decision
task, the ActivityWorker simply uses the object you provided.
The ActivityWorker class uses the AWS Flow Framework for Java annotations to determine the
registration and execution options.
Moreover, each worker can be configured to process tasks on multiple threads. This means that the
activity tasks of a workflow instance can run concurrently even if there is only one worker.
Decision tasks behave similarly with the exception that Amazon SWF guarantees that for a given
workflow execution only one decision can be executed at a time. A single workflow execution will
typically require multiple decision tasks; hence, it may end up executing on multiple processes and
threads as well. The decider is configured with the type of the workflow implementation. When a
decision task is received by the decider, it creates an instance (object) of the workflow implementation.
The framework provides an extensible factory pattern for creating these instances. The default workflow
factory creates a new object every time. You can provide custom factories to override this behavior.
Contrary to deciders, which are configured with workflow implementation types, activity workers are
configured with instances (objects) of the activity implementations. When an activity task is received by
the activity worker, it is dispatched to the appropriate activity implementation object.
The workflow worker maintains a single pool of threads and executes the workflow on the same
thread that was used to poll Amazon SWF for the task. Since activities are long running (at least when
compared to the workflow logic), the activity worker class maintains two separate pools of threads; one
for polling Amazon SWF for activity tasks and the other for processing tasks by executing the activity
implementation. This allows you to configure the number of threads to poll for tasks separate from the
number of threads to execute them. For example, you can have a small number of threads to poll and a
large number of threads to execute the tasks. The activity worker class polls Amazon SWF for a task only
when it has a free poll thread as well as a free thread to process the task.
1. Activity implementations must be stateless. You should not use instance variables to store application
state in activity objects. You may, however, use fields to store resources such as database connections.
2. Activity implementations must be thread safe. Since the same instance may be used to process tasks
from different threads at the same time, access to shared resources from the activity code must be
synchronized.
3. Workflow implementation can be stateful, and instance variables may be used to store state. Even
though a new instance of the workflow implementation is created to process each decision task, the
framework will ensure that state is properly recreated. However, the workflow implementation must
be deterministic. See the section Under the Hood (p. 119) for more details.
4. Workflow implementations don't need to be thread safe when using the default factory. The default
implementation ensures that only one thread uses an instance of the workflow implementation at a
time.
Worker Extensibility
The AWS Flow Framework for Java also contains a couple of low-level worker classes that give you
fine-grained control as well as extensibility. Using them, you can completely customize workflow and
activity type registration and set factories for creating implementation objects. These workers are
GenericWorkflowWorker and GenericActivityWorker.
the workflow implementation and for providing configuration settings such as registration
options. Under normal circumstances, you should use the WorkflowWorker class directly. It will
automatically create and configure implementation of the factories provided in the framework,
POJOWorkflowDefinitionFactoryFactory and POJOWorkflowDefinitionFactory. The factory
requires that the workflow implementation class must have a no argument constructor. This constructor
is used to create instances of the workflow object at run time. The factory looks at the annotations you
used on the workflow interface and implementation to create appropriate registration and execution
options.
Execution Context
Topics
• Decision Context (p. 83)
• Activity Execution Context (p. 85)
The framework provides an ambient context to workflow and activity implementations. This context is
specific to the task being processed and provides some utilities that you can use in your implementation.
A context object is created every time a new task is processed by the worker.
Decision Context
When a decision task is executed, the framework provides the context to workflow implementation
through the DecisionContext class. DecisionContext provides context-sensitive information like
workflow execution run Id and clock and timer functionality.
DecisionContextProvider contextProvider
= new DecisionContextProviderImpl();
DecisionContext context = contextProvider.getDecisionContext();
WorkflowClock also has a createTimer method which returns a Promise object that becomes
ready after the specified interval. You can use this value as a parameter to other asynchronous methods
to delay their execution by the specified period of time. This way you can effectively schedule an
asynchronous method or activity for execution at a later time.
The example in the following listing demonstrates how to periodically call an activity.
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 60,
defaultTaskStartToCloseTimeoutSeconds = 10)
public interface PeriodicWorkflow {
@Execute(version = "1.0")
void periodicWorkflow();
}
@Activities(version = "1.0")
@ActivityRegistrationOptions(defaultTaskScheduleToStartTimeoutSeconds = 300,
defaultTaskStartToCloseTimeoutSeconds = 3600)
public interface PeriodicActivity {
void activity1();
}
@Override
public void periodicWorkflow() {
callPeriodicActivity(0);
}
@Asynchronous
private void callPeriodicActivity(int count,
Promise<?>... waitFor) {
if (count == 100) {
return;
}
PeriodicActivityClient client = new PeriodicActivityClientImpl();
// call activity
Promise<Void> activityCompletion = client.activity1();
// Repeat the activity either after 1 hour or after previous activity run
// if it takes longer than 1 hour
callPeriodicActivity(count + 1, timer, activityCompletion);
}
}
In the above listing, the callPeriodicActivity asynchronous method calls activity1 and then
creates a timer using the current AsyncDecisionContext. It passes the returned Promise as an
argument to a recursive call to itself. This recursive call waits until the timer fires (1 hour in this example)
before executing.
ActivityExecutionContextProvider provider
= new ActivityExecutionContextProviderImpl();
ActivityExecutionContext aec = provider.getActivityExecutionContext();
Get the Amazon SWF Client Object that is Being Used by the
Executor
The Amazon SWF client object being used by the executor can be retrieved by calling getService
method on ActivityExecutionContext. This is useful if you want to make a direct call to the
Amazon SWF service.
on the generated client. When a workflow execution is started from the context of another workflow
execution, it is called a child workflow execution. This allows you to refactor complex workflows into
smaller units and potentially share them across different workflows. For example, you can create a
payment processing workflow and call it from an order processing workflow.
Semantically, the child workflow execution behaves the same as a standalone workflow except for the
following differences:
1. When the parent workflow terminates due to an explicit action by the user—for example, by calling
the TerminateWorkflowExecution Amazon SWF API, or it is terminated due to a timeout—then
the fate of the child workflow execution will be determined by a child policy. You can set this child
policy to terminate, cancel, or abandon (keep running) child workflow executions.
2. The output of the child workflow (return value of the entry point method) can be used by the parent
workflow execution just like the Promise<T> returned by an asynchronous method. This is different
from standalone executions where the application must get the output by using Amazon SWF APIs.
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 60,
defaultTaskStartToCloseTimeoutSeconds = 10)
public interface OrderProcessor {
@Execute(version = "1.0")
void processOrder(Order order);
}
@Override
public void processOrder(Order order) {
float amount = order.getAmount();
CardInfo cardInfo = order.getCardInfo();
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 60,
defaultTaskStartToCloseTimeoutSeconds = 10)
public interface PaymentProcessor {
@Execute(version = "1.0")
void processPayment(float amount, CardInfo cardInfo);
@Override
public void processPayment(float amount, CardInfo cardInfo) {
Promise<PaymentType> payType = activitiesClient.getPaymentType(cardInfo);
switch(payType.get()) {
case Visa:
activitiesClient.processVisa(amount, cardInfo);
break;
case Amex:
activitiesClient.processAmex(amount, cardInfo);
break;
default:
throw new UnSupportedPaymentTypeException();
}
}
@Activities(version = "1.0")
@ActivityRegistrationOptions(defaultTaskScheduleToStartTimeoutSeconds = 3600,
defaultTaskStartToCloseTimeoutSeconds = 3600)
public interface PaymentActivities {
Continuous Workflows
In some use cases, you may need a workflow that executes forever or runs for a long duration, for
example, a workflow that monitors the health of a server fleet.
Note
Since Amazon SWF keeps the entire history of a workflow execution, the history will keep
growing over time. The framework retrieves this history from Amazon SWF when it performs
a replay, and this will become expensive if the history size is too large. In such long running or
continuous workflows, you should periodically close the current execution and start a new one
to continue processing.
This is a logical continuation of the workflow execution. The generated self client can be used for this
purpose. In your workflow implementation, simply call the @Execute method on the self client. Once
the current execution completes, the framework will start a new execution using the same workflow Id.
You can also continue the execution by calling the continueAsNewOnCompletion method on the
GenericWorkflowClient that you can retrieve from the current DecisionContext. For example, the
following workflow implementation sets a timer to fire after a day and calls its own entry point to start a
new execution.
@Override
public void startWorkflow() {
Promise<Void> timer = clock.createTimer(86400);
continueAsNew(timer);
}
@Asynchronous
void continueAsNew(Promise<Void> timer) {
selfClient.startWorkflow();
}
}
When a workflow recursively calls itself, the framework will close the current workflow when all pending
tasks have completed and start a new workflow execution. Note that as long as there are pending tasks,
the current workflow execution will not close. The new execution will not automatically inherit any
history or data from the original execution; if you want to carry over some state to the new execution,
then you must pass it explicitly as input.
You can set task priorities for both workflows and activities. A workflow's task priority doesn't affect
the priority of any activity tasks it schedules, nor does it affect any child workflows it starts. The default
priority for an activity or workflow is set (either by you or by Amazon SWF) during registration, and the
registered task priority is always used unless it is overridden while scheduling the activity or starting a
workflow execution.
Task priority values can range from "-2147483648" to "2147483647", with higher numbers indicating
higher priority. If you don't set the task priority for an activity or workflow, it will be assigned a priority
of zero ("0").
Topics
• Setting Task Priority for Workflows (p. 88)
• Setting Task Priority for Activities (p. 89)
To register a workflow type with a default task priority, set the defaultTaskPriority option in
WorkflowRegistrationOptions when declaring it:
@Workflow
@WorkflowRegistrationOptions(
defaultTaskPriority = 10,
defaultTaskStartToCloseTimeoutSeconds = 240)
public interface PriorityWorkflow
{
@Execute(version = "1.0")
void startWorkflow(int a);
}
You can also set the taskPriority for a workflow when you start it, overriding the registered (default) task
priority.
StartWorkflowOptions priorityWorkflowOptions
= new StartWorkflowOptions().withTaskPriority(10);
PriorityWorkflowClientExternalFactory cf
= new PriorityWorkflowClientExternalFactoryImpl(swfService, domain);
priority_workflow_client = cf.getClient();
priority_workflow_client.startWorkflow(
"Smith, John", priorityWorkflowOptions);
Additionally, you can set the task priority when starting a child workflow or continuing a workflow as
new. For example, you can set the taskPriority option in ContinueAsNewWorkflowExecutionParameters or
in StartChildWorkflowExecutionParameters.
To register an activity type with a default task priority, set the defaultTaskPriority option in
ActivityRegistrationOptions when declaring it:
@Activities(version = "1.0")
@ActivityRegistrationOptions(
defaultTaskPriority = 10,
defaultTaskStartToCloseTimeoutSeconds = 120)
public interface ImportantActivities {
int doSomethingImportant();
}
You can also set the taskPriority for an activity when you schedule it, overriding the registered (default)
task priority.
activityClient.doSomethingImportant(activityOptions);
DataConverters
When your workflow implementation calls a remote activity, the inputs passed to it and the result of
executing the activity must be serialized so they can be sent over the wire. The framework uses the
DataConverter class for this purpose. This is an abstract class that you can implement to provide your
own serializer. A default Jackson serializer–based implementation, JsonDataConverter, is provided
in the framework. For more details, see the AWS SDK for Java documentation. Refer to the Jackson
JSON Processor documentation for details about how Jackson performs serialization as well as Jackson
annotations that can be used to influence it. The wire format used is considered part of the contract.
Hence, you can specify a DataConverter on your activities and workflow interfaces by setting the
DataConverter property of the @Activities and @Workflow annotations.
The framework will create objects of the DataConverter type you specified on @Activities
annotation to serialize the inputs to the activity and to deserialize its result. Similarly, objects of the
DataConverter type you specify on @Workflow annotation will be used to serialize parameters
you pass to the workflow, and in the case of child workflow, to deserialize the result. In addition to
inputs, the framework also passes additional data to Amazon SWF—for example, exception details—the
workflow serializer will be used for serializing this data as well.
You can also provide an instance of the DataConverter if you don't want the framework to
automatically create it. The generated clients have constructor overloads that take a DataConverter.
If you don't specify a DataConverter type and don't pass a DataConverter object, the
JsonDataConverter will be used by default.
The use of Promise<T> has been explained in previous sections. Some advanced use cases of
Promise<T> are discussed here.
@Asynchronous
public void printList(Promise<List<String>> list) {
for (String s: list.get()) {
activityClient.printActivity(s);
}
}
Semantically, this behaves as any other Promise typed parameter and the asynchronous method will
wait until the collection becomes available before executing. If the members of a collection are Promise
objects, then you can make the framework wait for all members to become ready as shown in the
following snippet. This will make the asynchronous method wait on each member of the collection to
become available.
@Asynchronous
public void printList(@Wait List<Promise<String>> list) {
for (Promise<String< s: list) {
activityClient.printActivity(s);
}
}
Note that the @Wait annotation must be used on the parameter to indicate that it contains Promise
objects.
Note also that the activity printActivity takes a String argument but the matching method in the
generated client takes a Promise<String>. We are calling the method on the client and not invoking the
activity method directly.
Settable<T>
Settable<T> is a derived type of Promise<T> that provides a set method that allows you to manually
set the value of a Promise. For example, the following workflow waits for a signal to be received by
waiting on a Settable<?>, which is set in the signal method:
//@Execute method
@Override
public Promise<String> start() {
return done(result);
}
//Signal
@Override
public void manualProcessCompletedSignal(String data) {
result.set(data);
}
@Asynchronous
public Promise<String> done(Settable<String> result){
return result;
}
}
A Settable<?> can also be chained to another promise at a time. You can use AndPromise and
OrPromise to group promises. You can unchain a chained Settable by calling the unchain() method
on it. When chained, the Settable<?> automatically becomes ready when the promise that it is chained
to becomes ready. Chaining is especially useful when you want to use a promise returned from within the
scope of a doTry() in other parts of your program. Since TryCatchFinally is used as a nested class,
you can't declare a Promise<> in the parent's scope and set it in doTry(). This is because Java requires
variables to be declared in parent scope and used in nested classes to be marked final. For example:
@Asynchronous
public Promise<String> chain(final Promise<String> input) {
final Settable<String> result = new Settable<String>();
new TryFinally() {
@Override
protected void doTry() throws Throwable {
Promise<String> resultToChain = activity1(input);
activity2(resultToChain);
@Override
protected void doFinally() throws Throwable {
if (result.isReady()) { // Was a result returned before the exception?
// Do cleanup here
}
}
};
return result;
}
A Settable can be chained to one promise at a time. You can unchain a chained Settable by calling
the unchain() method on it.
@NoWait
When you pass a Promise to an asynchronous method, by default, the framework will wait for the
Promise(s) to become ready before executing the method (except for collection types). You may
override this behavior by using the @NoWait annotation on parameters in the declaration of the
asynchronous method. This is useful if you are passing in Settable<T>, which will be set by the
asynchronous method itself.
Promise<Void>
Dependencies in asynchronous methods are implemented by passing the Promise returned by one
method as an argument to another. However, there may be cases where you want to return void from
a method, but still want other asynchronous methods to execute after its completion. In such cases, you
can use Promise<Void> as the return type of the method. The Promise class provides a static Void
method that you can use to create a Promise<Void> object. This Promise will become ready when the
asynchronous method finishes execution. You can pass this Promise to another asynchronous method
just like any other Promise object. If you are using Settable<Void>, then call the set method on it
with null to make it ready.
The framework is designed to be Inversion of Control (IoC) friendly. Activity and workflow
implementations as well as the framework supplied workers and context objects can be configured and
instantiated using containers like Spring. Out of the box, the framework provides integration with the
Spring Framework. In addition, integration with JUnit has been provided for unit testing workflow and
activity implementations.
Spring Integration
The com.amazonaws.services.simpleworkflow.flow.spring package contains classes that make it easy to
use the Spring framework in your applications. These include a custom Scope and Spring-aware activity
and workflow workers: WorkflowScope, SpringWorkflowWorker and SpringActivityWorker.
These classes allow you to configure your workflow and activity implementations as well as the workers
entirely through Spring.
WorkflowScope
WorkflowScope is a custom Spring Scope implementation provided by the framework. This scope
allows you to create objects in the Spring container whose lifetime is scoped to that of a decision task.
The beans in this scope are instantiated every time a new decision task is received by the worker. You
should use this scope for workflow implementation beans and any other beans it depends on. The
Spring-provided singleton and prototype scopes should not be used for workflow implementation beans
because the framework requires that a new bean be created for each decision task. Failure to do so will
result in unexpected behavior.
The following example shows a snippet of Spring configuration that registers the WorkflowScope and
then uses it for configuring a workflow implementation bean and an activity client bean.
You can learn more about using custom scopes in the Spring Framework documentation.
Spring-Aware Workers
When using Spring, you should use the Spring-aware worker classes provided by the framework:
SpringWorkflowWorker and SpringActivityWorker. These workers can be injected in your
application using Spring as shown in the next example. The Spring-aware workers implement Spring's
SmartLifecycle interface and, by default, automatically start polling for tasks when the Spring
context is initialized. You can turn off this functionality by setting the disableAutoStartup property
of the worker to true.
The following example shows how to configure a decider. This example uses
MyActivities and MyWorkflow interfaces (not shown here) and corresponding
implementations, MyActivitiesImpl and MyWorkflowImpl. The generated client
interfaces and implementations are MyWorkflowClient/MyWorkflowClientImpl and
MyActivitiesClient/MyActivitiesClientImpl (also not shown here).
The activities client is injected in the workflow implementation using Spring's auto wire feature:
@Override
public void start() {
client.activity1();
}
}
</bean>
Since the SpringWorkflowWorker is fully configured in Spring and automatically starts polling when
the Spring context is initialized, the host process for the decider is simple:
</bean>
client.activity1();
}
}
If you want to configure the context objects in the workflow implementation through Spring XML
configuration, then use the bean names declared in the WorkflowScopeBeanNames class in the
com.amazonaws.services.simpleworkflow.flow.spring package. For example:
Alternatively, you may inject a DecisionContextProvider in the workflow implementation bean and
use it to create the context. This can be useful if you want to provide custom implementations of the
provider and context.
JUnit Integration
The framework provides JUnit extensions as well as test implementations of the context objects, such as
a test clock, that you can use to write and run unit tests with JUnit. With these extensions, you can test
your workflow implementation locally inline.
In order to use this class, simply declare a field of type WorkflowTest and annotate it with the @Rule
annotation. Before running your tests, create a new WorkflowTest object and add your activity and
workflow implementations to it. You can then use the generated workflow client factory to create a
client and start an execution of the workflow. The framework also provides a custom JUnit runner,
FlowBlockJUnit4ClassRunner, that you must use for your workflow tests. For example:
@RunWith(FlowBlockJUnit4ClassRunner.class)
public class BookingWorkflowTest {
@Rule
public WorkflowTest workflowTest = new WorkflowTest();
List<String> trace;
@Before
public void setUp() throws Exception {
trace = new ArrayList<String>();
// Register activity implementation to be used during test run
BookingActivities activities = new BookingActivitiesImpl(trace);
workflowTest.addActivitiesImplementation(activities);
workflowTest.addWorkflowImplementationType(BookingWorkflowImpl.class);
}
@After
public void tearDown() throws Exception {
trace = null;
}
@Test
public void testReserveBoth() {
BookingWorkflowClient workflow = workflowFactory.getClient();
Promise<Void> booked = workflow.makeBooking(123, 345, true, true);
List<String> expected = new ArrayList<String>();
expected.add("reserveCar-123");
expected.add("reserveAirline-123");
expected.add("sendConfirmation-345");
AsyncAssert.assertEqualsWaitFor("invalid booking", expected, trace, booked);
}
}
You can also specify a separate task list for each activity implementation that you add to
WorkflowTest. For example, if you have a workflow implementation that schedules activities in host-
specific task lists, then you can register the activity in the task list of each host:
Notice that the code in the @Test is asynchronous. Therefore, you should use the asynchronous
workflow client to start an execution. In order to verify the results of your test, an AsyncAssert help
class is also provided. This class allows you to wait for promises to become ready before verifying results.
In this example, we wait for the result of the workflow execution to be ready before verifying the test
output.
If you are using Spring, then the SpringWorkflowTest class can be used instead of the WorkflowTest
class. SpringWorkflowTest provides properties that you can use to configure activity and workflow
implementations easily through Spring configuration. Just like the Spring-aware workers, you should use
the WorkflowScope to configure workflow implementation beans. This ensures that a new workflow
implementation bean is created for every decision task. Make sure to configure these beans with the
scoped-proxy proxy-target-class setting set to false. See the Spring Integration section for more
details. The example Spring configuration shown in the Spring Integration section can be changed to test
the workflow using SpringWorkflowTest:
xsi:schemaLocation="http://www.springframework.org/schema/beans ht
tp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframe
work.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
</beans>
@RunWith(FlowBlockJUnit4ClassRunner.class)
public class BookingWorkflowTest {
@Rule
public WorkflowTest workflowTest = new WorkflowTest();
List<String> trace;
@Before
public void setUp() throws Exception {
trace = new ArrayList<String>();
// Create and register mock activity implementation to be used during test run
BookingActivities activities = new BookingActivities() {
@Override
public void sendConfirmationActivity(int customerId) {
trace.add("sendConfirmation-" + customerId);
}
@Override
public void reserveCar(int requestId) {
trace.add("reserveCar-" + requestId);
}
@Override
public void reserveAirline(int requestId) {
trace.add("reserveAirline-" + requestId);
}
};
workflowTest.addActivitiesImplementation(activities);
workflowTest.addWorkflowImplementationType(BookingWorkflowImpl.class);
}
@After
public void tearDown() throws Exception {
trace = null;
}
@Test
public void testReserveBoth() {
BookingWorkflowClient workflow = workflowFactory.getClient();
Promise<Void> booked = workflow.makeBooking(123, 345, true, true);
List<String> expected = new ArrayList<String>();
expected.add("reserveCar-123");
expected.add("reserveAirline-123");
expected.add("sendConfirmation-345");
AsyncAssert.assertEqualsWaitFor("invalid booking", expected, trace, booked);
}
}
Alternatively, you can provide a mock implementation of the activities client and inject that into your
workflow implementation.
Error Handling
Topics
• TryCatchFinally Semantics (p. 102)
• Cancellation (p. 103)
• Nested TryCatchFinally (p. 106)
The try/catch/finally construct in Java makes it simple to handle errors and is used ubiquitously.
It allows you to associate error handlers to a block of code. Internally, this works by stuffing additional
metadata about the error handlers on the call stack. When an exception is thrown, the runtime looks at
the call stack for an associated error handler and invokes it; and if no appropriate error handler is found,
it propagates the exception up the call chain.
This works well for synchronous code, but handling errors in asynchronous and distributed programs
poses additional challenges. Since an asynchronous call returns immediately, the caller isn't on the call
stack when the asynchronous code executes. This means that unhandled exceptions in the asynchronous
code can't be handled by the caller in the usual way. Typically, exceptions that originate in asynchronous
code are handled by passing error state to a callback that is passed to the asynchronous method.
Alternatively, if a Future<?> is being used, it reports an error when you try to access it. This is less
than ideal because the code that receives the exception (the callback or code that uses the Future<?
>) doesn't have the context of the original call and may not be able to handle the exception adequately.
Furthermore, in a distributed asynchronous system, with components running concurrently, more than
one error may occur simultaneously. These errors could be of different types and severities and need to
be handled appropriately.
Cleaning up resource after an asynchronous call is also difficult. Unlike synchronous code, you can't use
try/catch/finally in the calling code to clean up resources since work initiated in the try block may still be
ongoing when the finally block executes.
The framework provides a mechanism that makes error handling in distributed asynchronous code
similar to, and almost as simple as, Java's try/catch/finally.
ImageProcessingActivitiesClient activitiesClient
= new ImageProcessingActivitiesClientImpl();
new TryCatchFinally() {
@Override
protected void doTry() throws Throwable {
List<String> images = getImageUrls(webPageUrl);
for (String image: images) {
Promise<String> localImage
= activitiesClient.downloadImage(image);
Promise<String> thumbnailFile
= activitiesClient.createThumbnail(localImage);
activitiesClient.uploadImage(thumbnailFile);
}
}
@Override
protected void doCatch(Throwable e) throws Throwable {
@Override
protected void doFinally() throws Throwable {
activitiesClient.cleanUp();
}
};
}
The TryCatchFinally class and its variants, TryFinally and TryCatch, work similar to Java's
try/catch/finally. Using it, you can associate exception handlers to blocks of workflow code
that may execute as asynchronous and remote tasks. The doTry() method is logically equivalent
to the try block. The framework automatically executes the code in doTry(). A list of Promise
objects can be passed to the constructor of TryCatchFinally. The doTry method will be executed
when all Promise objects passed in to the constructor become ready. If an exception is raised
by code that was asynchronously invoked from within doTry(), any pending work in doTry()
is canceled and doCatch() is called to handle the exception. For instance, in the listing above, if
downloadImage throws an exception, then createThumbnail and uploadImage will be canceled.
Finally, doFinally() is called when all asynchronous work is done (completed, failed, or canceled). It
can be used for resource cleanup. You can also nest these classes to suit your needs.
When an exception is reported in doCatch(), the framework provides a complete logical call stack
that includes asynchronous and remote calls. This can be helpful when debugging, especially if you
have asynchronous methods calling other asynchronous methods. For example, an exception from
downloadImage will produce an exception like this:
TryCatchFinally Semantics
The execution of an AWS Flow Framework for Java program can be visualized as a tree of concurrently
executing branches. A call to an asynchronous method, an activity, and TryCatchFinally itself creates
a new branch in this tree of execution. For example, the image processing workflow can be viewed as the
tree shown in the following figure.
An error in one branch of execution will cause the unwinding of that branch, just as an exception causes
the unwinding of the call stack in a Java program. The unwinding keeps moving up the execution branch
until either the error is handled or the root of the tree is reached, in which case the workflow execution is
terminated.
The framework reports errors that happen while processing tasks as exceptions. It associates the
exception handlers (doCatch() methods) defined in TryCatchFinally with all tasks that are
created by the code in the corresponding doTry(). If a task fails—for example, due to a timeout or an
unhandled exception—then the appropriate exception will be raised and the corresponding doCatch()
will be invoked to handle it. To accomplish this, the framework works in tandem with Amazon SWF to
propagate remote errors and resurrects them as exceptions in the caller's context.
Cancellation
When an exception occurs in synchronous code, the control jumps directly to the catch block, skipping
over any remaining code in the try block. For example:
try {
a();
b();
c();
}
catch (Exception e) {
e.printStackTrace();
}
In this code, if b() throws an exception, then c() is never invoked. Compare that to a workflow:
new TryCatch() {
@Override
protected void doTry() throws Throwable {
activityA();
activityB();
activityC();
}
@Override
protected void doCatch(Throwable e) throws Throwable {
e.printStackTrace();
}
};
In this case, calls to activityA, activityB, and activityC all return successfully and result in the
creation of three tasks that will be executed asynchronously. Let's say at a later time that the task for
activityB results in an error. This error is recorded in the history by Amazon SWF. In order to handle
this, the framework will first try to cancel all other tasks that originated within the scope of the same
doTry(); in this case, activityA and activityC. When all such tasks complete (cancel, fail, or
successfully complete), the appropriate doCatch() method will be invoked to handle the error.
Unlike the synchronous example, where c() was never executed, activityC was invoked and a task
was scheduled for execution; hence, the framework will make an attempt to cancel it, but there is no
guarantee that it will be canceled. Cancellation can't be guaranteed because the activity may have
already completed, may ignore the cancellation request, or may fail due to an error. However, the
framework does provide the guarantee that doCatch() is called only after all tasks started from the
corresponding doTry() have completed. It also guarantees that doFinally() is called only after all
tasks started from the doTry() and doCatch() have completed. If, for instance, the activities in the
above example depend on each other, say activityB depends on activityA and activityC on
activityB, then the cancellation of activityC will be immediate because it isn't scheduled in Amazon
SWF until activityB completes:
new TryCatch() {
@Override
protected void doTry() throws Throwable {
Promise<Void> a = activityA();
Promise<Void> b = activityB(a);
activityC(b);
}
@Override
protected void doCatch(Throwable e) throws Throwable {
e.printStackTrace();
}
};
Activity Heartbeat
The AWS Flow Framework for Java's cooperative cancellation mechanism allows in-flight activity tasks to
be canceled gracefully. When cancellation is triggered, tasks that blocked or are waiting to be assigned
to a worker are automatically canceled. If, however, a task is already assigned to a worker, the framework
will request the activity to cancel. Your activity implementation must explicitly handle such cancellation
requests. This is done by reporting heartbeat of your activity.
Reporting heartbeat allows the activity implementation to report the progress of an ongoing activity
task, which is useful for monitoring, and it lets the activity check for cancellation requests. The
recordActivityHeartbeat method will throw a CancellationException if a cancellation
has been requested. The activity implementation can catch this exception and act on the
cancellation request, or it can ignore the request by swallowing the exception. In order to honor
the cancellation request, the activity should perform the desired clean up, if any, and then rethrow
CancellationException. When this exception is thrown from an activity implementation, the
framework records that the activity task has been completed in canceled state.
The following example shows an activity that downloads and processes images. It heartbeats after
processing each image, and if cancellation is requested, it cleans up and rethrows the exception to
acknowledge cancellation.
@Override
public void processImages(List<String> urls) {
int imageCounter = 0;
for (String url: urls) {
imageCounter++;
Image image = download(url);
process(image);
try {
ActivityExecutionContext context
= contextProvider.getActivityExecutionContext();
context.recordActivityHeartbeat(Integer.toString(imageCounter));
} catch(CancellationException ex) {
cleanDownloadFolder();
throw ex;
}
}
}
Reporting activity heartbeat isn't required, but it is recommended if your activity is long running or may
be performing expensive operations that you wish to be canceled under error conditions. You should call
heartbeatActivityTask periodically from the activity implementation.
If the activity times out, the ActivityTaskTimedOutException will be thrown and getDetails on
the exception object will return the data passed to the last successful call to heartbeatActivityTask
for the corresponding activity task. The workflow implementation may use this information to determine
how much progress was made before the activity task was timed out.
Note
It isn't a good practice to heartbeat too frequently because Amazon SWF may throttle heartbeat
requests. See the Amazon Simple Workflow Service Developer Guide for limits placed by
Amazon SWF.
@Override
public void processOrder(int orderId, final float amount) {
paymentTask = new TryCatchFinally() {
@Override
protected void doTry() throws Throwable {
processingPayment = true;
@Override
protected void doCatch(Throwable e) throws Throwable {
if (e instanceof CancellationException) {
paymentClient.log("Payment canceled.");
} else {
throw e;
}
}
@Override
protected void doFinally() throws Throwable {
processingPayment = false;
}
};
@Override
public void cancelPayment() {
if (processingPayment) {
paymentTask.cancel(null);
}
}
}
An unhandled CancellationException is propagated up the execution branch just like any other
exception. However, the doCatch() method will receive the CancellationException only if there is
no other exception in the scope; other exceptions are prioritized higher than cancellation.
Nested TryCatchFinally
You may nest TryCatchFinally's to suit your needs. Since each TryCatchFinally creates a new
branch in the execution tree, you can create nested scopes. Exceptions in the parent scope will cause
cancellation attempts of all tasks initiated by nested TryCatchFinally's within it. However, exceptions
in a nested TryCatchFinally don't automatically propagate to the parent. If you wish to propagate
an exception from a nested TryCatchFinally to its containing TryCatchFinally, you should
rethrow the exception in doCatch(). In other words, only unhandled exceptions are bubbled up, just like
Java's try/catch. If you cancel a nested TryCatchFinally by calling the cancel method, the nested
TryCatchFinally will be canceled but the containing TryCatchFinally will not automatically get
canceled.
new TryCatch() {
@Override
protected void doTry() throws Throwable {
activityA();
new TryCatch() {
@Override
protected void doTry() throws Throwable {
activityB();
}
@Override
activityC();
}
@Override
protected void doCatch(Throwable e) throws Throwable {
reportError(e);
}
};
There are a variety of strategies for retrying activities; the best one depends on the details of your
workflow. The strategies fall into three basic categories:
• The retry-until-success strategy simply keeps retrying the activity until it completes.
• The exponential retry strategy increases the time interval between retry attempts exponentially until
the activity completes or the process reaches a specified stopping point, such as a maximum number
of attempts.
• The custom retry strategy decides whether or how to retry the activity after each failed attempt.
The following sections describe how to implement these strategies. The example workflow workers all
use a single activity, unreliableActivity, which randomly does one of following:
• Completes immediately
• Fails intentionally by exceeding the timeout value
• Fails intentionally by throwing IllegalStateException
Retry-Until-Success Strategy
The simplest retry strategy is to keep retrying the activity each time it fails until it eventually succeeds.
The basic pattern is:
1. Implement a nested TryCatch or TryCatchFinally class in your workflow's entry point method.
2. Execute the activity in doTry
3. If the activity fails, the framework calls doCatch, which runs the entry point method again.
4. Repeat Steps 2 - 3 until the activity completes successfully.
implements RetryActivityRecipeWorkflow {
@Override
public void runUnreliableActivityTillSuccess() {
final Settable<Boolean> retryActivity = new Settable<Boolean>();
new TryCatch() {
@Override
protected void doTry() throws Throwable {
Promise<Void> activityRanSuccessfully
= client.unreliableActivity();
setRetryActivityToFalse(activityRanSuccessfully, retryActivity);
}
@Override
protected void doCatch(Throwable e) throws Throwable {
retryActivity.set(true);
}
};
restartRunUnreliableActivityTillSuccess(retryActivity);
}
@Asynchronous
private void setRetryActivityToFalse(
Promise<Void> activityRanSuccessfully,
@NoWait Settable<Boolean> retryActivity) {
retryActivity.set(false);
}
@Asynchronous
private void restartRunUnreliableActivityTillSuccess(
Settable<Boolean> retryActivity) {
if (retryActivity.get()) {
runUnreliableActivityTillSuccess();
}
}
}
Note
doCatch doesn't handle the exception; it simply sets the retryActivity object
to true to indicate that the activity failed. The retry is handled by the asynchronous
restartRunUnreliableActivityTillSuccess method, which defers execution until
TryCatch completes. The reason for this approach is that, if you retry an activity in doCatch,
you can't cancel it. Retrying the activity in restartRunUnreliableActivityTillSuccess
allows you to execute cancellable activities.
• The @ExponentialRetry annotation is the simplest approach, but you must set the retry
configuration options at compile time.
• The RetryDecorator class allows you to set retry configuration at run time and change it as needed.
• The AsyncRetryingExecutor class allows you to set retry configuration at run time and change it
as needed. In addition, the framework calls a user-implemented AsyncRunnable.run method to run
each retry attempt.
All approaches support the following configuration options, where time values are in seconds:
The following sections describe the various ways that you can implement an exponential retry strategy.
1. Apply @ExponentialRetry to the appropriate activities and specify the retry configuration.
2. If an annotated activity fails, the framework automatically retries the activity according to the
configuration specified by the annotation's arguments.
@Activities(version = "1.0")
@ActivityRegistrationOptions(
defaultTaskScheduleToStartTimeoutSeconds = 30,
defaultTaskStartToCloseTimeoutSeconds = 30)
public interface ExponentialRetryAnnotationActivities {
@ExponentialRetry(
initialRetryIntervalSeconds = 5,
maximumAttempts = 5,
exceptionsToRetry = IllegalStateException.class)
public void unreliableActivity();
}
If the activity fails by throwing IllegalStateException, the framework automatically runs the retry
strategy specified in ExponentialRetryAnnotationActivities.
1. Create and configure an ExponentialRetryPolicy object that specifies the retry configuration.
2. Create a RetryDecorator object and pass the ExponentialRetryPolicy object from Step 1 to
the constructor.
3. Apply the decorator object to the activity by passing the activity client's class name to the
RetryDecorator object's decorate method.
4. Execute the activity.
If the activity fails, the framework retries the activity according to the ExponentialRetryPolicy
object's configuration. You can change the retry configuration as needed by modifying this object.
Note
The @ExponentialRetry annotation and the RetryDecorator class are mutually exclusive.
You can't use RetryDecorator to dynamically override a retry policy specified by an
@ExponentialRetry annotation.
The following workflow implementation shows how to use the RetryDecorator class to implement
an exponential retry strategy. It uses an unreliableActivity activity that doesn't have an
@ExponentialRetry annotation. The workflow interface is implemented in RetryWorkflow and has
one method, process, which is the workflow's entry point. The workflow worker is implemented in
DecoratorRetryWorkflowImpl, as follows:
If the activity fails, the framework retries it according to the configuration specified in Step 1.
Note
Several of the ExponentialRetryPolicy class's with methods have a corresponding set
method that you can call to modify the corresponding configuration option at any time:
setBackoffCoefficient, setMaximumAttempts, setMaximumRetryIntervalSeconds,
and setMaximumRetryExpirationIntervalSeconds.
The following workflow shows how to use the AsyncRetryingExecutor class to implement
an exponential retry strategy. It uses the same unreliableActivity activity as the
DecoratorRetryWorkflow workflow discussed earlier. The workflow interface is implemented in
RetryWorkflow and has one method, process, which is the workflow's entry point. The workflow
worker is implemented in AsyncExecutorRetryWorkflowImpl, as follows:
new TryCatch() {
@Override
protected void doTry() throws Throwable {
executor.execute(new AsyncRunnable() {
@Override
public void run() throws Throwable {
client.unreliableActivity();
}
});
}
@Override
protected void doCatch(Throwable e) throws Throwable {
}
};
}
}
1. process calls the handleUnreliableActivity method and passes it the configuration settings.
2. handleUnreliableActivity uses the configuration settings from Step 1 to create an
ExponentialRetryPolicy object, retryPolicy.
3. handleUnreliableActivity creates an AsyncRetryExecutor object, executor, and passes
the ExponentialRetryPolicy object from Step 2 and an instance of the workflow clock to the
constructor
4. handleUnreliableActivity implements an anonymous nested TryCatch class and overrides the
doTry and doCatch methods to run the retry attempts and handle any exceptions.
5. doTry creates an anonymous AsyncRunnable class and overrides the run method to implement
custom code to execute unreliableActivity. For simplicity, run just executes the activity, but you
can implement more sophisticated approaches as appropriate.
6. doTry calls executor.execute and passes it the AsyncRunnable object. execute calls the
AsyncRunnable object's run method to run the activity.
7. If the activity fails, executor calls run again, according to the retryPolicy object configuration.
For more discussion of how to use the TryCatch class to handle errors, see AWS Flow Framework for
Java Exceptions (p. 132).
1. Create a Settable<T> status object, which is used to indicate whether the activity failed.
2. Implement a nested TryCatch or TryCatchFinally class.
3. doTry executes the activity.
4. If the activity fails, doCatch sets the status object to indicate that the activity failed.
5. Call an asynchronous failure handling method and pass it the status object. The method defers
execution until TryCatch or TryCatchFinally completes.
6. The failure handling method decides whether to retry the activity, and if so, when.
The following workflow shows how to implement a custom retry strategy. It uses
the same unreliableActivity activity as the DecoratorRetryWorkflow and
AsyncExecutorRetryWorkflow workflows. The workflow interface is implemented in
RetryWorkflow and has one method, process, which is the workflow's entry point. The workflow
worker is implemented in CustomLogicRetryWorkflowImpl, as follows:
shouldRetry implements custom logic to decide whether to retry a failed activity. For simplicity,
shouldRetry always returns true and retryOnFailure executes the activity immediately, but
you can implement more sophisticated logic as needed.
9. Steps 2–8 repeat until unreliableActivity completes or shouldRetry decides to stop the
process.
Note
doCatch doesn't handle the retry process; it simply sets failure to indicate that the activity
failed. The retry process is handled by the asynchronous retryOnFailure method, which
defers execution until TryCatch completes. The reason for this approach is that, if you retry an
activity in doCatch, you can't cancel it. Retrying the activity in retryOnFailure allows you to
execute cancellable activities.
Daemon Tasks
The AWS Flow Framework for Java allows the marking of certain tasks as daemon. This allows you to
create tasks that do some background work that should get canceled when all other work is done.
For example, a health monitoring task should be canceled when the rest of the workflow is complete.
You can accomplish this by setting the daemon flag on an asynchronous method or an instance of
TryCatchFinally. In the following example, the asynchronous method monitorHealth() is marked
as daemon.
@Override
public void startMyWF(int a, String b) {
activitiesClient.doUsefulWorkActivity();
monitorHealth();
}
@Asynchronous(daemon=true)
void monitorHealth(Promise<?>... waitFor) {
activitiesClient.monitoringActivity();
}
}
@Override
public void startMyWF(int a, String b) {
activitiesClient.doUsefulWorkActivity();
new TryFinally(true) {
@Override
protected void doTry() throws Throwable {
activitiesClient.monitoringActivity();
}
@Override
protected void doFinally() throws Throwable {
// clean up
}
};
}
}
A daemon task started within a TryCatchFinally is scoped to the context it is created in—that
is, it will be scoped to either the doTry(), doCatch(), or doFinally() methods. For example, in
the following example the startMonitoring asynchronous method is marked daemon and called from
doTry(). The task created for it will be canceled as soon as the other tasks (doUsefulWorkActivity
in this case) started within doTry() are complete.
@Override
public void startMyWF(int a, String b) {
new TryFinally() {
@Override
protected void doTry() throws Throwable {
activitiesClient.doUsefulWorkActivity();
startMonitoring();
}
@Override
protected void doFinally() throws Throwable {
// Clean up
}
};
}
@Asynchronous(daemon = true)
void startMonitoring(){
activitiesClient.monitoringActivity();
}
...
public void greet() {
System.out.println("greet executes");
Promise<String> name = operations.getName();
System.out.println("client.getName returns");
Promise<String> greeting = operations.getGreeting(name);
System.out.println("client.greeting returns");
operations.say(greeting);
System.out.println("client.say returns");
}
}
**************
public class GreeterActivitiesImpl implements GreeterActivities {
public String getName() {
System.out.println("activity.getName completes");
return "World";
}
For details about the code, see HelloWorldWorkflow Application (p. 14). The following is an edited
version of the output, with comments that indicate the start of each replay episode.
//Episode 1
greet executes
client.getName returns
client.greeting returns
client.say returns
activity.getName completes
//Episode 2
greet executes
client.getName returns
client.greeting returns
client.say returns
activity.getGreeting completes
//Episode 3
greet executes
client.getName returns
client.greeting returns
client.say returns
• The first episode schedules the getName activity task, which has no dependencies.
• The second episode schedules the getGreeting activity task, which depends on getName.
• The third episode schedules the say activity task, which depends on getGreeting.
• The final episode schedules no additional tasks and finds no uncompleted activities, which terminates
the workflow execution.
Note
The three activities client methods are called once for each episode. However, only one of those
calls results in an activity task, so each task is performed only once.
//Episode 1
greet executes
client.name returns
workflow.getGreeting returns
client.say returns
activity.getName completes
//Episode 2
greet executes
client.name returns
workflow.getGreeting returns
client.say returns
workflow.getGreeting completes
HelloWorldAsync uses three replay episodes because there are only two activities. The getGreeting
activity was replaced by the getGreeting asynchronous workflow method, which doesn't initiate a replay
episode when it completes.
The first episode doesn't call getGreeting, because it depends on the completion of the name activity.
However, after getName completes, replay calls getGreeting once for each succeeding episode.
See Also
• AWS Flow Framework Basic Concepts: Distributed Execution (p. 38)
Task
The underlying primitive that the AWS Flow Framework for Java uses to manage the execution of
asynchronous code is the Task class. An object of type Task represents work that has to be performed
asynchronously. When you call an asynchronous method, the framework creates a Task to execute
the code in that method and puts it in a list for execution at a later time. Similarly, when you invoke an
Activity, a Task is created for it. The method call returns after this, usually returning a Promise<T>
as the future result of the call.
The Task class is public and may be used directly. For example, we can rewrite the Hello World example
to use a Task instead of an asynchronous method.
@Override
public void startHelloWorld(){
final Promise<String> greeting = client.getName();
new Task(greeting) {
@Override
protected void doExecute() throws Throwable {
client.printGreeting("Hello " + greeting.get() +"!");
}
};
}
The framework calls the doExecute() method when all the Promises passed to the constructor of the
Task become ready. For more details about the Task class, see the AWS Java SDK documentation.
The framework also includes a class called Functor which represents a Task that is also a Promise<T>.
The Functor object becomes ready when the Task completes. In the following example, a Functor is
created to get the greeting message:
Order of Execution
Tasks become eligible for execution only when all Promise<T> typed parameters, passed to the
corresponding asynchronous method or activity, become ready. A Task that is ready for execution is
logically moved to a ready queue. In other words, it is scheduled for execution. The worker class executes
the task by invoking the code that you wrote in the body of the asynchronous method, or by scheduling
an activity task in Amazon Simple Workflow Service (AWS) in case of an activity method.
As tasks execute and produce results, they cause other tasks to become ready and the execution of the
program keeps moving forward. The way the framework executes tasks is important to understand the
order in which your asynchronous code executes. Code that appears sequentially in your program may
not actually execute in that order.
@Asynchronous
private Promise<String> getUserName(){
return Promise.asPromise("Bob");
}
@Asynchronous
private void printHelloName(Promise<String> name){
System.out.println("Hello, " + name.get() + "!");
}
@Asynchronous
private void printHelloWorld(){
System.out.println("Hello, World!");
}
Hello, Amazon!
Hello, World!
Hello, Bob
This may not be what you expected but can be easily explained by thinking through how the tasks for
the asynchronous methods were executed:
1. The call to getUserName creates a Task. Let's call it Task1. Since getUserName doesn't take any
parameters, Task1 is immediately put in the ready queue.
2. Next, the call to printHelloName creates a Task that needs to wait for the result of getUserName.
Let's call it Task2. Since the requisite value isn't ready yet, Task2 is put in the wait list.
3. Then a task for printHelloWorld is created and added to the ready queue. Let's call it Task3.
4. The println statement then prints "Hello, Amazon!" to the console.
5. At this point, Task1 and Task3 are in the ready queue and Task2 is in the wait list.
6. The worker executes Task1, and its result makes Task2 ready. Task2 gets added to ready queue
behind Task3.
7. Task3 and Task2 are then executed in that order.
The execution of activities follows the same pattern. When you call a method on the activity client, it
creates a Task that, upon execution, schedules an activity in Amazon SWF.
The framework relies on features like code generation and dynamic proxies to inject the logic for
converting method calls to activity invocations and asynchronous tasks in your program.
Workflow Execution
The execution of the workflow implementation is also managed by the worker class. When you call a
method on the workflow client, it calls Amazon SWF to create a workflow instance. The tasks in Amazon
SWF should not be confused with the tasks in the framework. A task in Amazon SWF is either an activity
task or a decision task. The execution of activity tasks is simple. The activity worker class receives activity
tasks from Amazon SWF, invokes the appropriate activity method in your implementation, and returns
the result to Amazon SWF.
The execution of decision tasks is more involved. The workflow worker receives decision tasks from
Amazon SWF. A decision task is effectively a request asking the workflow logic what to do next. The first
decision task is generated for a workflow instance when it is started through the workflow client. Upon
receiving this decision task, the framework starts executing the code in the workflow method annotated
with @Execute. This method executes the coordination logic that schedules activities. When the state
of the workflow instance changes—for example, when an activity completes—further decision tasks
get scheduled. At this point, the workflow logic can decide to take an action based on the result of the
activity; for example, it may decide to schedule another activity.
The framework hides all these details from the developer by seamlessly translating decision tasks to the
workflow logic. From a developer's point of view, the code looks just like a regular program. Under the
covers, the framework maps it to calls to Amazon SWF and decision tasks using the history maintained
by Amazon SWF. When a decision task arrives, the framework replays the program execution plugging in
the results of the activities completed so far. Asynchronous methods and activities that were waiting for
these results get unblocked, and the program execution moves forward.
The execution of the example image processing workflow and the corresponding history is shown in the
following table.
Initial execution
Replay
Replay
Replay
...
When a call to processImage is made, the framework creates a new workflow instance in Amazon
SWF. This is a durable record of the workflow instance being started. The program executes until the
call to the downloadImage activity, which asks Amazon SWF to schedule an activity. The workflow
executes further and creates tasks for subsequent activities, but they can't be executed until the
downloadImage activity completes; hence, this episode of replay ends. Amazon SWF dispatches the
task for downloadImage activity for execution, and once it is completed, a record is made in the history
along with the result. The workflow is now ready to move forward and a decision task is generated by
Amazon SWF. The framework receives the decision task and replays the workflow plugging in the result
of the downloaded image as recorded in the history. This unblocks the task for createThumbnail, and
the execution of the program continues farther by scheduling the createThumbnail activity task in
Amazon SWF. The same process repeats for uploadImage. The execution of the program continues this
way until the workflow has processed all images and there are no pending tasks. Since no execution state
is stored locally, each decision task may be potentially executed on a different machine. This allows you
to easily write programs that are fault tolerant and easily scalable.
Nondeterminism
Since the framework relies on replay, it is important that the orchestration code (all workflow code
with the exception of activity implementations) be deterministic. For example, the control flow in your
program should not depend on a random number or the current time. Since these things will change
between invocations, the replay may not follow the same path through the orchestration logic. This will
lead to unexpected results or errors. The framework provides a WorkflowClock that you can use to get
the current time in a deterministic way. See the section on Execution Context (p. 83) for more details.
Note
Incorrect Spring wiring of workflow implementation objects can also lead to nondeterminism.
Workflow implementation beans as well as beans that they depend on must be in the workflow
scope (WorkflowScope). For example, wiring a workflow implementation bean to a bean
that keeps state and is in the global context will result in unexpected behavior. See the Spring
Integration (p. 92) section for more details.
This section describes some common pitfalls that you might run into while developing workflows using
AWS Flow Framework for Java. It also provides some tips to help you diagnose and debug problems.
Compilation Errors
If you are using the AspectJ compile time weaving option, you may run into compile time errors in
which the compiler isn't able to find the generated client classes for your workflow and activities. The
likely cause of such compilation errors is that the AspectJ builder ignored the generated clients during
compilation. You can fix this issue by removing AspectJ capability from the project and re-enabling it.
Note that you will need to do this every time your workflow or activities interfaces change. Because of
this issue, we recommend that you use the load time weaving option instead. See the section Setting up
the AWS Flow Framework for Java (p. 3) for more details.
• You configure a worker with a domain that doesn't exist. To fix this, first register the domain using the
Amazon SWF console or the Amazon SWF service API.
• You try to create workflow execution or activity tasks of types that have not been registered. This can
happen if you try to create the workflow execution before the workers have been run. Since workers
register their types when they are run for the first time, you must run them at least once before
attempting to start executions (or manually register the types using the Console or the service API).
Note that once types have been registered, you can create executions even if no worker is running.
• A worker attempts to complete a task that has already timed out. For example, if a worker takes too
long to process a task and exceeds a timeout, it will get an UnknownResource fault when it attempts
to complete or fail the task. The AWS Flow Framework workers will continue to poll Amazon SWF and
process additional tasks. However, you should consider adjusting the timeout. Adjusting the timeout
requires that you register a new version of the activity type.
to an asynchronous method (or a task) and access its value in the asynchronous method. AWS Flow
Framework for Java ensures that an asynchronous method is called only when all Promise arguments
passed to it have become ready. If you believe your code is correct or if you run into this while running
one of the AWS Flow Framework samples, then it is most likely due to AspectJ not being properly
configured. For details, see the section Setting up the AWS Flow Framework for Java (p. 3).
AWS Flow Framework provides a WorkflowReplayer class that you can use to replay a workflow
execution locally and debug it. Using this class, you can debug closed and running workflow executions.
WorkflowReplayer relies on the history stored in Amazon SWF to perform the replay. You can point it
to a workflow execution in your Amazon SWF account or provide it with the history events (for example,
you can retrieve the history from Amazon SWF and serialize it locally for later use). When you replay a
workflow execution using the WorkflowReplayer, it doesn't impact the workflow execution running
in your account. The replay is done completely on the client. You can debug the workflow, create
breakpoints, and step into code using your debugging tools as usual. If you are using Eclipse, consider
adding step filters to filter AWS Flow Framework packages.
For example, the following code snippet can be used to replay a workflow execution:
AWS Flow Framework also allows you to get an asynchronous thread dump of your workflow execution.
This thread dump gives you the call stacks of all open asynchronous tasks. This information can be useful
to determine which tasks in the execution are pending and possibly stuck. For example:
try {
String flowThreadDump = replayer.getAsynchronousThreadDumpAsString();
System.out.println("Workflow asynchronous thread dump:");
System.out.println(flowThreadDump);
}
catch (WorkflowException e) {
System.out.println("No asynchronous thread dump available as workflow has failed: " +
e);
}
Lost Tasks
Sometimes you may shut down workers and start new ones in quick succession only to discover that
tasks get delivered to the old workers. This can happen due to race conditions in the system, which is
distributed across several processes. The problem can also appear when you are running unit tests in a
tight loop. Stopping a test in Eclipse can also sometimes cause this because shutdown handlers may not
get called.
In order to make sure that the problem is in fact due to old workers getting tasks, you should look at
the workflow history to determine which process received the task that you expected the new worker to
receive. For example, the DecisionTaskStarted event in history contains the identity of the workflow
worker that received the task. The id used by the Flow Framework is of the form: {processId}@{host
name}. For instance, following are the details of the DecisionTaskStarted event in the Amazon SWF
console for a sample execution:
Identity 2276@ip-0A6C1DF5
Scheduled Event Id 33
In order to avoid this situation, use different task lists for each test. Also, consider adding a delay
between shutting down old workers and starting new ones.
@Activities
This annotation can be used on an interface to declare a set of activity types. Each method in an
interface annotated with this annotation represents an activity type. An interface can't have both
@Workflow and @Activities annotations-
activityNamePrefix
Specifies the prefix of the name of the activity types declared in the interface. If set to an empty
string (which is the default), the name of the interface followed by '.' is used as the prefix.
version
Specifies the default version of the activity types declared in the interface. The default value is 1.0.
dataConverter
Specifies the type of the DataConverter to use for serializing/deserializing data when creating
tasks of this activity type and its results. Set to NullDataConverter by default, which indicates
that the JsonDataConverter should be used.
@Activity
This annotation can be used on methods within an interface annotated with @Activities.
name
Specifies the name of the activity type. The default is an empty string, which indicates that the
default prefix and the activity method name should be used to determine the name of the activity
type (which is of the form {prefix}{name}). Note that when you specify a name in an @Activity
annotation, the framework will not automatically prepend a prefix to it. You are free to use your own
naming scheme.
version
Specifies the version of the activity type. This overrides the default version specified in the
@Activities annotation on the containing interface. The default is an empty string.
@ActivityRegistrationOptions
Specifies the registration options of an activity type. This annotation can be used on an interface
annotated with @Activities or the methods within. If specified in both places, then the annotation
used on the method takes effect.
defaultTasklist
Specifies the default task list to be registered with Amazon SWF for this activity type. This
default can be overridden when calling the activity method on the generated client using the
ActivitySchedulingOptions parameter. Set to USE_WORKER_TASK_LIST by default. This
is a special value which indicates that the task list used by the worker, which is performing the
registration, should be used.
defaultTaskScheduleToStartTimeoutSeconds
Specifies the defaultTaskScheduleToStartTimeout registered with Amazon SWF for this activity type.
This is the maximum time a task of this activity type is allowed to wait before it is assigned to a
worker. See the Amazon Simple Workflow Service API Reference for more details.
defaultTaskHeartbeatTimeoutSeconds
Specifies the defaultTaskHeartbeatTimeout registered with Amazon SWF for this activity type.
Activity workers must provide heartbeat within this duration; otherwise, the task will be timed out.
Set to -1 by default, which is a special value that indicates this timeout should be disabled. See the
Amazon Simple Workflow Service API Reference for more details.
defaultTaskStartToCloseTimeoutSeconds
Specifies the defaultTaskStartToCloseTimeout registered with Amazon SWF for this activity type.
This timeout determines the maximum time a worker can take to process an activity task of this
type. See the Amazon Simple Workflow Service API Reference for more details.
defaultTaskScheduleToCloseTimeoutSeconds
Specifies the defaultScheduleToCloseTimeout registered with Amazon SWF for this activity
type. This timeout determines the total duration that the task can stay in open state. Set to -1 by
default, which is a special value that indicates this timeout should be disabled. See the Amazon
Simple Workflow Service API Reference for more details.
@Asynchronous
When used on a method in the workflow coordination logic, indicates that the method should be
executed asynchronously. A call to the method will return immediately, but the actual execution will
happen asynchronously when all Promise<> parameters passed to the methods become ready. Methods
annotated with @Asynchronous must have a return type of Promise<> or void.
daemon
Indicates if the task created for the asynchronous method should be a daemon task. False by
default.
@Execute
When used on a method in an interface annotated with the @Workflow annotation, identifies the entry
point of the workflow.
Important
Only one method in the interface can be decorated with @Execute.
name
Specifies the name of the workflow type. If not set, the name defaults to {prefix}{name}, where
{prefix} is the name of the workflow interface followed by a '.' and {name} is the name of the
@Execute-decorated method in the workflow.
version
@ExponentialRetry
When used on an activity or asynchronous method, sets an exponential retry policy if the method throws
an unhandled exception. A retry attempt is made after a back-off period, which is calculated by the
power of the number of attempts.
intialRetryIntervalSeconds
Specifies the duration to wait before the first retry attempt. This value should not be greater than
maximumRetryIntervalSeconds and retryExpirationSeconds.
maximumRetryIntervalSeconds
Specifies the maximum duration between retry attempts. Once reached, the retry interval is capped
to this value. Set to -1 by default, which means unlimited duration.
retryExpirationSeconds
Specifies the duration after which exponential retry will stop. Set to -1 by default, which means
there is no expiration.
backoffCoefficient
Specifies the coefficient used to calculate the retry interval. See Exponential Retry Strategy (p. 109).
maximumAttempts
Specifies the number of attempts after which exponential retry will stop. Set to -1 by default, which
means there is no limit on the number of retry attempts.
exceptionsToRetry
Specifies the list of exception types that should trigger a retry. Unhandled exception of these types
will not propagate further and the method will be retried after the calculated retry interval. By
default, the list contains Throwable.
excludeExceptions
Specifies the list of exception types that should not trigger a retry. Unhandled exceptions of this
type will be allowed to propagate. The list is empty by default.
@GetState
When used on a method in an interface annotated with the @Workflow annotation, identifies that the
method is used to retrieve the latest workflow execution state. There can be at most one method with
this annotation in an interface with the @Workflow annotation. Methods with this annotation must not
take any parameters and must have a return type other than void.
@ManualActivityCompletion
This annotation can be used on an activity method to indicate that the activity task should not be
completed when the method returns. The activity task will not be automatically completed and
would need to be completed manually directly using the Amazon SWF API. This is useful for use cases
where the activity task is delegated to some external system that isn't automated or requires human
intervention to be completed.
@Signal
When used on a method in an interface annotated with the @Workflow annotation, identifies a signal
that can be received by executions of the workflow type declared by the interface. Use of this annotation
is required to define a signal method.
name
Specifies the name portion of the signal name. If not set, the name of the method is used.
@SkipRegistration
When used on an interface annotated with the @Workflow annotation, indicates that the workflow
type should not be registered with Amazon SWF. One of @WorkflowRegistrationOptions and
@SkipRegistrationOptions annotations must be used on an interface annotated with @Workflow,
but not both.
execution occurs. In certain scenarios, it is necessary to override this default behavior. Promise<>
parameters passed into @Asynchronous methods and annotated with @NoWait are not waited for.
Collections parameters (or subclasses of) that contain promises, such as List<Promise<Int>>, must
be annotated with @Wait annotation. By default, the framework doesn't wait for the members of a
collection.
@Workflow
This annotation is used on an interface to declare a workflow type. An interface decorated with this
annotation should contain exactly one method that is decorated with the @Execute (p. 129) annotation
to declare an entry point for your workflow.
Note
An interface can't have both @Workflow and @Activities annotations declared at once; they
are mutually exclusive.
dataConverter
Specifies which DataConverter to use when sending requests to, and receiving results from,
workflow executions of this workflow type.
Example
import com.amazonaws.services.simpleworkflow.flow.annotations.Execute;
import com.amazonaws.services.simpleworkflow.flow.annotations.Workflow;
import com.amazonaws.services.simpleworkflow.flow.annotations.WorkflowRegistrationOptions;
@Workflow
@WorkflowRegistrationOptions(defaultExecutionStartToCloseTimeoutSeconds = 3600)
public interface GreeterWorkflow {
@Execute(version = "1.0")
public void greet();
}
@WorkflowRegistrationOptions
When used on an interface annotated with @Workflow, provides default settings used by Amazon SWF
when registering the workflow type.
Note
Either @WorkflowRegistrationOptions or @SkipRegistrationOptions must be used on
an interface annotated with @Workflow, but you can't specify both.
Description
For more information about workflow timeouts, see Amazon SWF Timeout Types (p. 44).
defaultTaskStartToCloseTimeoutSeconds
Specifies the defaultTaskStartToCloseTimeout registered with Amazon SWF for the workflow
type. This specifies the time a single decision task for a workflow execution of this type can take to
complete.
For more information about workflow timeouts, see Amazon SWF Timeout Types (p. 44).
defaultTaskList
The default task list used for decision tasks for executions of this workflow type. The default set here
can be overridden by using StartWorkflowOptions when starting a workflow execution.
Specifies the policy to use for child workflows if an execution of this type is terminated. The default
value is ABANDON. The possible values are:
• ABANDON – Allow the child workflow executions to keep running
• TERMINATE – Terminate child workflow executions
• REQUEST_CANCEL – Request cancellation of the child workflow executions
Topics
• ActivityFailureException (p. 133)
• ActivityTaskException (p. 133)
• ActivityTaskFailedException (p. 133)
• ActivityTaskTimedOutException (p. 133)
• ChildWorkflowException (p. 133)
• ChildWorkflowFailedException (p. 133)
• ChildWorkflowTerminatedException (p. 133)
• ChildWorkflowTimedOutException (p. 134)
• DataConverterException (p. 134)
• DecisionException (p. 134)
• ScheduleActivityTaskFailedException (p. 134)
• SignalExternalWorkflowException (p. 134)
• StartChildWorkflowFailedException (p. 134)
• StartTimerFailedException (p. 134)
• TimerException (p. 134)
ActivityFailureException
This exception is used by the framework internally to communicate activity failure. When an activity fails
due to an unhandled exception, it is wrapped in ActivityFailureException and reported to Amazon
SWF. You need to deal with this exception only if you use the activity worker extensibility points. Your
application code will never need to deal with this exception.
ActivityTaskException
This is the base class for activity task failure exceptions: ScheduleActivityTaskFailedException,
ActivityTaskFailedException, ActivityTaskTimedoutException. It contains the task Id and
activity type of the failed task. You can catch this exception in your workflow implementation to deal
with activity failures in a generic way.
ActivityTaskFailedException
Unhandled exceptions in activities are reported back to the workflow implementation by throwing an
ActivityTaskFailedException. The original exception can be retrieved from the cause property of
this exception. The exception also provides other information that is useful for debugging purposes, such
as the unique activity identifier in the history.
The framework is able to provide the remote exception by serializing the original exception from the
activity worker.
ActivityTaskTimedOutException
This exception is thrown if an activity was timed out by Amazon SWF. This could happen if the
activity task could not be assigned to the worker within the require time period or could not be
completed by the worker in the required time. You can set these timeouts on the activity using the
@ActivityRegistrationOptions annotation or using the ActivitySchedulingOptions
parameter when calling the activity method.
ChildWorkflowException
Base class for exceptions used to report failure of child workflow execution. The exception contains the
Ids of the child workflow execution as well as its workflow type. You can catch this exception to deal with
child workflow execution failures in a generic way.
ChildWorkflowFailedException
Unhandled exceptions in child workflows are reported back to the parent workflow implementation by
throwing a ChildWorkflowFailedException. The original exception can be retrieved from the cause
property of this exception. The exception also provides other information that is useful for debugging
purposes, such as the unique identifiers of the child execution.
ChildWorkflowTerminatedException
This exception is thrown in parent workflow execution to report the termination of a child workflow
execution. You should catch this exception if you want to deal with the termination of the child
workflow, for example, to perform cleanup or compensation.
ChildWorkflowTimedOutException
This exception is thrown in parent workflow execution to report that a child workflow execution was
timed out and closed by Amazon SWF. You should catch this exception if you want to deal with the
forced closure of the child workflow, for example, to perform cleanup or compensation.
DataConverterException
The framework uses the DataConverter component to marshal and unmarshal data that is sent over
the wire. This exception is thrown if the DataConverter fails to marshal or unmarshal data. This could
happen for various reasons, for example, due to a mismatch in the DataConverter components being
used to marshal and unmarshal the data.
DecisionException
This is the base class for exceptions that represent failures to enact a decision by Amazon SWF. You can
catch this exception to generically deal with such exceptions.
ScheduleActivityTaskFailedException
This exception is thrown if Amazon SWF fails to schedule an activity task. This could happen due to
various reasons—for example, the activity was deprecated, or an Amazon SWF limit on your account
has been reached. The failureCause property in the exception specifies the exact cause of failure to
schedule the activity.
SignalExternalWorkflowException
This exception is thrown if Amazon SWF fails to process a request by the workflow execution to signal
another workflow execution. This happens if the target workflow execution could not be found—that is,
the workflow execution you specified doesn't exist or is in closed state.
StartChildWorkflowFailedException
This exception is thrown if Amazon SWF fails to start a child workflow execution. This could happen due
to various reasons—for example, the type of child workflow specified was deprecated, or a Amazon SWF
limit on your account has been reached. The failureCause property in the exception specifies the
exact cause of failure to start the child workflow execution.
StartTimerFailedException
This exception is thrown if Amazon SWF fails to start a timer requested by the workflow execution. This
could happen if the specified timer ID is already in use, or an Amazon SWF limit on your account has
been reached. The failureCause property in the exception specifies the exact cause of failure.
TimerException
This is the base class for exceptions related to timers.
WorkflowException
This exception is used internally by the framework to report failures in workflow execution. You need to
deal with this exception only if you are using a workflow worker extensibility point.
com.amazonaws.services.simpleworkflow.flow
Contains the annotations used by the AWS Flow Framework for Java programming model.
com.amazonaws.services.simpleworkflow.flow.aspectj
Contains AWS Flow Framework for Java components required for features such as
@Asynchronous (p. 129) and @ExponentialRetry (p. 129).
com.amazonaws.services.simpleworkflow.flow.common
Contains core components, such as generic clients, that other features build on.
com.amazonaws.services.simpleworkflow.flow.interceptors
Contains classes that implement activity and workflow definitions for the annotation-based
programming model.
com.amazonaws.services.simpleworkflow.flow.spring
Contains helper classes, such as TestWorkflowClock, for unit testing workflow implementations.
com.amazonaws.services.simpleworkflow.flow.worker
Document History
The following table describes the important changes to the documentation since the last release of the
AWS Flow Framework for Java Developer Guide.
Update Cleaned up the code examples throughout this guide. June 5, 2017
Update Simplified and improved the organization and contents of this May 19, 2017
guide.
Update Simplified and improved the Making Changes to Decider Code: April 10,
Versioning and Feature Flags (p. 47) section. 2017
Update Added the new Best Practices (p. 47) section with new March 3,
guidance on making changes to decider code. 2017
New feature You can specify Lambda tasks in addition to traditional July 21, 2015
Activity tasks in your workflows. For more information, see
Implementing AWS Lambda Tasks (p. 76).
New feature Amazon SWF includes support for setting the task priority December 17,
on a task list, attempting to deliver the tasks with a higher 2014
priority before tasks with lower priority. For more information,
see Setting Task Priority (p. 88).
Update • Made updates and fixes, including updates of the setup June 28,
instructions (p. 3) for Eclipse 4.3 and AWS SDK for Java 2013
1.4.7.
• Added a new set of tutorials for building starter scenarios
New feature The initial release of the AWS Flow Framework for Java. February 27,
2012
AWS glossary
For the latest AWS terminology, see the AWS glossary in the AWS General Reference.