The Architect´s Napkin

Software Architecture on the Back of a Napkin
posts - 69 , comments - 227 , trackbacks - 0

My Links

News

Archives

Post Categories

Image Galleries

Aspect Oriented Programming made easy with Event-Based Components – Part II

In my previous post I described the architecture for a small application to index .TXT files. Here´s are the napkins with my design EBC diagrams so far:

image

Currently the implementation is working in a synchronous and sequential mode.

Now, today I want to move on and introduce a couple of aspects (in the AOP sense) into the design/code. I find Event-Based Component architectures very easy to extent in that regard. No special AOP tools necessary. But see for yourself…

Adding a logging aspect

The “Hello, world!” scenario for Aspect-Oriented Programming seems to be logging. See here for example a logging aspect by the creator of PostSharp. (By thy way I like PostSharp very much. If you need to use an AOP framework in your .NET code, be sure to evaluate PostSharp. But more than PostSharp I like to not need to use any AOP framework, of course ;-) A tool not used is a tool not adding complexity and a tool keeping your learning curve flat.)

So how and where to add logging to the indexing application? What about logging the .TXT files processed with the number of words extracted from them? This information is available between at the entry point to Build index.

Also we could log the total number of words indexed. That information is available as an output from Build index. It´s part of the IndexStats data flowing back to the “frontend”.

Here´s, what such an aspect means for an EBC design:

 image image

Simple, isn´t? An aspect is just another activity you insert into some flow. Where you do that, whether on the level of abstraction shown above, or on a lower level like so, that´s not important:

image

An aspect is just another activity. Usually, though, it´s a generic activity you can use in many context. Here´s the code for the Log aspect activity logging single messages:

public class Log<T>
{
    private readonly Func<T, string> createLogMessage;


    public Log(Func<T, string> createLogMessage)
    {
        this.createLogMessage = createLogMessage;
    }


    public virtual void In_Process(T data)
    {
        Write(createLogMessage(data));
        this.Out_Data(data);
    }

    public event Action<T> Out_Data;


    private void Write(string logMessage)
    {
        File.AppendAllLines("log.txt", new[]{string.Format("{0}\t{1}", DateTime.Now, logMessage)});
    }
}

The LogIEnumerable<T> Log aspect activity is looking similar.

Sure, this is very, very simple logging code. But if you like you can beef it up – without the contexts in which it is used needing any change. The Log aspect activity is simply plugged in between other activities. I chose to do that in the Build index composite activity:

