Class Activity


  • public final class Activity
    extends java.lang.Object
    An activity is the implementation of a particular task in the business logic.

    Activity Interface

    Activities are defined as methods of a plain Java interface. Each method defines a single activity type. A single workflow can use more than one activity interface and call more than one activity method from the same interface. The only requirement is that activity method arguments and return values are serializable to a byte array using the provided DataConverter implementation. The default implementation uses JSON serializer, but an alternative implementation can be easily configured.

    Example of an interface that defines four activities:

    
     public interface FileProcessingActivities {
    
         void upload(String bucketName, String localName, String targetName);
    
         String download(String bucketName, String remoteName);
    
         @ActivityMethod(scheduleToCloseTimeoutSeconds = 2)
         String processFile(String localName);
    
         void deleteLocalFile(String fileName);
     }
    
     
    An optional @ActivityMethod annotation can be used to specify activity options like timeouts or a task list. Required options that are not specified through the annotation must be specified at run time.

    Activity Implementation

    Activity implementation is an implementation of an activity interface. A single instance of the activity's implementation is shared across multiple simultaneous activity invocations. Therefore, the activity implementation code must be thread safe.

    The values passed to activities through invocation parameters or returned through a result value are recorded in the execution history. The entire execution history is transferred from the Cadence service to workflow workers when a workflow state needs to recover. A large execution history can thus adversely impact the performance of your workflow. Therefore, be mindful of the amount of data you transfer via activity invocation parameters or return values. Other than that, no additional limitations exist on activity implementations.

    
     public class FileProcessingActivitiesImpl implements FileProcessingActivities {
    
         private final AmazonS3 s3Client;
    
         private final String localDirectory;
    
         void upload(String bucketName, String localName, String targetName) {
             File f = new File(localName);
             s3Client.putObject(bucket, remoteName, f);
         }
    
         String download(String bucketName, String remoteName, String localName) {
             // Implementation omitted for brevity.
             return downloadFileFromS3(bucketName, remoteName, localDirectory + localName);
         }
    
         String processFile(String localName) {
             // Implementation omitted for brevity.
             return compressFile(localName);
         }
    
         void deleteLocalFile(String fileName) {
             File f = new File(localDirectory + fileName);
             f.delete();
         }
     }
     

    Accessing Activity Info

    The Activity class provides static getters to access information about the workflow that invoked it. Note that this information is stored in a thread-local variable. Therefore, calls to Activity accessors succeed only in the thread that invoked the activity function.

    
     public class FileProcessingActivitiesImpl implements FileProcessingActivities {
    
          @Override
          public String download(String bucketName, String remoteName, String localName) {
             log.info("domain=" +  Activity.getDomain());
             WorkflowExecution execution = Activity.getWorkflowExecution();
             log.info("workflowId=" + execution.getWorkflowId());
             log.info("runId=" + execution.getRunId());
             ActivityTask activityTask = Activity.getTask();
             log.info("activityId=" + activityTask.getActivityId());
             log.info("activityTimeout=" + activityTask.getStartToCloseTimeoutSeconds());
             return downloadFileFromS3(bucketName, remoteName, localDirectory + localName);
          }
          ...
      }
     

    Asynchronous Activity Completion

    Sometimes an activity lifecycle goes beyond a synchronous method invocation. For example, a request can be put in a queue and later a reply comes and is picked up by a different worker process. The whole request-reply interaction can be modeled as a single Cadence activity.

    To indicate that an activity should not be completed upon its method return, call doNotCompleteOnReturn() from the original activity thread. Then later, when replies come, complete the activity using ActivityCompletionClient. To correlate activity invocation with completion use either TaskToken or workflow and activity IDs.

    
     public class FileProcessingActivitiesImpl implements FileProcessingActivities {
    
          public String download(String bucketName, String remoteName, String localName) {
              byte[] taskToken = Activity.getTaskToken(); // Used to correlate reply
              asyncDownloadFileFromS3(taskToken, bucketName, remoteName, localDirectory + localName);
              Activity.doNotCompleteOnReturn();
              return "ignored"; // Return value is ignored when doNotCompleteOnReturn was called.
          }
          ...
     }
     
    When the download is complete, the download service potentially calls back from a different process:
    
         public  void completeActivity(byte[] taskToken, R result) {
             completionClient.complete(taskToken, result);
         }
    
         public void failActivity(byte[] taskToken, Exception failure) {
             completionClient.completeExceptionally(taskToken, failure);
         }
     

    Caution: since using this sometimes implies "long" timeouts, activity-worker losses prior to recording the getTaskToken() in an external system (or prior to another thread calling it) may not be noticed until the "long" timeout occurs. This can be resolved by having another system call ActivityCompletionClient.heartbeat(byte[], Object) while that external action is running, but there is currently no way to mitigate this issue without these heartbeats. For in-process-only async completion, relying on heartbeating is safe and reliable because these heartbeats should occur as long as the process / background thread is still running.

    If you cannot heartbeat and cannot tolerate this kind of delayed-activity-loss detection, consider emulating a long activity via a signal channel instead: you can start a short-lived activity and wait for a "saved to external system" signal, retrying as necessary, and then wait for an "external system finished" signal containing the final result.

    Activity Heartbeating

    Some activities are long running. To react to their crashes quickly, use a heartbeat mechanism. Use the heartbeat(Object) function to let the Cadence service know that the activity is still alive. You can piggyback `details` on an activity heartbeat. If an activity times out, the last value of `details` is included in the ActivityTimeoutException delivered to a workflow. Then the workflow can pass the details to the next activity invocation. This acts as a periodic checkpointing mechanism of an activity's progress.

    
     public class FileProcessingActivitiesImpl implements FileProcessingActivities {
    
          @Override
          public String download(String bucketName, String remoteName, String localName) {
             InputStream inputStream = openInputStream(file);
             try {
                 byte[] bytes = new byte[MAX_BUFFER_SIZE];
                 while ((read = inputStream.read(bytes)) != -1) {
                     totalRead += read;
                     f.write(bytes, 0, read);
                     // Let the service know about the download progress.
                     Activity.heartbeat(totalRead);
                 }
             }finally{
                 inputStream.close();
             }
          }
          ...
     }
     
    See Also:
    Worker, Workflow, WorkflowClient
    • Method Summary

      All Methods Static Methods Concrete Methods 
      Modifier and Type Method Description
      static void doNotCompleteOnReturn()
      If this method is called during an activity execution then activity will not complete when its method returns.
      static java.lang.String getDomain()  
      static <V> java.util.Optional<V> getHeartbeatDetails​(java.lang.Class<V> detailsClass)
      Extracts heartbeat details from the last failed attempt.
      static <V> java.util.Optional<V> getHeartbeatDetails​(java.lang.Class<V> detailsClass, java.lang.reflect.Type detailsType)
      static IWorkflowService getService()  
      static ActivityTask getTask()  
      static byte[] getTaskToken()  
      static WorkflowExecution getWorkflowExecution()  
      static <V> void heartbeat​(V details)
      Use to notify Cadence service that activity execution is alive.
      static java.lang.RuntimeException wrap​(java.lang.Exception e)
      If there is a need to return a checked exception from an activity do not add the exception to a method signature but rethrow it using this method.
      • Methods inherited from class java.lang.Object

        clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    • Method Detail

      • doNotCompleteOnReturn

        public static void doNotCompleteOnReturn()
        If this method is called during an activity execution then activity will not complete when its method returns. It is expected to be completed asynchronously using ActivityCompletionClient.

        Caution: since using this sometimes implies "long" timeouts, activity-worker losses prior to recording the getTaskToken() in an external system (or prior to another thread calling it) may not be noticed until the "long" timeout occurs. This can be resolved by having another system call ActivityCompletionClient.heartbeat(byte[], Object) while that external action is running, but there is currently no way to mitigate this issue without these heartbeats. For in-process-only async completion, relying on heartbeating is safe and reliable because these heartbeats should occur as long as the process / background thread is still running.

        If you cannot heartbeat and cannot tolerate this kind of delayed-activity-loss detection, consider emulating a long activity via a signal channel instead: you can start a short-lived activity and wait for a "saved to external system" signal, retrying as necessary, and then wait for an "external system finished" signal containing the final result.

      • getTaskToken

        public static byte[] getTaskToken()
        Returns:
        task token that is required to report task completion when manual activity completion is used.
      • getWorkflowExecution

        public static WorkflowExecution getWorkflowExecution()
        Returns:
        workfow execution that requested the activity execution
      • getTask

        public static ActivityTask getTask()
        Returns:
        task that caused activity execution
      • getHeartbeatDetails

        public static <V> java.util.Optional<V> getHeartbeatDetails​(java.lang.Class<V> detailsClass)
        Extracts heartbeat details from the last failed attempt. This is used in combination with retry options. An activity could be scheduled with an optional RetryOptions on ActivityOptions. If an activity failed then the server would attempt to dispatch another activity task to retry according to the retry options. If there was heartbeat details reported by the activity from the failed attempt, the details would be delivered along with the activity task for the retry attempt. The activity could extract the details by getHeartbeatDetails(Class)() and resume from the progress.
        Parameters:
        detailsClass - type of the heartbeat details
      • getHeartbeatDetails

        public static <V> java.util.Optional<V> getHeartbeatDetails​(java.lang.Class<V> detailsClass,
                                                                    java.lang.reflect.Type detailsType)
        Similar to getHeartbeatDetails(Class). Use when details is of a generic type.
        Parameters:
        detailsClass - type of the heartbeat details
        detailsType - type including generic information of the heartbeat details.
      • getService

        public static IWorkflowService getService()
        Returns:
        an instance of the Simple Workflow Java client that is the same used by the invoked activity worker. It can be useful if activity wants to use WorkflowClient for some operations like sending signal to its parent workflow. @TODO getWorkflowClient method to hide the service.
      • getDomain

        public static java.lang.String getDomain()
      • wrap

        public static java.lang.RuntimeException wrap​(java.lang.Exception e)
        If there is a need to return a checked exception from an activity do not add the exception to a method signature but rethrow it using this method. The library code will unwrap it automatically when propagating exception to the caller. There is no need to wrap unchecked exceptions, but it is safe to call this method on them.

        The reason for such design is that returning originally thrown exception from a remote call (which child workflow and activity invocations are ) would not allow adding context information about a failure, like activity and child workflow id. So stubs always throw a subclass of ActivityException from calls to an activity and subclass of ChildWorkflowException from calls to a child workflow. The original exception is attached as a cause to these wrapper exceptions. So as exceptions are always wrapped adding checked ones to method signature causes more pain than benefit.

        Throws original exception if e is RuntimeException or Error. Never returns. But return type is not empty to be able to use it as:

         try {
             return someCall();
         } catch (Exception e) {
             throw CheckedExceptionWrapper.throwWrapped(e);
         }
         
        If throwWrapped returned void it wouldn't be possible to write throw CheckedExceptionWrapper.throwWrapped and compiler would complain about missing return.
        Returns:
        never returns as always throws.