
Build a Temporal Application from scratch in Ruby
- Build the application
- Test and run a Worker
- Run and observe retries
In this tutorial, you'll build your first Temporal Application from scratch using the Temporal Ruby SDK. You'll develop a small application that asks for your name and then uses APIs to get your public IP address and your location based on that address. External requests can fail due to rate limiting, network interruptions, or other errors. Using Temporal for this application will let you automatically recover from these and other kinds of failures without having to write explicit error-handling code.
The app will consist of the following pieces:
- Two Activities: the first gets your IP address, and the second uses that IP to find your location.
- A Workflow that calls both Activities, using the result of the first as input to the second.
- A Worker to host the Workflow and Activity code.
- A client program to start your Workflow.
You'll also write tests to verify your Workflow runs successfully.
Prerequisites
Before starting this tutorial:
- Set up a local development environment for developing Temporal Applications with Ruby. Ensure the Temporal Service is running locally and you can access the Web UI on port
8233(the default). - Follow the Run your first Temporal application with the Ruby SDK tutorial to understand how Temporal's components fit together.
Create a new Temporal Ruby project
To get started with the Temporal Ruby SDK, you'll create a new Bundler project, just like any other Ruby program you're creating. Then you'll add the Temporal SDK package to your project.
In a terminal, create a new project directory called temporal-ip-geolocation:
mkdir temporal-ip-geolocation
Switch to the new directory:
cd temporal-ip-geolocation
From the root of your new project directory, set up a Bundler project. This creates a Gemfile in the current directory.
bundle init
Then add the Temporal Ruby SDK to the Gemfile:
bundle add temporalio
You'll see the following output, indicating that the SDK is now a project dependency:
Fetching gem metadata from https://rubygems.org/.....
Resolving dependencies...
Next, install the Temporal SDK from the Gemfile:
bundle install
You'll see an output similar to the following:
Installing temporalio 0.4.0 (arm64-darwin)
Bundle complete! 1 Gemfile dependency, 6 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
With the project created, you'll create the application's core logic.
Write functions to call external services
Your application will make two HTTP requests. The first returns your current public IP, while the second uses that IP to provide city, state, and country information.
You'll use Temporal Activities to make these requests. Activities are where you execute non-deterministic code or perform operations that may fail, such as API requests or database calls.
If an Activity fails, Temporal can automatically retry it until it succeeds or reaches a specified retry limit. This ensures that transient issues, like network glitches or temporary service outages, don't result in data loss or incomplete processes.
Create a new folder which will hold your Temporal logic:
mkdir -p lib
Within lib, create another folder - ip_geolocate - which will contain your Workflow and Activity logic.
mkdir -p lib/ip_geolocate
Create the file get_ip_activity.rb which will return your current public IP:
touch lib/ip_geolocate/get_ip_activity.rb
With the Ruby SDK, you can define Activities as regular Ruby methods. Open the file get_ip_activity.rb in your editor and add the following code to define a Temporal Activity that retrieves your IP address from icanhazip.com:
require 'json'
require 'net/http'
require 'uri'
require 'temporalio/activity'
module IPGeolocate
class GetIPActivity < Temporalio::Activity::Definition
def execute
url = URI('https://icanhazip.com')
# Create an HTTP session that can be reused
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = (url.scheme == 'https')
# Start a session
http.start do |session|
# Make the request within the session
request = Net::HTTP::Get.new(url.request_uri)
response = session.request(request)
# Return the response body with whitespace removed
response.body.strip
end
end
end
end
The response from icanhazip.com is plain-text, and it includes a newline, so you trim off the newline character before returning the result.
Notice that there's no error-handling code in this function. When you build your Workflow, you'll use Temporal's Activity Retry policies to retry this code automatically if there's an error.
Now add the second Activity that accepts an IP address and retrieves location data. Create the file get_location_activity.rb which will return your current location:
touch lib/ip_geolocate/get_location_activity.rb
Now add the following code to get_location_activity.rb:
require 'temporalio/activity'
require 'net/http'
require 'uri'
require 'json'
module IPGeolocate
class GetLocationActivity < Temporalio::Activity::Definition
# Use the IP address to get the location
def execute(ip)
url = URI("http://ip-api.com/json/#{ip}")
response = Net::HTTP.get(url)
data = JSON.parse(response)
"#{data['city']}, #{data['regionName']}, #{data['country']}"
end
end
end
This Activity follows the same pattern as the GetIPActivity Activity. It's a method that calls a remote service. This time, the service returns JSON data rather than text.
While Activities can accept input arguments, it's a best practice to send a single argument rather than multiple arguments. In this case you only have a single string. If you have more than one argument, bundle them up in a serializable object. Review the Activity parameters section of the Temporal documentation for more details.
You've created your two Activities. Now you'll coordinate them using a Temporal Workflow.
Control application logic with a Workflow
Workflows are where you configure and organize the execution of Activities. You define a Workflow by writing a Workflow Definition using one of the Temporal SDKs.
Temporal Workflows must be deterministic so that Temporal can replay your Workflow in the event of a crash. That's why you call Activities from your Workflow code. Activities don't have the same determinism constraints that Workflows have.
Create the file get_address_from_ip_workflow.rb in the ip_geolocate folder:
touch lib/ip_geolocate/get_address_from_ip_workflow.rb
In the Ruby SDK, you implement a Workflow the same way you define an Activity; using a method. Add the following code to import the Activities, configure how the Workflow should handle failures with a Retry Policy, and define the GetAddressFromIPWorkflow Workflow, which calls both Activities, using the value of the first as the input to the second:
require 'temporalio/workflow'
require 'temporalio/retry_policy'
require_relative 'get_ip_activity'
require_relative 'get_location_activity'
module IPGeolocate
class GetAddressFromIPWorkflow < Temporalio::Workflow::Definition
def execute(name)
ip = Temporalio::Workflow.execute_activity(
GetIPActivity,
start_to_close_timeout: 300,
retry_policy: Temporalio::RetryPolicy.new(
initial_interval: 2.0, # amount of time that must elapse before the first retry occurs
backoff_coefficient: 1.5, # Coefficient used to calculate the next retry interval
max_interval: 30.0 # maximum interval between retries
# max_attempts: 5, # Uncomment this if you want to limit attempts
# non_retryable_error_types: # Defines non-retryable error types
)
)
location = Temporalio::Workflow.execute_activity(
GetLocationActivity,
ip,
schedule_to_close_timeout: 300
)
"Hello, #{name}. Your IP is #{ip} and you are located in #{location}."
end
end
end
In this example, you've specified that the Start-to-Close Timeout for your Activities will be five minutes, meaning that your Activity has five minutes to complete before it times out. Of all the Temporal timeout options, start_to_close_timeout is the one you should always set.
Temporal's default behavior is to automatically retry an Activity that fails, which means that transient or intermittent failures require no action on your part. This behavior is defined by the Retry Policy. If you don't specify the values on your Retry Policy, you will automatically use Temporal's default Retry Policy values. Note that max_attempts is commented out, which means there's no limit to the number of times Temporal will retry your Activities if they fail. The non_retryable_error_types field is also commented out, meaning that Temporal will retry all error types.
Next you'll create a Worker that executes the Workflow and Activities, and write tests to confirm everything works as expected.
Get notified when we launch new educational content
New courses, tutorials, and learning resources - straight to your inbox.