When developing software, one of the main criteria should always be to develop quality software. To serve this purpose, one of the main criteria we must follow is “the principle of robustness." The main idea behind that principle is that the program we write should be able to survive in “abnormal” situations, even outside its requirements, and should not crash. Although this sounds simple at the first glance, in most cases, it comes at the cost of additional work.
One of my remote projects was related to N-Layered architecture. In it, we used old WCF/.asmx services and wrapped them into Azure functions. You could say the slogan was, “wrap them to use in modern world."
Each layer encapsulates the work of its module and transmits only the necessary information to the upper layer. But when transmitting necessary information, exceptional cases should be kept in mind. If we don’t process exceptions correctly in each layer, the upper layer will have the information about the working principle and internal implementation of the lower layer. Each layer should encapsulate its modular work at the maximum level, and transmit to the upper layer only the information it needs to know. The less information the layers have about each others' work, the better the architecture will be. Let’s go directly to the implementation without going to the architectural aspects of the work, without deviating from the goal.
Birds-eye view of the architecture:
First, let’s look at the problem as it was before excbirds eyeere handled. The lower layer simply processes the exceptional cases in its layer via try..catch and returns an exception. This means that if an exception occurs in a layer related to its internal implementation, the upper layer will have information about it. This will lead to the dispersion of the workload.
try
{
//do some operation
//more operation
}
catch(Exception exp)
{
_logger.WriteToLog(exp.Message);
throw;
}
If an exception occurs in the Azure functions layer, we will see detailed error information about the web service. The reason for this is that each layer doesn’t handle unique exception cases.
To solve such problems, we need a custom exception handling mechanism that depends on the situation, because each layer should transmit only as much information as it needs to know. We can also call this friendly exception throwing.
To solve the problem, I developed a private exception handling class in the sublayer.
[Serializable]
public class ServiceException: ApplicationException {
public ServiceException(ServiceExceptionMessage serviceExceptionMessage) {
ServiceExceptionMessage = serviceExceptionMessage;
}
public ServiceExceptionMessage ServiceExceptionMessage {
get;
private set;
}
public ServiceException(string message): base(message) {}
public ServiceException(string message, Exception inner): base(message, inner) {}
protected ServiceException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context): base(info, context) {}
}
public class ServiceExceptionMessage {
public List < ValidationError > Errors {
get;
set;
}
public ServiceExceptionMessage() {
Errors = new List < ValidationError > ();
}
}
We use this class as an exception contract between the upper and lower layers:
It was necessary to process that exception in the Azure function. Since I have given the newly created exception classes in the lower layer, the upper layer can use those function classes by simply referencing them.
The BadResponseErrorResult class is also manually created. Its main responsibility is to reflect the exception to the user according to the contract, if an exception occurs in Azure functions.
In conclusion, note that handling exceptions correctly is one of the most important issues when working with N-layered architecture (it applies to any architecture, not just the one here.). To accomplish this, you can write a custom handling mechanism and provide as much as information as the upper layer needs.