Retry Library
In this chapter, we will apply the concepts we have seen so far in the book to develop a simple Retry library.
Failure Handler Proc
Sometimes you want to do something after your retry attempts has failed in your code. You may want to log the exception to a remote server or simply write to a log file. You could then investigate the problem later. Let's write a simple failure call back proc object that will handle the failure when we connect to a remote web service.
uri = 'www.google.com'
query = 'rich'
MAX_RETRY_ATTEMPT = 3
failure_call_back = -> { raise "Timeout Error: Cannot reach service #{uri} #{query.inspect.to_s} after #{MAX_RETRY_ATTEMPT} retry attempts." }
failure_call_back.call
You can call the failure_call_back proc object immediately.
Using a Class
But being able to call it later is more useful. It allows our library to be generic and agnostic to the application specific variables. Let's change our code to make it context independent.
# Application specific code below
uri = 'www.google.com'
query = 'rich'
retries = 3
failure_call_back = -> { raise "Timeout Error: Cannot reach service #{uri} #{query.inspect.to_s} after #{retries} retry attempts." }
# Library code begins
class RetryMe
def self.retry(failure_call_back)
# Retry implementation goes here
# On failure the following call back is executed
failure_call_back.call
end
end
# Application code below
RetryMe.retry(failure_call_back)
This prints:
RuntimeError: Timeout Error: Cannot reach service www.google.com "rich" after 3 retry attempts.
This works, but this approach is not message centric. How can we improve the design? Can we use a module?
Using a Module
uri = 'www.google.com'
query = 'rich'
retries = 3
failure_call_back = -> { raise "Timeout Error: Cannot reach service #{uri} #{query.inspect.to_s} after #{retries} retry attempts." }
module RetryMe
def retry(failure_call_back)
failure_call_back.call
end
end
include RetryMe
retry(failure_call_back)
This gives an error:
syntax error, unexpected '(', expecting end-of-input
retry(failure_call_back)
^
catch_exception.rb:20: in `block in <top (required)>': undefined method `each' for nil:NilClass (NoMethodError)
We cannot use retry as the method name because retry is a Ruby keyword. Let's rename the retry method to attempt.
uri = 'www.google.com'
query = 'rich'
retries = 3
failure_call_back = -> { raise "Timeout Error: Cannot reach service #{uri} #{query.inspect.to_s} after #{retries} retry attempts." }
module RetryMe
def attempt(failure_call_back)
failure_call_back.call
end
end
include RetryMe
attempt(failure_call_back)
This results in the expected output.
# RuntimeError: Timeout Error: Cannot reach service www.google.com "rich" after 3 retry attempts.
This approach requires include RetryMe
statement before we can use the attempt method.
Opening the Kernel Module
We can add our method to the Kernel Module.
uri = 'www.google.com'
query = 'rich'
retries = 3
failure_call_back = -> { raise "Timeout Error: Cannot reach service #{uri} #{query.inspect.to_s} after #{retries} retry attempts." }
module Kernel
def attempt(failure_call_back)
failure_call_back.call
end
end
attempt(failure_call_back)
This works. However, there is a drawback. This makes it available everywhere.
Using Refinements
We can restrict access to our methods by using refinements.
uri = 'www.google.com'
query = 'rich'
retries = 3
failure_call_back = -> { raise "Timeout Error: Cannot reach service #{uri} #{query.inspect.to_s} after #{retries} retry attempts." }
module RetryMe
refine Object do
def attempt(failure_call_back)
failure_call_back.call
end
end
end
using RetryMe
attempt(failure_call_back)
This also works. If you compare this solution to the module based solution, there is only one difference. The keyword using
is used instead of include
.
Retryable Gem
If you browse the source code for retryable gem, you will find that it used the Kernel module approach in the first version. The 2.0 version uses the module approach where Retryable module has a retryable class method.
Summary
In this chapter, we developed the core of a simple Retry library that takes advantage of delayed execution of proc objects. The failure call back proc object is called only when all the attempts has failed. We briefly saw how the concepts we learned in this book is used in the wild.