I have this main-class that receives a queuemessage and then uses a few other classes to do some work. All these other classes use some lower classes themselves, and eventually data is written to a database or send to wcf services.
Based on the result of the lower classes the main-class has to decide wheter to remove the queuemessage, or place it on the queue again, or send it to a deadletterqueue.
If for example a the database is unreachable, the queuemessage can be placed in the queue to try it again later. But if the wdcf service returns that it doesn't accept some data, the message has to be sent to the deadletterqueue.
I have a couple of ways to implement this scenario:
- Throw exceptions and only handle them in the main-class.
- Throw exceptions but catch them in each calling class. And rethrow a new exception
- Return result-objects which indicate error/success-state
These are my ideas about the scenarios:
If one of the lowest classes throws an exception, and the main-class has to handle it, it couples the main-class all the way to the lowest classes. If one of the lowest classes decides to change an exception, I have to change the main-class exception handling.
There is no good way to let upper classes know which exceptions will be thrown from the called class in C#.
This is what i prefer. Every called method can return a result-object, with an enum indicating succes or failure, and the type of failure.
So, my preferred way is option 3, but I don't know if that's architecturally acceptable. Or if there are any better ways.
Code
This is what the code (in simplified form) looks like:
QueueHandler
private static void HandleQueueMessage(Message message)
{
var deliveryOrder = deserialize(message.body);
var deliveryOrderHandler = new DeliveryOrderHandler();
var result = deliveryOrderHandler.Execute(deliveryOrder.PubId);
switch (result)
{
case DeliveryOrderHandlerResult.DeliverySucceeded:
break;
case DeliveryOrderHandlerResult.FataleErrorInExternalSystem:
case DeliveryOrderHandlerResult.MAndatoryDocuhmentTransformationFailed:
SendDeliveryOrderToDeadletterQueue(deliveryOrder);
break;
default:
deliveryOrder.AbortCount = deliveryOrder.AbortCount + 1;
ResendDeliveryOrderToQueue(deliveryOrder);
break;
}
}
DeliveryOrderHandler
private DeliveryOrderHandlerResult Execute(long pubId)
{
DeliveryOrderHandlerResult deliveryOrderHandlerResult;
var transformationResult = GetTransformationResultaat(pubId);
if (transformationResult == TransformationResult.Success)
{
var deliveryResult = DeliverDocumentToExternalSystem(pubId);
if (deliveryResult.Status == DeliveryResult.Success)
{
SaveDeliveryResult(pubId, deliveryResult);
}
deliveryOrderHandlerResult = deliveryResult.Status;
}
else
{
switch (transformationResult)
{
case TransformationResult.NotStarted:
deliveryOrderHandlerResult = DeliveryOrderHandlerResult.TransformationNotStarted;
case TransformationResult.Busy:
deliveryOrderHandlerResult = DeliveryOrderHandlerResult.TransformationBusy;
case TransformationResult.MandatoryTransformationFailed:
deliveryOrderHandlerResult = DeliveryOrderHandlerResult.MandatoryTransformationFailed;
default:
throw new Exception(--unknown enum value --);
}
}
return deliveryOrderHandlerResult;
}
DeliverDocumentToExternalSystem
pseudo:
- Create Delivery package by reading data from database and transformed files from disk
- Send package to external system
As you can see there's a lot that can go wrong; failed database connections, failed wcf service calls, files not present etc.
I was hoping I could prevent this:
QueueHandler
private static void HandleQueueMessage(Message message)
{
var deliveryOrder = deserialize(message.body);
var deliveryOrderHandler = new DeliveryOrderHandler();
try
{
var result = deliveryOrderHandler.Execute(deliveryOrder.PubId);
switch(result)
{
case DeliveryOrderHandlerResult.Success:
// remove message from queue
case DeliveryOrderHandlerResult.NotStarted:
// resent message to queue
case DeliveryOrderHandlerResult.MandatoryTransformationFailed:
// send message to deadletterqueue
case ...
// handle
case ...
// handle
}
}
pseudo catches:
catch (DatabaseNotFoundexception ex)
{
// resent message to queue
}
catch (ExternalWcfServiceDownException ex)
{
// resent message to queue
}
catch (FileNotFoundException ex)
{
// send message to deadletterqueue
}
catch (...)
{
// handle
}
catch (...)
{
// handle
}
}