Menu
AWS SDK for Java
Developer Guide

Building a Simple Amazon SWF Application

This topic will introduce you to programming Amazon SWF applications with the AWS SDK for Java, while presenting a few important concepts along the way.

About the example

The example project will create a workflow with a single activity that accepts workflow data passed through the AWS cloud (In the tradition of HelloWorld, it'll be the name of someone to greet) and then prints a greeting in response.

While this seems very simple on the surface, Amazon SWF applications consist of a number of parts working together:

  • A domain, used as a logical container for your workflow execution data.

  • One or more workflows which represent code components that define logical order of execution of your workflow's activities and child workflows.

  • A workflow worker, also known as a decider, that polls for decision tasks and schedules activities or child workflows in response.

  • One or more activities, each of which represents a unit of work in the workflow.

  • An activity worker that polls for activity tasks and runs activity methods in response.

  • One or more task lists, which are queues maintained by Amazon SWF used to issue requests to the workflow and activity workers. Tasks on a task list meant for workflow workers are called decision tasks. Those meant for activity workers are called activity tasks.

  • A workflow starter that begins your workflow execution.

Behind the scenes, Amazon SWF orchestrates the operation of these components, coordinating their flow from the AWS cloud, passing data between them, handling timeouts and heartbeat notifications, and logging workflow execution history.

Prerequisites

Development environment

The development environment used in this tutorial consists of:

  • The AWS SDK for Java.

  • Apache Maven (3.3.1).

  • JDK 1.7 or later. This tutorial was developed and tested using JDK 1.8.0.

  • A good Java text editor (your choice).

Note

If you use a different build system than Maven, you can still create a project using the appropriate steps for your environment and use the the concepts provided here to follow along. More information about configuring and using the AWS SDK for Java with various build systems is provided in Getting Started.

Likewise, but with more effort, the steps shown here can be implemented using any of the AWS SDKs with support for Amazon SWF.

All of the necessary external dependencies are included with the AWS SDK for Java, so there's nothing additional to download.

AWS access

To access Amazon Web Services (AWS), you must have an active AWS account. For information about signing up for AWS and creating an IAM user (recommended over using root account credentials), see Sign Up for AWS and Create an IAM User.

This tutorial uses the terminal (command-line) to run the example code, and expects that you have your AWS credentials and configuration accessible to the SDK. The easiest way to do this is to use the environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. You should also set the AWS_REGION to the region you want to use.

For example, on Linux, macOS, or Unix, set the variables this way:

Copy
export AWS_ACCESS_KEY_ID=your_access_key_id export AWS_SECRET_ACCESS_KEY=your_secret_access_key export AWS_REGION=us-east-1

To set these variables on Windows, use these commands:

Copy
set AWS_ACCESS_KEY_ID=your_access_key_id set AWS_SECRET_ACCESS_KEY=your_secret_access_key set AWS_REGION=us-east-1

Important

Substitute your own access key, secret access key and region information for the example values shown here.

For more information about configuring your credentials for the SDK, see Set up AWS Credentials and Region for Development.

Create a SWF project

  1. Start a new project with Maven:

    Copy
    mvn archetype:generate -DartifactId=helloswf \ -DgroupId=example.swf.hello -DinteractiveMode=false

    This will create a new project with a standard maven project structure:

    Copy
    helloswf ├── pom.xml └── src ├── main │   └── java │   └── example │   └── swf │   └── hello │   └── App.java └── test └── ...

    You can ignore or delete the test directory and all it contains, we won't be using it for this tutorial. You can also delete App.java, since we'll be replacing it with new classes.

  2. Edit the project's pom.xml file and add the aws-java-sdk-simpleworkflow module by adding a dependency for it within the <dependencies> block.

    Copy
    ependencies> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-simpleworkflow</artifactId> <version>1.11.78</version> </dependency> dependencies>
  3. Make sure that Maven builds your project with JDK 1.7+ support. Add the following to your project (either before or after the <dependencies> block) in pom.xml:

    Copy
    <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build>

Code the project

The example project will consist of four separate applications, which we'll visit one by one:

  • HelloTypes.java—contains the project's domain, activity and workflow type data, shared with the other components. It also handles registering these types with SWF.

  • ActivityWorker.java—contains the activity worker, which polls for activity tasks and runs activities in response.

  • WorkflowWorker.java—contains the workflow worker (decider), which polls for decision tasks and schedules new activities.

  • WorkflowStarter.java—contains the workflow starter, which starts a new workflow execution, which will cause SWF to start generating decision and workflow tasks for your workers to consume.

Common steps for all source files

All of the files that you create to house your Java classes will have a few things in common. In the interest of time, these steps will be implied every time you add a new file to the project:

  1. Create the file in the in the project's src/main/java/example/swf/hello/ directory.

  2. Add a package declaration to the beginning of each file to declare its namespace. The example project uses:

    Copy
    package aws.example.helloswf;
  3. Add import declarations for the AmazonSimpleWorkflowClient class and for multiple classes in the com.amazonaws.services.simpleworkflow.model namespace. To simplify things, we'll use:

    Copy
    import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow; import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClientBuilder; import com.amazonaws.services.simpleworkflow.model.*;

Register a domain, workflow and activity types

We'll begin by creating a new executeable class, HelloTypes.java. This file will contain shared data that different parts of your workflow will need to know about, such as the name and version of your activity and workflow types, the domain name and the task list name.

  1. Open your text editor and create the file HelloTypes.java, adding a package declaration and imports according to the common steps.

  2. Declare the HelloTypes class and provide it with values to use for your registered activity and workflow types:

    Copy
    public class HelloTypes { public static final String DOMAIN = "HelloDomain"; public static final String TASKLIST = "HelloTasklist"; public static final String WORKFLOW = "HelloWorkflow"; public static final String WORKFLOW_VERSION = "1.0"; public static final String ACTIVITY = "HelloActivity"; public static final String ACTIVITY_VERSION = "1.0"; }

    These values will be used throughout the code.

  3. After the String declarations, create an instance of the AmazonSimpleWorkflowClient class. This is the basic interface to the Amazon SWF methods provided by the AWS SDK for Java.

    Copy
    private static final AmazonSimpleWorkflow swf = AmazonSimpleWorkflowClientBuilder.defaultClient();
  4. Add a new function to register a SWF domain. A domain is a logical container for a number of related SWF activity and workflow types. SWF components can only communicate with each other if they exist within the same domain.

    Copy
    public static void registerDomain() { try { System.out.println("** Registering the domain '" + DOMAIN + "'."); swf.registerDomain(new RegisterDomainRequest() .withName(DOMAIN) .withWorkflowExecutionRetentionPeriodInDays("1")); } catch (DomainAlreadyExistsException e) { System.out.println("** Domain already exists!"); } }

    When you register a domain, you provide it with a name (any set of 1 – 256 characters excluding :, /, |, control characters or the literal string 'arn') and a retention period, which is the number of days that Amazon SWF will keep your workflow's execution history data after a workflow execution has completed. The maximum workflow execution retention period is 90 days. See RegisterDomainRequest for more information.

    If a domain with that name already exists, a DomainAlreadyExistsException is raised. Because we're unconcerned if the domain has already been created, we can ignore the exception.

    Note

    This code demonstrates a common pattern when working with AWS SDK for Java methods, data for the method is supplied by a class in the simpleworkflow.model namespace, which you instantiate and populate using the chainable .with* methods.

  5. Add a function to register a new activity type. An activity represents a unit of work in your workflow.

    Copy
    public static void registerActivityType() { try { System.out.println("** Registering the activity type '" + ACTIVITY + "-" + ACTIVITY_VERSION + "'."); swf.registerActivityType(new RegisterActivityTypeRequest() .withDomain(DOMAIN) .withName(ACTIVITY) .withVersion(ACTIVITY_VERSION) .withDefaultTaskList(new TaskList().withName(TASKLIST)) .withDefaultTaskScheduleToStartTimeout("30") .withDefaultTaskStartToCloseTimeout("600") .withDefaultTaskScheduleToCloseTimeout("630") .withDefaultTaskHeartbeatTimeout("10")); } catch (TypeAlreadyExistsException e) { System.out.println("** Activity type already exists!"); } }

    An activity type is identified by a name and a version, which are used to uniquely identify the activity from any others in the domain that it's registered in. Activities also contain a number of optional parameters, such as the default task-list used to receive tasks and data from SWF and a number of different timeouts that you can use to place constraints upon how long different parts of the activity execution can take. See RegisterActivityTypeRequest for more information.

    Note

    All timeout values are specified in seconds. See Amazon SWF Timeout Types for a full description of how timeouts affect your workflow executions.

    If the activity type that you're trying to register already exists, an TypeAlreadyExistsException is raised.

  6. Add a function to register a new workflow type. A workflow, also known as a decider represents the logic of your workflow's execution.

    Copy
    public static void registerWorkflowType() { try { System.out.println("** Registering the workflow type '" + WORKFLOW + "-" + WORKFLOW_VERSION + "'."); swf.registerWorkflowType(new RegisterWorkflowTypeRequest() .withDomain(DOMAIN) .withName(WORKFLOW) .withVersion(WORKFLOW_VERSION) .withDefaultChildPolicy(ChildPolicy.TERMINATE) .withDefaultTaskList(new TaskList().withName(TASKLIST)) .withDefaultTaskStartToCloseTimeout("30")); } catch (TypeAlreadyExistsException e) { System.out.println("** Workflow type already exists!"); } }

    Similar to activity types, workflow types are identified by a name and a version and also have configurable timeouts. See RegisterWorkflowTypeRequest for more information.

    If the workflow type that you're trying to register already exists, an TypeAlreadyExistsException is raised.

  7. Finally, make the class executable by providing it a main method, which will register the domain, the activity type, and the workflow type in turn:

    Copy
    public static void main(String[] args) { registerDomain(); registerWorkflowType(); registerActivityType(); }

You can build and run the application now to run the registration script, or continue with coding the activity and workflow workers. Once the domain, workflow and activity have been registered, you won't need to run this again—these types persist until you deprecate them yourself.

Implement the activity worker

An activity is the basic unit of work in a workflow. A workflow provides the logic, scheduling activities to be run (or other actions to be taken) in response to decision tasks. A typical workflow usually consists of a number of activities that can run synchronously, asynchronously, or a combination of both.

The activity worker is the bit of code that polls for activity tasks that are generated by Amazon SWF in response to workflow decisions. When it receives an activity task, it runs the corresponding activity and returns a success/failure response back to the workflow.

We'll implement a simple activity worker that drives a single activity.

  1. Open your text editor and create the file ActivityWorker.java, adding a package declaration and imports according to the common steps.

  2. Add the ActivityWorker class to the file, and give it a data member to hold a SWF client that we'll use to interact with Amazon SWF:

    Copy
    public class ActivityWorker { private static final AmazonSimpleWorkflow swf = AmazonSimpleWorkflowClientBuilder.defaultClient(); }
  3. Add the method that we'll use as an activity:

    Copy
    private static String sayHello(String input) throws Throwable { return "Hello, " + input + "!"; }

    The activity simply takes a string, combines it into a greeting and returns the result. Although there is little chance that this activity will raise an exception, it's a good idea to design activities that can raise an error if something goes wrong.

  4. Add a main method that we'll use as the activity task polling method. We'll start it by adding some code to poll the task list for activity tasks:

    Copy
    public static void main(String[] args) { while (true) { System.out.println("Polling for an activity task from the tasklist '" + HelloTypes.TASKLIST + "' in the domain '" + HelloTypes.DOMAIN + "'."); ActivityTask task = swf.pollForActivityTask( new PollForActivityTaskRequest() .withDomain(HelloTypes.DOMAIN) .withTaskList( new TaskList().withName(HelloTypes.TASKLIST))); String task_token = task.getTaskToken(); }

    The activity receives tasks from Amazon SWF by calling the SWF client's pollForActivityTask method, specifying the domain and task list to use in the passed-in PollForActivityTaskRequest.

    Once a task is received, we retrieve a unique identifier for it by calling the task's getTaskToken method.

  5. Next, write some code to process the tasks that come in. Add the following to your main method, right after the code that polls for the task and retrieves its task token.

    Copy
    if (task_token != null) { String result = null; Throwable error = null; try { System.out.println("Executing the activity task with input '" + task.getInput() + "'."); result = sayHello(task.getInput()); } catch (Throwable th) { error = th; } if (error == null) { System.out.println("The activity task succeeded with result '" + result + "'."); swf.respondActivityTaskCompleted( new RespondActivityTaskCompletedRequest() .withTaskToken(task_token) .withResult(result)); } else { System.out.println("The activity task failed with the error '" + error.getClass().getSimpleName() + "'."); swf.respondActivityTaskFailed( new RespondActivityTaskFailedRequest() .withTaskToken(task_token) .withReason(error.getClass().getSimpleName()) .withDetails(error.getMessage())); } }

    If the task token is not null, then we can start running the activity method (sayHello), providing it with the input data that was sent with the task.

    If the task succeeded (no error was generated), then the worker responds to SWF by calling the SWF client's respondActivityTaskCompleted method with a RespondActivityTaskCompletedRequest object containing the task token and the activity's result data.

    On the other hand, if the task failed, then we respond by calling the respondActivityTaskFailed method with a RespondActivityTaskFailedRequest object, passing it the task token and information about the error.

Note

This activity will not shut down gracefully if killed. Although it is beyond the scope of this tutorial, an alternative implementation of this activity worker is provided in the accompanying topic, Shutting Down Activity and Workflow Workers Gracefully.

Implement the workflow worker

Your workflow logic resides in a piece of code known as a workflow worker. The workflow worker polls for decision tasks that are sent by Amazon SWF in the domain, and on the default tasklist, that the workflow type was registered with.

When the workflow worker receives a task, it makes some sort of decision (usually whether to schedule a new activity or not) and takes an appropriate action (such as scheduling the activity).

  1. Open your text editor and create the file WorkflowWorker.java, adding a package declaration and imports according to the common steps.

  2. Add a few additional imports to the file:

    Copy
    import java.util.ArrayList; import java.util.List; import java.util.UUID;
  3. Declare the WorkflowWorker class, and create an instance of the AmazonSimpleWorkflowClient class used to access SWF methods.

    Copy
    public class WorkflowWorker { private static final AmazonSimpleWorkflow swf = AmazonSimpleWorkflowClientBuilder.defaultClient(); }
  4. Add the main method. The method loops continuously, polling for decision tasks using the SWF client's pollForDecisionTask method. The PollForDecisionTaskRequest provides the details.

    Copy
    public static void main(String[] args) { PollForDecisionTaskRequest task_request = new PollForDecisionTaskRequest() .withDomain(HelloTypes.DOMAIN) .withTaskList(new TaskList().withName(HelloTypes.TASKLIST)); while (true) { System.out.println( "Polling for a decision task from the tasklist '" + HelloTypes.TASKLIST + "' in the domain '" + HelloTypes.DOMAIN + "'."); DecisionTask task = swf.pollForDecisionTask(task_request); String taskToken = task.getTaskToken(); if (taskToken != null) { try { executeDecisionTask(taskToken, task.getEvents()); } catch (Throwable th) { th.printStackTrace(); } } } }

    Once a task is received, we call its getTaskToken method, which returns a string that can be used to identify the task. If the returned token is not null, then we process it further in the executeDecisionTask method, passing it the task token and the list of HistoryEvent objects sent with the task.

  5. Add the executeDecisionTask method, taking the task token (a String) and the HistoryEvent list.

    Copy
    private static void executeDecisionTask(String taskToken, List<HistoryEvent> events) throws Throwable { List<Decision> decisions = new ArrayList<Decision>(); String workflow_input = null; int scheduled_activities = 0; int open_activities = 0; boolean activity_completed = false; String result = null; }

    We also set up some data members to keep track of things such as:

    • A list of Decision objects used to report the results of processing the task.

    • A String to hold workflow input provided by the "WorkflowExecutionStarted" event

    • a count of the scheduled and open (running) activities to avoid scheduling the same activity when it has already been scheduled or is currently running.

    • a boolean to indicate that the activity has completed.

    • A String to hold the activity results, for returning it as our workflow result.

  6. Next, add some code to executeDecisionTask to process the HistoryEvent objects that were sent with the task, based on the event type reported by the getEventType method.

    Copy
    System.out.println("Executing the decision task for the history events: ["); for (HistoryEvent event : events) { System.out.println(" " + event); switch(event.getEventType()) { case "WorkflowExecutionStarted": workflow_input = event.getWorkflowExecutionStartedEventAttributes() .getInput(); break; case "ActivityTaskScheduled": scheduled_activities++; break; case "ScheduleActivityTaskFailed": scheduled_activities--; break; case "ActivityTaskStarted": scheduled_activities--; open_activities++; break; case "ActivityTaskCompleted": open_activities--; activity_completed = true; result = event.getActivityTaskCompletedEventAttributes() .getResult(); break; case "ActivityTaskFailed": open_activities--; break; case "ActivityTaskTimedOut": open_activities--; break; } } System.out.println("]");

    For the purposes of our workflow, we are most interested in:

    • the "WorkflowExecutionStarted" event, which indicates that the workflow execution has started (typically meaning that you should run the first activity in the workflow), and that provides the initial input provided to the workflow. In this case, it's the name portion of our greeting, so it's saved in a String for use when scheduling the activity to run.

    • the "ActivityTaskCompleted" event, which is sent once the scheduled activity is complete. The event data also includes the return value of the completed activity. Since we have only one activity, we'll use that value as the result of the entire workflow.

    The other event types can be used if your workflow requires them. See the HistoryEvent class description for information about each event type.

    Note

    Strings in switch statements were introduced in Java 7. If you're using an earlier version of Java, you can make use of the EventType class to convert the String returned by history_event.getType() to an enum value and then back to a String if necessary:

    Copy
    EventType et = EventType.fromValue(event.getEventType());
  7. After the switch statement, add more code to respond with an appropriate decision based on the task that was received.

    Copy
    if (activity_completed) { decisions.add( new Decision() .withDecisionType(DecisionType.CompleteWorkflowExecution) .withCompleteWorkflowExecutionDecisionAttributes( new CompleteWorkflowExecutionDecisionAttributes() .withResult(result))); } else { if (open_activities == 0 && scheduled_activities == 0) { ScheduleActivityTaskDecisionAttributes attrs = new ScheduleActivityTaskDecisionAttributes() .withActivityType(new ActivityType() .withName(HelloTypes.ACTIVITY) .withVersion(HelloTypes.ACTIVITY_VERSION)) .withActivityId(UUID.randomUUID().toString()) .withInput(workflow_input); decisions.add( new Decision() .withDecisionType(DecisionType.ScheduleActivityTask) .withScheduleActivityTaskDecisionAttributes(attrs)); } else { // an instance of HelloActivity is already scheduled or running. Do nothing, another // task will be scheduled once the activity completes, fails or times out } } System.out.println("Exiting the decision task with the decisions " + decisions);
    • If the activity hasn't been scheduled yet, we respond with a ScheduleActivityTask decision, which provides information in a ScheduleActivityTaskDecisionAttributes structure about the activity that Amazon SWF should schedule next, also including any data that Amazon SWF should send to the activity.

    • If the activity was completed, then we consider the entire workflow completed and respond with a CompletedWorkflowExecution decision, filling in a CompleteWorkflowExecutionDecisionAttributes structure to provide details about the completed workflow. In this case, we return the result of the activity.

    In either case, the decision information is added to the Decision list that was declared at the top of the method.

  8. Complete the decision task by returning the list of Decision objects collected while processing the task. Add this code at the end of the executeDecisionTask method that we've been writing:

    Copy
    swf.respondDecisionTaskCompleted( new RespondDecisionTaskCompletedRequest() .withTaskToken(taskToken) .withDecisions(decisions));

    The SWF client's respondDecisionTaskCompleted method takes the task token that identifies the task as well as the list of Decision objects.

Implement the workflow starter

Finally, we'll write some code to start the workflow execution.

  1. Open your text editor and create the file WorkflowStarter.java, adding a package declaration and imports according to the common steps.

  2. Add the WorkflowStarter class:

    Copy
    public class WorkflowStarter { private static final AmazonSimpleWorkflow swf = AmazonSimpleWorkflowClientBuilder.defaultClient(); public static final String WORKFLOW_EXECUTION = "HelloWorldWorkflowExecution"; public static void main(String[] args) { String workflow_input = "Amazon SWF"; if (args.length > 0) { workflow_input = args[0]; } System.out.println("Starting the workflow execution '" + WORKFLOW_EXECUTION + "' with input '" + workflow_input + "'."); WorkflowType wf_type = new WorkflowType() .withName(HelloTypes.WORKFLOW) .withVersion(HelloTypes.WORKFLOW_VERSION); Run run = swf.startWorkflowExecution(new StartWorkflowExecutionRequest() .withDomain(HelloTypes.DOMAIN) .withWorkflowType(wf_type) .withWorkflowId(WORKFLOW_EXECUTION) .withInput(workflow_input) .withExecutionStartToCloseTimeout("90")); System.out.println("Workflow execution started with the run id '" + run.getRunId() + "'."); } }

    The WorkflowStarter class consists of a single method, main, which takes an optional argument passed on the command-line as input data for the workflow.

    The SWF client method, startWorkflowExecution, takes a StartWorkflowExecutionRequest object as input. Here, in addition to specifying the domain and workflow type to run, we provide it with:

    • a human-readable workflow execution name

    • workflow input data (provided on the command-line in our example)

    • a timeout value that represents how long, in seconds, that the entire workflow should take to run.

    The Run object that startWorkflowExecution returns provides a run ID, a value that can be used to identify this particular workflow execution in Amazon SWF's history of your workflow executions.

    Note

    The run ID is generated by Amazon SWF, and is not the same as the workflow execution name that you pass in when starting the workflow execution.

Build the example

To build the example project with Maven, go to the helloswf directory and type:

Copy
mvn package

The resulting helloswf-1.0.jar will be generated in the target directory.

Run the example

The example consists of four separate executable classes, which are run independently of each other.

Note

If you are using a Linux, macOS, or Unix system, you can run all of them, one after another, in a single terminal window. If you are running Windows, you should open two additional command-line instances and navigate to the helloswf directory in each.

Setting the Java classpath

Although Maven has handled the dependencies for you, to run the example, you'll need to provide the AWS SDK library and its dependencies on your Java classpath. You can either set the CLASSPATH environment variable to the location of your AWS SDK libraries and the third-party/lib directory in the SDK, which includes necessary dependencies:

Copy
export CLASSPATH='target/helloswf-1.0.jar:/path/to/sdk/lib/*:/path/to/sdk/third-party/lib/*' java example.swf.hello.HelloTypes

or use the java command's -cp option to set the classpath while running each applications.

Copy
java -cp target/helloswf-1.0.jar:/path/to/sdk/lib/*:/path/to/sdk/third-party/lib/* \ example.swf.hello.HelloTypes

The style that you use is up to you. If you had no trouble building the code, buth then try to run the examples and get a series of "NoClassDefFound" errors, it is likely because the classpath is set incorrectly.

Register the domain, workflow and activity types

Before running your workers and the workflow starter, you'll need to register the domain and your workflow and activity types. The code to do this was implemented in Register a domain, workflow and activity types.

After building, and if you've set the CLASSPATH, you can run the registration code by executing the command:

Copy
java aws.example.helloswf.HelloTypes

Start the activity and workflow workers

Now that the types have been registered, you can start the activity and workflow workers. These will continue to run and poll for tasks until they are killed, so you should either run them in separate terminal windows, or, if you're running on Linux, macOS, or Unix you can use the & operator to cause each of them to spawn a separate process when run.

Copy
java aws.example.helloswf.ActivityWorker & java aws.example.helloswf.WorkflowWorker &

If you're running these commands in separate windows, omit the final & operator from each line.

Start the workflow execution

Now that your activity and workflow workers are polling, you can start the workflow execution. This process will run until the workflow returns a completed status. You should run it in a new terminal window (unless you ran your workers as new spawned processes by using the & operator).

Copy
java aws.example.helloswf.WorkflowStarter

Note

If you want to provide your own input data, which will be passed first to the workflow and then to the activity, add it to the command-line. For example:

Copy
java aws.example.helloswf.WorkflowStarter "Thelonious"

Once you begin the workflow execution, you should start seeing output delivered by both workers and by the workflow execution itself. When the workflow finally completes, its output will be printed to the screen.

Complete source for this example

You can browse the complete source for this example on Github in the aws-java-developer-guide repository.

For more information