This is part three in a series of articles on .NET architecture. You can start here for the introduction and table of contents. This post will focus on the first thing you should think about when starting a new .NET application: cross-cutting concerns. And, by the way, I got really tired of type “cross-cutting concern” after about a dozen times, so I decided to coin a new acronym for them: C3s. I hope you like it.
What is a cross-cutting concern?
I’m not going to try to pin down an exact definition, but there are several things that give a strong indicator that a particular function in an application is a C3. There are exceptions to all of these, but it is a good starting point. Here they are:
- The functionality is needed across many different layers / modules / classes within the application.
- The functionality is not core to the application.
- The functionality is necessary in most, if not all, applications.
We’ll get into a bunch of examples later in the article. But just so you have something to focus on, I’ll give you the quintessential example of a C3: logging. You need it in almost all applications of any complexity; it is never core functionality for the application; and you need it in almost every method you write. Logging, like any other C3, deserves some up front thought when you sit down to code and click on File > New Project.
Best practices with cross-cutting concerns
There are a few things to keep in mind when dealing with these types of functions that make them different from standard pieces of an application:
- You are going to need it!
Because these pieces of functionality are needed all throughout your application, it is better to plan for them early and use them throughout development. For many of these types of functions, if you try to add them in later, it won’t go well. You won’t remember all of the places you should’ve added it, and you won’t have coded the application in a way that is friendly to adding it in after the fact.This is one of those rare cases where you want to violate the YAGNI principle (see the second post in this series for details). Of course, you shouldn’t add in every single C3 you can think of to every application. But, when you start an app, you should give some thought to these types of functions, and make sure your application is well equipped for the ones it is likely to need.
- Always decouple
If YAGNI applies less for C3s, then the principle of decoupling applies doubly so. Because C3s tend to leak into every place in an application, they often lead to code duplication, and tangling of dependencies. You don’t want every part of your code to have a concrete dependency on a particular implementation of a C3. Instead, use interfaces, or dependency injection, or perhaps even aspect-oriented programming, which I cover later in the article.There are many ways to achieve decoupling; the important part is to make sure you do it, because it will be too hard to pull everything apart later. Have you ever gotten 80% done with an application only to realize that you need to switch out your logger? Or your security provider? Then you know what I’m talking about.
- Don’t reinvent the wheel
Almost by definition, C3s tend to be functionality that is needed by many different applications. Therefore, if you need these types of functionality in your application, you should look at existing implementations you can leverage. As you can see in my chart of examples below, almost all of the common C3s are addressed either directly in the .NET Framework, or in open source libraries. And if those don’t work, you can usually purchase a library from a vendor, and not have to start from scratch. These types of generic functionality are perfect candidates for reusing the work of others.
Examples of cross-cutting concerns
Now let’s get into the real meat of this post – examples. Below is a list of some common C3s (in no particular order) that you may want to think about when starting an application. For each one, I also give some advice on how to approach that C3 for .NET applications in particular.
|Logging||I have yet to encounter a serious application that did not have at least minimal logging requirements. And even if logging wasn’t a specific requirement, I would almost always build it in. I’ve had plenty of occasions where I wish that a particular area of code had logging, but I’ve never had the opposite problem.In the .NET world, there are at least a half dozen logging frameworks that I’ve run into, but two stand out. One is the log4net framework, a port of a popular and stable Java logging library. The other is the Logging Application Blockfrom the Microsoft Patterns & Practices group.I’m going to break another principle in this post: that of preferring Microsoft components. The Enterprise Library scares me, because it seems like the same rigor is not put around products released from P&P group versus actual product teams. And also the App Blocks tend to lag behind the latest .NET technologies. So I recommend taking a look at log4net. I’ve had good luck with it in the past, and it seems to have all the features you’d need in a logger.|
|Instrumentation||Instrumentation is closely related to logging, and many people would probably consider them the same thing. But I’ve decided to spike it out separately, because it deserves some additional thought beyond logging.In my mind, logging involves creating messages when certain events occur within the application. Instrumentation involves gathering statistics about those things: the amount of time something takes, a count of how many objects are loaded at a critical point, the amount of line items on a transaction, etc.The good news is that whatever logging framework you choose will serve you equally well for instrumentation. Just make sure you put some thought towards the areas of your application that should be instrumented.|
|Security||Developers (myself included) always seem to leave security until the end of the development cycle, and for good reason. Once you add security, everything gets harder to test, and you have to start managing multiple user accounts to be able to exercise the application with different security profiles.But if you leave security until the end, you end up forgetting it in lots of places where it belongs, and all of the security bugs (yes, there will be bugs) crop up toward the end of the project. And they can be the worst types of bugs to resolve. Because of this, I recommend putting some up-front thought toward security..NET does have support built in for security, which is good enough for the simple cases. You can check out this article for further information. But if your application has more sophisticated security needs, such as complex privilege matrices, or the need to secure particular instances of objects, you will probably need to extend the built-in security framework with some custom code.|
|Error Handling||Why do we have to talk about error handling? You sprinkle some try/catch blocks throughout the code, and you’re done, right? Not quite. Think about most high-quality applications that you use in a given day, such as MS Office, Visual Studio, or Adobe Photoshop. When they hit an error condition, do they just crash, lose all their data, and force the user to start from scratch? No. Most of the time they try to recover gracefully from the error condition and let the user keep on going. Or, if they can’t do that, they at least save the user’s data and restart in recovery mode.Ideally, you want your application to recovery gracefully whenever possible, and avoid frustrating your user with loss of data, confusing error screens, or an app that just suddenly stops working. Oftentimes, with enough forethought and cleverness, you can provide this without needing much more than try/catch and a global error event. However, the MS Patterns & Practices group provides the Exception Handling Application Block which has more sophisticated features. Again, I am a bit wary of using the MS Enterprise Library in production applications, but I have not seen a good alternative library for this functionality. If there are other options you are aware of, please share in the comments.|
|Validation||Many applications, especially those classified as “line of business” apps, have complex business rules that are executed when a user tries to conduct a transaction or save an entity. If there are errors that the user needs to correct, you have to figure out how to tell them what the error is, show it on the UI, and respond once they’ve corrected the problem. This is known as validation, and it is generally a pain in the ass.Every UI technology in the .NET Framework tries to help developers solve the problem of validation, and each in their own way. These validation solutions are getting more and more sophisticated, and the latest ones in MVC and Silverlight are really quite good. However, they are not yet perfect, and you still need to put some thought around validation if you are going to make heavy use of it in application. This is especially true if you have special validation needs, such as having different classes of messages (e.g. warnings, errors, critical errors), or if you want to run validations on the client side as well as the server.You are better off using whatever validation technology exists in the UI framework you are using. Don’t try to paddle upstream and write your own validation mechanisms, unless you have a very good reason. If you design your application with validation in mind, you’ll be much better off in the long run.|
|Caching||A lot of applications can get away with just hitting the database everytime they need data. If this is your application, congratulations you don’t have to worry. However, this is often too simplistic for applications that have to worry about performance, scalability, “offline mode”, or similar issues. In these cases, you need to hold on to the data in a layer closer to the application. In most cases this is in memory, but it can also be on a local disk or a server specifically dedicated toward caching.In the .NET world, there are many different options for caching, but it depends on which particular technology stack you are using. Usually caching is much more important in web applications, which is why ASP.NET has several different options for caching. Outside of ASP.NET, you should take a look at System.Runtime.Caching. And if you need more sophisticated caching, you can take a look at AppFabric caching, which used to be known as its codename, Velocity.|
|Transaction Management||Transaction management is one of those things that are easy to overlook. You wrap your calls to ADO.NET in a transaction and you think you are all set. Until 6 months after the application is in production, and your manager pulls you into his office and asks you to explain why there are completed transactions in the payment processing system that were never delivered to the check writing system. And that is not a fun conversation to have.Transaction management doesn’t begin and end with the database. You also have to worry about it when you make calls to web services, or write messages to a message queue, or fire off workflows in an external system.I plan on covering transaction management in more detail in a post in this series focused on data access. But for now, take a look at the System.Transactions namespace. Most of the .NET technologies work with it, and it is usually quite painless.|
Wrangling C3s with Aspect-oriented Programming
Whenever the subject of cross-cutting concerns comes up, aspect-oriented programming (AOP) is always lurking nearby. The idea behind AOP is that C3 behaviors (a.k.a. “aspects”), you don’t want to sprinkle code all over the application. Instead, you express the code for an aspect only once, and then use attributes or other declarations to indicate which areas of the application should be affected by that aspect.
The advantage of AOP is maximum decoupling of C3s from the rest of your code. You don’t have to worry about impacting the application when you want to change logging frameworks, or add more sophisticated security rules. You can treat these as aspects, and implement them completely independently, without the application code needing any changes. The disadvantage of this pattern is the non-obviousness of what is happening. A maintenance developer trying to fix a security bug in your application might have a lot of trouble figuring out that the security code being executed is nowhere near the method that is failing.
Unfortunately, Microsoft has not yet made AOP a first class citizen in the .NET Framework. There is the Policy Injection Application Block from MS Patterns & Practices that provide AOP for .NET. Also, many Inversion of Control frameworks, including Unity from the same group, provide some AOP functionality. Check out my friend Tom’s post for more on that subject. The ContextBoundObject class within the .NET Framework can also be used for rudimentary AOP behavior, though that is not its original intent. None of those options are all that great, though, for various reasons. If you want to have serious AOP behavior in your application, I recommend taking a look at PostSharp. That library is geared toward AOP, whereas the other options treat it as an afterthought.
As you can see from the length of this post, cross-cutting concerns can be an involved subject. But it is important to consider these behaviors when designing a new application. Applications of any complexity will always have certain cross-cutting concerns that need to be satisfied. It is better to realize this up front, and make sure that your design satisfies them in a way that won’t cause you a headache in the long run.