Aspects are common features that you write every now and then in
different parts of your project. it can be some specific way of
handling exceptions in your code, or logging method calls, or
timing execution of methods, or retrying some methods and so on. If
you are not doing it using any Aspect Oriented Programming
framework, then you are repeating a lot of similar code throughout
the project, which is making your code hard to maintain. For ex,
say you have a business layer where methods need to be logged,
errors need to be handled in a certain way, execution needs to be
timed, database operations need to be retried and so on. So, you
write code like this:
public bool InsertCustomer(string firstName, string lastName, int age, Dictionary<string, string> attributes) { if (string.IsNullOrEmpty(firstName)) throw new ApplicationException("first name cannot be empty"); if (string.IsNullOrEmpty(lastName)) throw new ApplicationException("last name cannot be empty"); if (age < 0) throw new ApplicationException("Age must be non-zero"); if (null == attributes) throw new ApplicationException("Attributes must not be null"); // Log customer inserts and time the execution Logger.Writer.WriteLine("Inserting customer data..."); DateTime start = DateTime.Now; try { CustomerData data = new CustomerData(); bool result = data.Insert(firstName, lastName, age, attributes); if (result == true) { Logger.Writer.Write("Successfully inserted customer data in " + (DateTime.Now-start).TotalSeconds + " seconds"); } return result; } catch (Exception x) { // Try once more, may be it was a network blip or some temporary downtime try { CustomerData data = new CustomerData(); if (result == true) { Logger.Writer.Write("Successfully inserted customer data in " + (DateTime.Now-start).TotalSeconds + " seconds"); } return result; } catch { // Failed on retry, safe to assume permanent failure. // Log the exceptions produced Exception current = x; int indent = 0; while (current != null) { string message = new string(Enumerable.Repeat('t', indent).ToArray()) + current.Message; Debug.WriteLine(message); Logger.Writer.WriteLine(message); current = current.InnerException; indent++; } Debug.WriteLine(x.StackTrace); Logger.Writer.WriteLine(x.StackTrace); return false; } } }
Here you see the two lines of real code, which inserts the
Customer calling a class, is hardly visible due to all the
concerns (log, retry, exception handling, timing)
you have to implement in business layer. There’s validation,
error handling, caching, logging, timing, auditing, retring,
dependency resolving and what not in business layers nowadays. The
more a project matures, the more concerns get into your codebase.
So, you keep copying and pasting boilerplate codes and write the
tiny amount of real stuff somewhere inside that boilerplate.
What’s worse, you have to do this for every business layer
method. Say now you want to add a UpdateCustomer method in
your business layer. you have to copy all the concerns again and
put the two lines of real code somewhere inside that
boilerplate.
Think of a scenario where you have to make a project wide change
on how errors are handled. You have to go through all the hundreds
of business layer functions you wrote and change it one by one. Say
you need to change the way you time execution. You have to go
through hundreds of functions again and do that.
Aspect Oriented Programming solves these challenges. When you
are doing AOP, you do it the cool way:
[EnsureNonNullParameters]
[Log]
[TimeExecution]
[RetryOnceOnFailure] public void InsertCustomerTheCoolway(string firstName, string lastName, int age, Dictionary<string, string> attributes) { CustomerData data = new CustomerData(); data.Insert(firstName, lastName, age, attributes); }
Here you have
separated the common stuffs like logging, timing, retrying,
validation, which are formally called ‘concern’,
completely out of your real code. The method is nice and clean, to
the point. All the concerns are taken out of the code of the
function and added to the function using Attribute. Each
Attribute here represents one Aspect. For example, you can
add Logging aspect to any function just by adding the Log
attribute. Whatever AOP framework you use, the framework ensures
the Aspects are weaved into the code either at build time or at
runtime.
There are AOP frameworks which allows you to weave the Aspects
at compile time by using post build events and IL manipulations eg
PostSharp, some does it at runtime
using
DynamicProxy and some requires your classes to inherit from
ContextBoundObject in order to
support Aspects using C# built-in features. All of these have
some barrier to entry, you have to justify using some external
library, do enough performance test to make sure the libraries
scale and so on. What you need is a dead simple way to achieve
“separation of concern”, may not be full blown Aspect
Oriented Programming. Remember, the purpose here is separation of
concern and keep code nice and clean.
So, let me show you a dead simple way of separation of concern,
writing standard C# code, no Attribute or IL manipulation black
magics, simple calls to classes and delegates, yet achieve nice
separation of concern in a reusable and maintainable way. Best of
all, it’s light, just one small class.
public void InsertCustomerTheEasyWay(string firstName, string lastName, int age, Dictionary<string, string> attributes) { AspectF.Define .Log(Logger.Writer, "Inserting customer the easy way") .HowLong(Logger.Writer, "Starting customer insert",
"Inserted customer in {1} seconds") .Retry() .Do(() => { CustomerData data = new CustomerData(); data.Insert(firstName, lastName, age, attributes); }); }
If you want to read details about how it works and it can save
you hundreds of hours of repeatetive coding, read on:
AspectF
fluent way to add Aspects for cleaner maintainable code
If you like it, please vote for me!