CQRS and exception handling
This is the last article in the series of articles designed to supplement my CQRS in Practice course. In it, I’d like to discuss one particular aspect of exception handling relevant to CQRS and the decorator pattern.
Exceptions and exceptional situations
I wrote a lot on the topic of error handling on this blog. Here are a few notable articles:
Don’t worry, I don’t expect you to read all of them, so here’s a brief summary. All errors can be divided into two categories:
-
Errors we know how to deal with (expected errors).
-
Errors we don’t know how to deal with (unexpected errors).
Errors from the first category should be represented with plain values. The Result class is intended for this exact purpose.
Errors from the second category are best handled by exceptions. Exceptions exhibit the behavior you’d like to have in response to unexpected errors: interrupt the current operation as soon as possible and carry out some generic message to the end user.
Note the semantics difference between expected and unexpected errors. You can’t possibly work around an unexpected error, at least in any meaningful way. The moment you start making steps towards handling that unexpected error, it automatically becomes expected and therefore must be represented with a Result instance.
For example, say that you know an email server goes offline from time to time and you want to show the user a friendly error message when that happens. Something along the lines of "Please, try again later". You shouldn’t use exceptions to implement this functionality. What you should do is catch the exception thrown by the SMTP client and transform it into a Result instance at the lowest level of the execution stack possible and then work with that Result instance moving forward.
This guideline is applicable to both library development and enterprise application development. If you’re writing libraries or frameworks, you can’t (and shouldn’t) know whether your client can deal with the failure, it’s up to the client specifics. If you, as a library author, don’t know what to do with that failure, you should throw an exception. On the other hand, if the client app can deal with the exception generated by the library code, they should wrap it into a Result.
Ultimately, you should catch exceptions in two cases only:
-
When you know how to deal with an exception thrown by a library. You should catch such an exception at the lowest level possible and converted into a Result before processing.
-
When you put a generic handler for all unhandled exceptions in your app. You should catch such exceptions at the highest level possible. These exceptions should be allowed to stop the current operation and shouldn’t be processed at all. The only processing you can do at this point is log them.
You should throw exceptions yourself only in a situation you don’t know how to deal with (a bug, failed assumption, unrecoverable hardware or software failure, etc). Such exceptions serve as safeguards. They go straight to the generic handler and fail fast your app.
CQRS and decorators
Decorator is a class or a method that modifies the behavior of an existing class or method without changing its public interface. In other words, it’s a wrapper that preserves the interface of the thing it wraps.
The decorator pattern is especially useful in CQRS. It helps you avoid code duplication by extracting cross-cutting concerns into their own classes - decorators. It also helps adhere to the single responsibility principle: you can keep command and query handlers simple and focused on business use cases.
For example, you may need to log each individual command. Instead of duplicating this logic in command handlers, you can put it into a decorator that would execute before each handler. Moreover, as you introduce more and more decorators, their composition becomes increasingly rich in terms of functionality, and still, each separate element of this composition remains very simple:
CQRS decorators and exception handling
Let’s now take a specific and quite widely spread use case for the decorator pattern: database retry. Let’s say that the application database can go down from time to time and we need to cope with that by retrying the same operation for a number of attempts.
One way to implement it would be to do the re-try in the command handler itself:
public class EditPersonalInfoCommandHandler : ICommandHandler<EditPersonalInfoCommand>
{
public Result Handle(EditPersonalInfoCommand command)
{
for (int i = 0; ; i++)
{
try
{
var unitOfWork = new UnitOfWork(_sessionFactory);
Student student = unitOfWork.GetStudentById(command.Id);
student.Name = command.Name;
student.Email = command.Email;
unitOfWork.Commit();
return Result.Ok();
}
catch (Exception ex)
{
if (i >= _config.NumberOfDatabaseRetries || !IsDatabaseException(ex))
throw;
}
}
}
}
The bad part in this implementation is that is introduces a lot of code duplication. If you have multiple handlers where you want to do the re-try, you’ll need to copy the loop with the try-catch block in all of them.
The decorator pattern removes this duplication. Instead of catching the exception in the command handler, you need to create a wrapper (a decorator) on top of the generic ICommandHandler
interface. This wrapper would accept the handler as a continuation delegate and try to call that handler for several times before giving up:
public sealed class DatabaseRetryDecorator<TCommand> : ICommandHandler<TCommand>
where TCommand : ICommand
{
private readonly ICommandHandler<TCommand> _handler;
private readonly Config _config;
public DatabaseRetryDecorator(ICommandHandler<TCommand> handler, Config config)
{
_config = config;
_handler = handler;
}
public Result Handle(TCommand command)
{
for (int i = 0; ; i++)
{
try
{
Result result = _handler.Handle(command);
return result;
}
catch (Exception ex)
{
if (i >= _config.NumberOfDatabaseRetries || !IsDatabaseException(ex))
throw;
}
}
}
private bool IsDatabaseException(Exception exception)
{
string message = exception.InnerException?.Message;
if (message == null)
return false;
return message.Contains("The connection is broken and recovery is not possible")
|| message.Contains("error occurred while establishing a connection");
}
}
The command handler would then look like this:
[DatabaseRetry]
public class EditPersonalInfoCommandHandler : ICommandHandler<EditPersonalInfoCommand>
{
public Result Handle(EditPersonalInfoCommand command)
{
var unitOfWork = new UnitOfWork(_sessionFactory);
Student student = unitOfWork.GetStudentById(command.Id);
student.Name = command.Name;
student.Email = command.Email;
unitOfWork.Commit();
return Result.Ok();
}
}
You can find the full source code of the course’s sample project on GitHub.
This implementation works well, but don’t we have a problem here? Shouldn’t you catch exceptions you know how to deal with at the lowest level possible? The decorator clearly resides above the handler in the execution stack.
To satisfy this guideline, you would need to convert the exception into Result in the unitOfWork.Commit()
method (UnitOfWork
is a custom wrapper class on top of NHibernate’s Session). That’s the closest location to the library code generating the exception. The decorator then would check the result instance coming from the handler and if it contains a database connectivity error, it would re-try the handler:
public Result Handle(TCommand command)
{
for (int i = 0; ; i++)
{
Result result = _handler.Handle(command);
if (result.Error == Errors.DatabaseException)
{
if (i >= _config.NumberOfDatabaseRetries)
throw new Exception();
continue;
}
return result;
}
}
So, wouldn’t this implementation be better?
No, it wouldn’t. There’s no violation of the exception handling guideline in the initial version of the decorator. The guideline requires catching exceptions at the lowest level possible before processing them. You shouldn’t covert into Result exceptions that you don’t intend to process, which is exactly what we did in the new version. Now all database connectivity exceptions are converted into Result values, even when there’s no decorator on top of the command handler. Such exceptions will simply be ignored.
Decorators are the lowest level at which you can catch exceptions before processing them. In fact, the mere presence of a decorator controls whether the exception can be processed at all. Remove the decorator, and the exception must go straight to the generic exception handler at the application root. And that’s exactly how the decorator from the initial version behaves.
CQRS and exception handling: Summary
You should only covert an exception into a Result if you can process that exception. Converting exceptions in handlers and processing them in decorators violates this rule - there might be no decorator that would handle that exception.
Other articles in the series
-
CQRS and exception handling (this post)
Subscribe
Comments
comments powered by Disqus