Crosscutting Concerns

The Crosscutting Concerns are the areas in which high-impact mistakes are most often made when designing an application. There are common causes that lead to this and there are common practices that can help to avoid such mistakes. In this post I will talk about the way I usually address most of the crosscutting concerns at the start of a project, when usually there are many other more urgent things to think of.

The crosscutting concerns are the features of the design that may apply across all layers. This functionality typically supports operations that affect the entire application. If we are thinking of a layered architecture, usually the components from more layers (Presentation Layer, Business Layer or Data Access Layers) use components that address in a common way a concern, or which assures that a critical non-functional requirement is going to be met.

The crosscutting concerns are very much related to the quality requirements (also named non-functional requirements or quality attributes). In fact the crosscutting concerns should derive from these requirements. They are usually implemented in a separate area of the code and specifically address these requirements. For example:

  • Logging concern derives from Diagnostics, Monitoring or Reliability requirements
  • Authentication and Authorization concern derives from Security requirements
  • Caching concern derives from Performance requirements
  • Error Handling concern derives from Diagnostics, Availability and Robustness requirements
  • Localization concern derives from Multi-language and Globalization requirements
  • Communication concern derives from Scalability, Availability or Integration requirements

One of the most common mistake with the crosscutting concerns is that we tend to neglect their importance at the start of the project, and we poorly consider them or we don’t do it at all. Then, when we already have a ton of code written, somewhere in the second half of the project, from various reasons these quality requirements become actual again. But now… because of all the code we have, the cost of addressing them in a consistent and robust manner becomes very high. To add consistent and useful logging for example, now, would mean to go back through all the code and all the tested functionality and change it to call a logging function. This may be costly. The same goes for authorization, localization and many others.

The challenge comes from the fact that the crosscutting components are like support code for the components that implement the functional requirements. They should be done before we start developing functionality. But this is often not a good idea. At the start of the project it is good to start implementing the functionalities, so we can get feedback and show progress, not to spend too much time on things that may be postponed.

The key is to address them at the very beginning, but not to implement them. We should just identify them and design only the most important aspects. We should postpone the making of any time consuming decision. I’m not saying make uninformed decisions because of lack of time and change them later, I am saying to design in a way that allows postponing these decisions.

Lets take Logging for example. We can easily define the logging interface, looking at the monitoring and diagnostics requirements and considering the complexity of the application. It will be something like this:

public static class Log  
{  
 	public static void Error(string healine, string message)  
 	{ }
 
 	public static void Warning(string healine, string message)  
 	{ }
 
 	public static void Trace(string functionName, string message)  
 	{ }
 
 	public static void Debug(string message, object[] variables)  
 	{ }  
}

For the start the functions can do nothing. They can be left unimplemented. Later, we can come back to this and invest time to decide which logging framework should we use. We will think about the configurability of the logging, whether we should log in the database or not, weather we should send some logs by email, whether we should integrate with a monitoring tool and which one, at a later time. Until then, all the code that implements the functional use cases, has a support for logging and if we make a clear and simple logging strategy then we will have meaningful calls to these functions.

Maybe in a few sprints, we’ll come with a simple implementation of this, which will write logs in a text file, to help us in debugging. Then later, we could integrate the framework we know best to write the logs in csv file so we can easily filter and search. By the time we get near the deployment in different test environments, we will know more about the logging the system needs and it will be easier to make a good decisions on how to address this concern’s implementation better. However, in all this time the code we are writing calls the logging interface, so we don’t need to go back and search for meaningful places to insert these calls.

The same practice can be applied for all of the crosscutting concerns. I also show it in a previous post Localization Concern which covers the localization aspects in detail.

So, the idea is that at the start of the project we should abstract each component that addresses a crosscutting concern. We can do this by thinking on how the other layers will interact with it, and define the abstraction from their perspective. The other layers should depend on this abstraction only. We can assure this by hiding (or encapsulating) the implementation of the crosscutting concern. By encapsulating the implementation we have the flexibility to change it or  to replace the frameworks we use without a significant impact on the other code. In most of the cases the dependencies will be like in this diagram, where ApplicationComponents do not know about the Log4Net library, which is just an implementation detail.

As a conclusion, following this approach, we can have the interface (abstractions) of the crosscutting concerns components very early in the project and have it called from all the use cases. With this we postpone the real implementation of these concerns and we limit the cost of adding it later. The time to define these abstraction is usually short. It goes from a few hours to a few days depending on the size and complexity of the application, but it always pays off rather than having this tackled for the first time in the second half of the project.

This topic is discussed in more detail in my Code Design Training
Featured image credit: noche via 123RF Stock Photo