Activity and Workflow Clients - AWS Flow Framework for Java

Activity and Workflow Clients

Workflow and activity clients are generated by the framework based on the @Workflow and @Activities interfaces. Separate client interfaces are generated that contain methods and settings that make sense only on the client. If you are developing using Eclipse, this is done by the Amazon SWF Eclipse plug-in every time you save the file containing the appropriate interface. The generated code is placed in the generated sources directory in your project in the same package as the interface.

Note

Note that the default directory name used by Eclipse is .apt_generated. Eclipse doesn't show directories whose names start with a '.' in Package Explorer. Use a different directory name if you want to view the generated files in Project Explorer. In Eclipse, right-click the package in Package Explorer, and then choose Properties, Java Compiler, Annotation processing, and modify the Generate source directory setting.

Workflow Clients

The generated artifacts for the workflow contain three client-side interfaces and the classes that implement them. The generated clients include:

  • An asynchronous client intended to be consumed from within a workflow implementation that provides asynchronous methods to start workflow executions and send signals

  • An external client that can be used to start executions and send signals and retrieve workflow state from outside the scope of a workflow implementation

  • A self client that can be used to create continuous workflows

For example, the generated client interfaces for the example MyWorkflow interface are:

//Client for use from within a workflow public interface MyWorkflowClient extends WorkflowClient { Promise<Void> startMyWF( int a, String b); 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); } //External client for use outside workflows public interface MyWorkflowClientExternal extends WorkflowClientExternal { void startMyWF( int a, String b); void startMyWF( int a, String b, StartWorkflowOptions optionsOverride); void signal1( int a, int b, String c); MyWorkflowState getState(); } //self client for creating continuous workflows public interface MyWorkflowSelfClient extends WorkflowSelfClient { void startMyWF( int a, String b); 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:

    void startMyWF(int a, String b);

    Generated method:

    Promise<Void> startMyWF(int a, String b);

    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:

    void startMyWF(int a, String b);

    Generated method:

    Promise<void> startMyWF(int a, String b, Promise<?>...waitFor);

    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:

    void startMyWF(int a, String b);

    Generated method:

    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:

    void startMyWF(int a, String b);

    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:

    void startMyWF(int a, String b);

    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:

    void startMyWF(int a, String b);

    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:

void signal1(int a, int b, String c);

Generated method:

void signal1(int a, int b, String c);

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.

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.

The framework also generates factories for creating the strongly typed clients. The generated client factories for the example MyWorkflow interface are:

//Factory for clients to be used from within a workflow public interface MyWorkflowClientFactory extends WorkflowClientFactory<MyWorkflowClient> { } //Factory for clients to be used outside the scope of a workflow public interface MyWorkflowClientExternalFactory { GenericWorkflowClientExternal getGenericClient(); void setGenericClient(GenericWorkflowClientExternal genericClient); DataConverter getDataConverter(); void setDataConverter(DataConverter dataConverter); StartWorkflowOptions getStartWorkflowOptions(); void setStartWorkflowOptions(StartWorkflowOptions startWorkflowOptions); MyWorkflowClientExternal getClient(); MyWorkflowClientExternal getClient(String workflowId); MyWorkflowClientExternal getClient(WorkflowExecution workflowExecution); MyWorkflowClientExternal getClient( WorkflowExecution workflowExecution, GenericWorkflowClientExternal genericClient, DataConverter dataConverter, StartWorkflowOptions options); }

The WorkflowClientFactory base interface is:

public interface WorkflowClientFactory<T> { GenericWorkflowClient getGenericClient(); void setGenericClient(GenericWorkflowClient genericClient); DataConverter getDataConverter(); void setDataConverter(DataConverter dataConverter); StartWorkflowOptions getStartWorkflowOptions(); void setStartWorkflowOptions(StartWorkflowOptions startWorkflowOptions); T getClient(); T getClient(String workflowId); T getClient(WorkflowExecution execution); T getClient(WorkflowExecution execution, StartWorkflowOptions options); T getClient(WorkflowExecution execution, StartWorkflowOptions options, DataConverter dataConverter); }

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 and Child Workflow Executions 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:

public interface MyActivitiesClient extends ActivitiesClient { Promise<Integer> activity1(); Promise<Integer> activity1(Promise<?>... waitFor); Promise<Integer> activity1(ActivitySchedulingOptions optionsOverride, Promise<?>... waitFor); Promise<Void> activity2(int a); Promise<Void> activity2(int a, Promise<?>... waitFor); Promise<Void> activity2(int a, ActivitySchedulingOptions optionsOverride, Promise<?>... waitFor); Promise<Void> activity2(Promise<Integer> a); Promise<Void> activity2(Promise<Integer> a, Promise<?>... waitFor); Promise<Void> activity2(Promise<Integer> a, ActivitySchedulingOptions optionsOverride, Promise<?>... waitFor); }

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:

    void activity2(int foo);

    Generated method:

    Promise<Void> activity2(int foo);

    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:

    void activity2(int foo);

    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:

    void activity2(int foo);

    Generated method:

    Promise<Void> activity2(Promise<Integer> foo);

    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:

    void activity2(int foo);

    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:

    void activity2(int foo);

    Generated method:

    Promise< Void > activity2(int foo, Promise<?>...waitFor);

    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:

    void activity2(int foo);

    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:

void activity2(int foo);

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 ActivitySchedulingOptions object, configure it as desired, and pass it to the activity method. In the following example, we have specified the task list that should be used for the activity task. This will override the default registered task list for this invocation of the activity.

public class OrderProcessingWorkflowImpl implements OrderProcessingWorkflow { OrderProcessingActivitiesClient activitiesClient = new OrderProcessingActivitiesClientImpl(); // Workflow entry point @Override public void processOrder(Order order) { Promise<Void> paymentProcessed = activitiesClient.processPayment(order); ActivitySchedulingOptions schedulingOptions = new ActivitySchedulingOptions(); if (order.getLocation() == "Japan") { schedulingOptions.setTaskList("TasklistAsia"); } else { schedulingOptions.setTaskList("TasklistNorthAmerica"); } 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.

Signaling and Canceling Workflow Executions

The generated workflow client has methods corresponding to each signal that can be sent to the workflow. You can use them from within a workflow to send signals to other workflow executions. This provides a typed mechanism for sending signals. However, sometimes you may need to dynamically determine the signal name—for example, when the signal name is received in a message. You can use the dynamic workflow client to dynamically send signals to any workflow execution. Similarly, you can use the client to request cancellation of another workflow execution.

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); }