public Build_index(Compile_words compileWords, Write_index_to_file writeIndexToFile)
{
    var joinIndexAndFilename = new Join<Index, string>();
    var logStats = new Log<IndexStats>(stats => string.Format("{0} words indexed", stats.WordCount));
    var logFileWords = new LogIEnumerable<Tuple<string, string[]>>(
fileWords => string.Format("{0}, {1} words",
fileWords.Item1, fileWords.Item2.Count()));

    this.in_Process = _ => logFileWords.In_Process(_);
    logFileWords.Out_Data += compileWords.In_Process;

    compileWords.Out_Statistics += logStats.In_Process;
    logStats.Out_Data += _ => this.Out_Statistics(_);
...

See how the data is running through the aspects? Before the data flowed right into Compile words:

this.in_Process = _ => compileWords.In_Process(_);

Now it´s first flowing into the Log aspect and then on to Compile words:

this.in_Process = _ => logFileWords.In_Process(_);
logFileWords.Out_Data += compileWords.In_Process;

A simple detour – of which the sending as well as the receiving activities are oblivious.

Adding a validation aspect

Next, how about adding some validation? So far the feature process represents the happy day case: all´s working well. But what if the directory to index does not exist? The application crashes with a DirectoryNotFound exception. Not good. Rather the program should check, if the directory does not exist and exit with an error return code.

Where to add the validation? Before Crawl directory tree is starts. I suggest to do it like this:

image

Again the aspect is just another activity inserted into the data flow. It´s implementation is generic. Other activities don´t realize there has been anything plugged in between them.

If validation became more complicated than in this scenario, you sure would implement a custom validation activity to separate domain validation rules from other concerns and the wiring code within the composite activity Compile files. But for the purpose of this post it´s sufficient to rely on a standard Validate activity like this:

public class Validate<T>
{
    private readonly Func<T, bool> validateData;
    private readonly Func<T, string> describeInvalidSituation;


    public Validate(Func<T, bool> validateData, Func<T, string> describeInvalidSituation)
    {
        this.validateData = validateData;
        this.describeInvalidSituation = describeInvalidSituation;
    }


    public void In_Validate(T data)
    {
        if (validateData(data))
            this.Out_ValidData(data);
        else
            this.Out_InvalidData(describeInvalidSituation(data));
    }


    public event Action<T> Out_ValidData;
    public event Action<string> Out_InvalidData = _ => { };
}

Using it is as simple a using the Log aspect activity. Just plug it in before Craw directory tree in Compile files:

public Compile_files(Crawl_directory_tree crawlDirTree)
{
    var split = new Split<Tuple<string, string>, string, string>(
        t => t.Item1,
        t => t.Item2
        );

    var validatePath = new Validate<string>(
        Directory.Exists,
        path => string.Format("Diretory to be indexed not found: {0}", path)
        );

    this.in_Process = _ => split.Input(_);
    split.Output0 += validatePath.In_Validate;
    validatePath.Out_ValidData += crawlDirTree.In_Process;
    validatePath.Out_InvalidData += _ => this.Out_ValidationError(_);
...

Instead of feeding the Split output to Crawl directory tree it flows into Validate. If the path exists, it´s passed on to Crawl directory tree; otherwise an error message is reported and the flow stops.

And what happens with the validation error? It´s logged and then the program stops by returning an error code:

static int Main(string[] args)
{
    int returnCode = 0;

    // Build
    ...
    var logValidationError = new Log<string>(errMsg => "*** " + errMsg);


    // Bind
    ...           
    compileFiles.Out_ValidationError += logValidationError.In_Process;
    logValidationError.Out_Data += _ =>
                                        {
                                            ...
                                            returnCode = 1;
                                        };

    ...

    // Run
    ...
 
    return returnCode;
}

Adding an exception handling aspect

The Validate aspect activity reports an invalid path through a dedicated output channel. If you want to change the course of feature process based on an validation error, you need to connect to that output. That´s an expected deviation from the normal cource of execution. But what about unexpected situations? What´s supposed to happen if and exception occurs somewhere down the feature process?

This is a mission for an exception handling aspect. Let me put one right before the first activity like this:

image

The aspect activity again ist very simple:

public class HandleException<T>
{
    public void In_Process(T data)
    {
        try
        {
            this.Out_Process(data);
        }
        catch (Exception ex)
        {
            this.Out_Exception(ex);
        }
    }


    public event Action<T> Out_Process;
    public event Action<Exception> Out_Exception = _ => { };
}

(Are you beginning to see the pattern across the aspects? ;-)

Here´s how it´s used at the entry point of the program. I´m wiring it into the feature process right before Compile files:

static int Main(string[] args)
{
    int returnCode = 0;

    // Build
    ...
    var logException = new Log<Exception>(ex => "*** " + ex.Message + '\n' + ex.StackTrace);

    var handleEx = new HandleException<Tuple<string, string>>();


    // Bind
    handleEx.Out_Process += compileFiles.In_Process;
    handleEx.Out_Exception += logException.In_Process;

    logException.Out_Data += _ =>
                                {
                                    Console.WriteLine("Aborted indexing due to exception!");
                                    returnCode = 1;
                                };

    ...

    // Run
    handleEx.In_Process(new Tuple<string, string>(args[0], args[1]));

    return returnCode;
}

Summary

That´s how easy it is to add aspects to EBC designs: just plug them in the data flow where you see fit. If you want to hide them, stuff them into a composite activity. You can even aggreate activities with aspects into larger composites.

The beauty of it is, that it´s completely transparent to either end of a data flow if you insert an aspect into it.

Also, with EBCs aspect become visible right where they are used. They are not just some column next to layers in a block diagram but activities with a purpose. If you like, you can even invent your own notation for them. Maybe you want them not to be in the way of the main data flow. So why not get them out of the way like this:

image

Invent your own little symbols to annotate data flows with. Why not?

I hope you´ve got feeling for how easy AOP really is with EBC. Next time I want to expand on this and show you how to introduce asynchronous processing the same way, so you can stop worrying about threads. Just wrap up the multi threading API of your choice into async coordination activities and you´re done. Ok, almost ;-) Asynchronous processing is inherently more difficult than synchronous processing. But at least EBC can help you to lessen the pain of basic multi-threading plumbing and give you a higher level programming paradigm.

Print | posted on Thursday, August 5, 2010 5:55 PM | Filed Under [ Event-Based Components ]

Feedback

No comments posted yet.
Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 

Powered by: