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.

results matching ""

    No results matching ""