Refactor ThisAnswers
One of the more interesting results from the replies that I got is that a lot of people didn’t actually read the code.
The major reason that the code was complex was that there were several concerns that had to be dealt with, but the reason that this method dealt with several concerns at the same time was that different actions are required for different errors, that the error handling code itself need to be aware of errors, etc.
One of the nicer suggestions that I got was to create a strategy chain, like this:
public class TransactionBehavior : BaseBehavior { public void Execute() { using(var tx = new TransactionScope()) { NextBehavior.Execute(); tx.Complete(); } } } public class ExceptionBehavior : BaseBehavior { public void Execute() { try { NextBehavior.Execute(); } catch(Exception ex) { logger.Error(ex.Message, ex); } } } public class ConsumeBehavior : BaseBehavior { public void Execute() { foreach(var consumer in GatherConsumers()) { reflection.InvokeConsume(consumer); } NextBehavior.Execute(); } }
And that looks really nice, but it suffer from one major problem, it doesn’t (and can’t) handle error scenarios in a good way when different errors have different meanings and requires different responses.
I got some comments that were very critical about the code, but it is quite telling that about 90% of the actual refactoring attempts ended up dropping some critical functionality.
Comments
As it currently exists an exception can be thrown by.
Processing message in consumer throws.
An error in MessageArrived, MessageCompleted, BeforeMessageCompleted, MessageProcessingFailure
Commiting the transaction.
Deserializing message
The ways that exceptions are handled differently.
Transaction is rolled back with an error action for incrementing failure and message put back into queue.
Error is logged and ignored.
I may be simplifying something in my head, but I'm pretty sure those can all be accounted for. I might even question the need of some of the events at all if this were the approach taken. Additionally the behaviors could be given the option on how to handle errors if they each need their own error handling. I don't think error handling awareness is all that much to ask of the end user if you're adding behaviors. You could also get rid of the generic catch all ErrorBehavior and provide something more explicit in the BaseBehavior <handleerrorandignore>
Corey,
What user?
There isn't a user here, this is infrastructure code that the user never sees
The end user writing behaviors outside the core provided ones. In other words a person who might write a message module currently for whatever reason, providing an NHibernate session, auditing, authorization, etc. Ugh I forgot generics were stripped out of comments.
I meant BaseBehavior<HandleErrorAndIgnore>
Corey,
Error handling is not something that the IMessageModule author should care about. That is not something that they need to, because the infrastructure takes care of this.
Maybe it's not that people didn't read the code and more that there weren't any unit tests to verify behavior. ;)
Also, I have a hunch that much of the complexity found in that method was the consequence of decisions made in other parts of the code. I agree that none of the posted refactorings felt quite right, but without more context it's hard to come up with a good solution.
I'm with Colin. Corey's refactoring is a major step in the right direction. If you want more, we need use cases, unit tests, something, anything more to put the original code into context.
Even if you were to ignore my comments above. You could achieve the equivalent to what exists now in the RhinoQueueTransport (or at least before you changed things) with the below chain named for comparison. The only thing I see being lost is the logging with very specific messages in each of the catch blocks. Did I miss something?
Policies
.WrapBehaviorChainsWith<FireMessageReceivedBehavior>()
.WrapBehaviorChainsWith<TransactionBehavior>()
.WrapBehaviorChainsWith<FireMessageCompletedBehavior>()
.WrapBehaviorChainsWith<FireMessageProcessingFailure>();
I'll probably toy with this idea on my own and if you're up for taking a look when it's "ready" to poke holes in. I may need some ideas for the Windsor magic to take it the long haul, but I should be able to prove the concept without it for now.
Cross cutting concerns so, use AOP?
Comment preview