Basic Workflow Example

Continuing from Hello World, this topic provides an introduction to creating a basic workflow with the AWS Flow Framework for Ruby, and demonstrates the basic process of creating a multi-step workflow, setting options, and starting a workflow execution.

Note

The complete code for the example is presented in this topic, but you will also find it in the awslabs/aws-flow-ruby-samples repository on GitHub along with many other examples of programming with the AWS Flow Framework for Ruby.

Prerequisites#

This example assumes that you meet the following prerequisites:

  • Ruby and the AWS Flow Framework for Ruby (at least version 2.4.0) are installed as described in Setting Up.
  • Your AWS credentials are configured as described in Providing AWS Credentials.

Create your Application#

Just as with the Hello World example, we'll use the aws-flow-utils command to generate an application skeleton project.

To create the application project:

  • Open a command-line (terminal) window and type:

    aws-flow-utils -c local -n Booking
    

An Amazon SWF application configured to use the AWS Flow Framework for Ruby will be created for you in the local directory, called Booking. Here is the layout of the project that will be created:

Booking/
 |-- Gemfile
 |-- flow/
 |   |-- activities.rb
 |   `-- workflows.rb
 `-- worker.json

Create the Activities#

For this example, we'll define a couple of activities that emulate a travel-booking workflow: reserve_car, reserve_air, and send_confirmation.

To define the Booking activities:

  1. Open the flow/activities.rb file in your generated Booking project.

  2. Add the following code:

    # BookingActivity class defines a set of activities for the Booking sample.
    class BookingActivity
      extend AWS::Flow::Activities
    
      # The activity method is used to define activities. It accepts a list of names
      # of activities and a block specifying registration options for those
      # activities
      activity :reserve_car, :reserve_air, :send_confirmation do
        {
          version: "1.0",
        }
      end
    
      # This activity can be used to reserve a car for a given request_id
      def reserve_car(request_id)
        puts "Reserving car for Request ID: #{request_id}\n"
      end
    
      # This activity can be used to reserve a flight for a given request_id
      def reserve_air(request_id)
        puts "Reserving airline for Request ID: #{request_id}\n"
      end
    
      # This activity can be used to send a booking confirmation to the customer
      def send_confirmation(customer_id)
        puts "Sending notification to customer: #{customer_id}\n"
      end
    end
    

The activity class is based on Activities, which provides a common interface for defining and working with activity methods. In fact, in the activity defined in the Hello World tutorial, AWS Flow Framework for Ruby converted the HelloWorld class to an Activities-based class behind the scenes, before running it.

In this example, the activities are assigned options using the Activities#activity method, which takes a list of activity names and assigns each of them the set of ActivityRegistrationOptions defined in the block.

As with HelloWorld, activities are defined by methods that take a single input parameter, and each one performs a specific job in the workflow.

Create the Workflow#

The defined activities comprise a synchronization workflow pattern: the customer could either reserve a car, an airline ticket, or both. In any of these cases, a confirmation will be sent.

To implement the Booking workflow:

  1. Open the flow/workflows.rb file in your generated Booking project.

  2. Add the following code:

    require 'flow/activities'
    
    # BookingWorkflow class defines the workflows for the Booking sample
    class BookingWorkflow
      extend AWS::Flow::Workflows
    
      # Use the workflow method to define workflow entry point.
      workflow :make_booking do
        {
          version: "1.0",
          default_execution_start_to_close_timeout: 120
        }
      end
    
      # Create an activity client using the activity_client method to schedule
      # activities
      activity_client(:client) { { from_class: "BookingActivity" } }
    
      # This is the entry point for the workflow
      def make_booking options
    
        puts "Workflow has started\n" unless is_replaying?
        # This array will hold all futures that are created when asynchronous
        # activities are scheduled
        futures = []
    
        if options[:reserve_car]
          puts "Reserving a car for customer\n" unless is_replaying?
          # The activity client can be used to schedule activities
          # asynchronously by using the send_async method
          futures << client.send_async(:reserve_car, options[:request_id])
        end
        if options[:reserve_air]
          puts "Reserving air ticket\n" unless is_replaying?
          futures << client.send_async(:reserve_air, options[:customer_id])
        end
    
        puts "Waiting for reservation to complete\n" unless is_replaying?
        # wait_for_all is a flow construct that will wait on the array of
        # futures passed to it
        wait_for_all(futures)
    
        # After waiting on the reservation activities to complete, the workflow
        # will call the send_confirmation activity.
        client.send_confirmation(options[:customer_id])
    
        puts "Workflow has completed\n" unless is_replaying?
      end
    
      # Helper method to check if Flow is replaying the workflow. This is used to
      # avoid duplicate log messages
      def is_replaying?
        decision_context.workflow_clock.replaying
      end
    end
    

Workflow methods are defined in a class based on Workflows, and each workflow method takes an input parameter, just as the activity methods did. Similarly, you can use the Workflows#workflow method to set registration options for your workflows.

The workflow method, make_booking, uses the input parameter to choose whether or not to run the reserve_car and reserve_air activities. Each booking activity runs asynchronously using GenericClient#send_async, which returns immediately with a future that is filled once the activity completes. For more information, see Executing Tasks Asynchronously.

Activities and workflows will normally be replayed if an exception occurs, which sets the value of is_replaying in the workflow_clock attribute of the DecisionContext object held by the Workflows class. In this case, the workflow checks its value to avoid repeating its status messages with every replay.

Finally, the workflow calls Core#wait_for_all to wait for all of the running activities to complete before running the final activity, send_confirmation.

Start Workers#

For the workflow and activities to run, we need to start workers to listen for tasks and start running the appropriate methods in our implementation. As with HelloWorld, we'll start the worker using the aws-flow-ruby utility.

To write the runner configuration:

  1. Open the worker.json file in your Booking project.

  2. Add the following JSON configuration data to the file:

    {
      "domain":
        {
          "name": "Booking",
          "retention_in_days": 10
        },
      "workflow_workers": [
        {
          "number_of_workers": 5,
          "task_list": "booking_tasklist"
        }
      ],
      "activity_workers": [
        {
          "number_of_workers": 5,
          "number_of_forks_per_worker": 10,
          "task_list": "booking_activity_tasklist"
        }
      ]
    }
    

Now, start the workers so they can begin polling for tasks.

To start the workers:

  • Starting within the Booking directory, run the aws-flow-ruby utility:

    aws-flow-ruby -f worker.json
    

The runner will provide you with some output that describes the process IDs of your worker threads:

waiting on workers [10972, 10975, ...] to complete

Your worker is now polling for tasks, but to provide it with tasks to process, you need to start a workflow execution.

Start a Workflow Execution#

When executing a workflow instead of a single activity, use the start_workflow method instead of start.

To start the workflow execution:

  1. Create a new file (you can call it starter.rb) and add the following code:

    require 'aws/decider'
    
    input = {
      request_id: "1234567890",
      customer_id: "1234567890",
      reserve_car: true,
      reserve_air: true
    }
    
    opts = {
      domain: "Booking",
      version: "1.0"
    }
    
    AWS::Flow::start_workflow("BookingWorkflow.make_booking", input, opts)
    
  2. Open a command-line window and run your script using Ruby:

    ruby starter.rb
    

The call to start takes:

  • the fully-qualified name (class.method) of your workflow method
  • input data for the workflow
  • a block of StartWorkflowOptions

When you run the script, the make_booking workflow and its associated activities will begin running in the background on the previously-started workers.

You can view your workflow execution the same way as for HelloWorld, just be sure to select the Booking domain in the SWF console.

Next Steps#

Use the following topics and resources to learn more about the framework: