Gents,
Our current exception use/handling in conductor is less than ideal.
I've tried to highlight some of the issues here, and proposed an
approach that I feel we should take when it comes to exceptions
handling. Anyone with thoughts on this topic please chime in.
Cheers
Martyn
--
So, we use the below syntax in many places in conductor code. Using
this syntax to raise an exception is problematic:
raise "Some error occurred while trying to do something"
Why?
There is no way for conductor to interpret the cause of this exception,
and react accordingly.
Example1: Recovery of errors
(of course this could be rewritten in a way to not use custom exception
but is just a simple example to demonstrate the need)
def perform_task
case @task.name
when "Start Instance": start_instance((a)task.realms[0])
...
end
end
def start_instance(realm)
...
# Instance failed realms full
raise "Could not start instance realms are full"
end
end
In the above code, perform_task calls start_instance which raises an
exception with the user message "the realm is full". This then gets
filtered up the chain and caught in the ApplicationController which logs
the error and filters the message back to the user.
-- Using Custom Exceptions
def perform_task(task)
begin
case task.name
when "Start Instance": start_instance(task.realms[0])
...
end
rescue RealmFull => rfe
if task.realms.size > 1
task.realms.delete(0)
perform_task|(task)
else
raise rfe
end
end
end
def start_instance(realm)
...
# Instance failed realms full
raise RealmFull.new(:message => "Could not start instance
realms are full")
end
end
In the 2nd example, we can recover from realm full problem by catching
the RealmFull exception, and retrying the start_instance with the next
realm in our list until our list is exhausted. Once the realm list is
exhausted there nothing more we can do at this point so we raise the
RealmFull exception up to the next level, where it is then handled
accordingly.
Of course you could say why not just catch all exceptions in the first
example and react accordingly. Well yes in this example you could do
that, but imagine start_instance can fail in many places: realm full,
hwp does not exist, ami not found etc...
Example2: Catching/Handle generic exceptions at the top of the chain
Once we have defined custom exceptions we can then manage them at the
top level, categorised into particular genres. An example where this
already happens in conductor is in the API, where we have defined an
APIException, all exceptions throw from within the API controllers
extend this exception and all other exceptions should* be wrapped in an
appropriate APIException. This allows us to handle all APIExceptions at
the top level. ApplicationController has a filter than catches all
APIExceptions that are raised this far up the stack. It then creates a
proper XMLResponse, with HTTP status code, Error Code and Error Message.
You can imagine we have lots of exceptions genres, e.g. UIException.
When application controller catches a UIException it knows it must
display a message to the user.
ConductorException, might simply just log the exception trace and carry
on as normal.
And so on.
--- Exception Handling
In order to add in the power of exceptions we need to all agree on a
particular approach.
Here are 3 rules that if we stick to will let us handle exceptions more
wisely.
1) Never Swallow Exceptions (Unless they are an expectation)
2) Try to handle the exception locally.
3) If 2 is not possible, pass the exception up the stack, (wrapping it
into something understandable to the upstream method, if neccessary).
If everyone sticks to these 3 rules. Exceptions will be handled and
recovered from in all cases possible, those that are not will eventually
work their way up to the application controller where they will be
handled appropriately, according to their genre.
Show replies by date