Business Errors Aren't Exceptions


In my earlier projects, I had an approach for the application layer to throw any business failure as Exception error and had a custom exception handling middleware to monitor the request and when the business error was thrown, it would be handled by logging it and also writing an appropriate response to the client.
Over time, I tried to gain more knowledge by investigating through architectural concerns and being more realistic about the weaknesses of my design. So, I tried to dig deeper into this approach. Eventually, I reached the point that a business failure must not be treated as an Exception error in an application. It is not an unexpected error while we define it explicitly and by facing that business error, the request would be pushed to the custom middleware which is available on pipeline to handle it and log it. Also because of business failures acting as exception errors, both the try/catch mechanism itself and the full stack trace being logged cause overhead to the application.
Thanks to the global exception handling or having custom middleware options in .NET, the exceptional errors can be handled and monitored. But now the question is “How would business errors be handled in a use case?”.
My recommendation is to use the FluentResults package and its classes to return generic or non-generic results for the expected business errors. By inheriting the FluentResults.Error class, it would be possible to have multiple error object types. Here is a code snippet of how it works:
var user = await appDBContext.Users.FindAsync(userId, cancellationToken);
if (user == null) return Result.Fail<UserDto>(new NotFoundError($"User {userId} not found"));
return Result.Ok(_mapper.Map<UserDto>(user));
public class NotFoundError : Error { public NotFoundError(string message) : base(message) { } }
Now there may be a concern about logging these errors or defining a metric to monitor a specific business error type. There are some ways to fulfil this need. For example, when the MediatR package is used in the application layer of a clean architecture, a behavior can be introduced to do these common concerns using its IPipelineBehavior feature. It acts similarly to ASP.NET middleware. So, if MediatR does not exist in layer a custom middleware can also do that too.
Based on this approach, instead of adding overhead to the application by throwing business errors, we would place them correctly and treat them according to their nature as expected business errors.
No comments yet. Be the first to share your thoughts!