Archive for May, 2012

Build an Event-driven Service Framework using AOP

4

Aspect Oriented Programming (AOP) is a very intriguing concept, but the terminology is not intuitive and discourages many developers. The intent of this article is not to provide an overview of AOP and the unique terminology, but provide an example of a reusable implementation using Spring.NET.

We can describe AOP as a technique to alter object behavior. After leading a recent enterprise project leveraging AOP, a team member best described AOP as object hijacking. We can hijack or intercept a client’s request without modifying the target object.

A few popular AOP examples are supporting database transactions, caching and logging. If you want to add a debug log entry for every call to a method then you can incorporate AOP to avoid coding the log call for every method. If you want to support data or object caching then AOP can perform the lookup and return the value without coding this common logic in every method. I would encourage you to explore AOP and the many features and benefits.

In this example (download), we will build a common event-driven services framework using AOP.  We can build a library of common services that we can easily reuse and associate with objects. The available events will be OnStart, OnComplete, OnError and OnSuccess.  So…we can add any service to any of the supported events. We will build a simple service implementation to write a console message, but the framework has no limits to the possibilities. The real benefit of the console service is to illustrate how the framework manages the object instantiation and method invocation with AOP.

We will follow some of the design patterns introduced during the How to design and build better software for tomorrow? series. First, we will create the object that we would like to hijack. We will start by creating the IJob interface as a contract for all implementations. The Execute method will be our target, which should contain the core logic of the Job implementations. The Services property will be a collection of IService that we would like to associate with the Job implementation.

01
namespace AOPEventDrivenServices.Logic
02
 {
03
 public interface IJob
04
   {
05
     /// <summary>
06
     /// Services invoked during events (e.g. OnStart, OnError, OnComplete 
07
     /// and OnSuccess) 
08
     /// Read-only forces additive behavior
09
     /// </summary>
10
     IList<IService> Services { get; }
11
 
12
     /// <summary>
13
     /// Execute method will begin the job execution, which should be called
14
     /// by the client
15
     /// </summary>
16
     void Execute();
17
 
18
   }
19
 }
20

The IService is the interface representing the event-driven services that will be associated with a Job implementation. The Invoke method is the core method, which will be called when invoking a service. The Event method will define the event for the service, which we previously stated includes OnStart, OnComplete, OnError and OnSuccess.

01
namespace AOPEventDrivenServices.Logic
02
 {
03
    public interface IService
04
    {
05
      /// <summary>
06
      /// Notification Event
07
      /// (e.g. OnError and OnSuccess)
08
      /// </summary>
09
      Events Event { get; set; }
10
 
11
      /// <summary>
12
      /// Invoke method will begin the service execution
13
      /// </summary>
14
      void Invoke();
15
    }
16
 }
17

We will wire everything together using the Spring.NET IoC container, so we can define the objects using dependency injection.

01
    <!-- Context resource section for common objects -->
02
    <objects xmlns="http://www.springframework.net">
03
      <!-- Register Job Service Post Processor object -->
04
      <object type="AOPEventDrivenServices.Spring.JobServicePostProcessor,
05
                    AOPEventDrivenServices.Spring" />
06
      <object id="TestJob"
07
              type="AOPEventDrivenServices.Logic.TestJob,
08
                    AOPEventDrivenServices.Logic">
09
        <property name ="Services">
10
          <list element-type="AOPEventDrivenServices.Logic.IService,
11
                              AOPEventDrivenServices.Logic">
12
            <object type="AOPEventDrivenServices.Logic.ConsoleService,
13
                          AOPEventDrivenServices.Logic">
14
              <property name="Event"
15
                        value="OnStart"/>
16
              <property name="Message"
17
                        value="ConsoleService OnStart message #1"/>
18
            </object>
19
            <object type="AOPEventDrivenServices.Logic.ConsoleService, 
20
                          AOPEventDrivenServices.Logic">
21
              <property name="Event"
22
                        value="OnSuccess"/>
23
              <property name="Message"
24
                        value="ConsoleService OnSuccess message"/>
25
            </object>
26
            <object type="AOPEventDrivenServices.Logic.ConsoleService,
27
                          AOPEventDrivenServices.Logic">
28
              <property name="Event"
29
                        value="OnComplete"/>
30
              <property name="Message"
31
                        value="ConsoleService OnComplete message"/>
32
            </object>
33
            <object type="AOPEventDrivenServices.Logic.ConsoleService,
34
                          AOPEventDrivenServices.Logic">
35
              <property name="Event"
36
                        value="OnStart"/>
37
              <property name="Message"
38
                        value="ConsoleService OnStart message #2"/>
39
            </object>
40
          </list>
41
        </property>
42
      </object>                
43
    </objects>
44

Since we do not want the client to be responsible for handling the services, we will create a Spring.NET object post processor to manage the details and return an AOP proxy object. This object is invoked after the Spring.NET IoC container instantiates and initializes an object. The Spring.NET IoC contains many hooks to customize or extend the behavior, so you have visibility and control over every step of the process. We built a custom IObjectPostProcessor to receive a callback for each object instance created by the container. At this point, we wrapped the object with AOP proxy.  We are using the AOP Alliance interface for around advice using method interception, which is supported using the ServiceHandler object. The JobServicePostProcessor replaces the standard object instance with the proxy using the ServiceHandler advice type. When a client invokes the Execute method on the returned object, the proxy will intercept the call and redirect to the ServiceHandler Invoke method to manage our event-driven services.

01
using System;
02
using System.Collections.Generic;
03
using System.Text;
04
using Spring.Objects.Factory.Config;
05
using Spring.Aop.Support;
06
using Spring.Aop.Framework;
07
using AOPEventDrivenServices.Logic;
08
 
09
namespace AOPEventDrivenServices.Spring
10
{
11
    /// <summary>
12
    /// JobServicePostProcessor is a Spring Object Post Processor 
13
    /// responsible for creating a proxy/wrapper object for all
14
    /// IJob type objects supporting event-driven services. 
15
    /// The ServiceHandler manages all events as an AOP method
16
    /// interceptor advice.
17
    /// The IJob Execute method is the single pointcut, which 
18
    /// means only calls to this method are supported.
19
    /// NOTE: the JobServicePostProcessor must be added to the
20
    /// application context. We recommend the spring/root of the
21
    /// app or web configuration files. If you use the
22
    /// IApplicationContext then the JobServicePostProcessor will
23
    /// be registered without additional steps.
24
    /// </summary>
25
    public class JobServicePostProcessor : IObjectPostProcessor 
26
    {
27
        public JobServicePostProcessor() { }
28
 
29
        /// <summary>
30
        /// Not implemented - just return current object
31
        /// </summary>
32
        /// <param name="instance"></param>
33
        /// <param name="name"></param>
34
        /// <returns></returns>
35
        public object PostProcessBeforeInitialization(object instance,
36
                                                      string name)
37
        {
38
            return instance;
39
        }
40
 
41
        /// <summary>
42
        /// Return wrapper AOP object for all IJob types requesting Services
43
        /// </summary>
44
        /// <param name="instance"></param>
45
        /// <param name="name"></param>
46
        /// <returns></returns>
47
        public object PostProcessAfterInitialization(object instance,
48
                                                     string name)
49
        {
50
            //check object to make sure it is of the IJob type
51
            if (instance.GetType().GetInterface("IJob") != null)
52
            {
53
                Console.WriteLine("JobServicePostProcessor initialization");
54
                IJob job = (IJob)instance;
55
                //check if Services collection property contains any items
56
                if (job.Services != null)
57
                {
58
                    //the Job implementation contains one or more services,
59
                    //so let's hijack and return
60
                    //a proxy to handle the service invocations
61
                    if (job.Services.Count != 0)
62
                    {
63
                        //Create proxy object with AOP wrapper
64
                        ProxyFactory factory = new ProxyFactory(job);
65
                        //Add Advisors with associated Advice using Execute
66
                        //method as the pointcut 
67
                        factory.AddAdvisor(new DefaultPointcutAdvisor(
68
                                           new SdkRegularExpressionMethodPointcut("Execute"),
69
                                           new ServiceHandler()));                            
70
                        //return the proxy/wrapper object when requested
71
                        Console.WriteLine("JobServicePostProcessor creating proxy for "
72
                                          + job.GetType().Name);
73
                        return (IJob)factory.GetProxy();
74
                    }
75
                }
76
            }
77
            //object does not contain any services - return object as is
78
            return instance;
79
        }
80
    }
81
}
82

01
using System;
02
using System.Collections.Generic;
03
using AOPEventDrivenServices.Logic;
04
using Spring.Aop;
05
using System.Reflection;
06
using AopAlliance.Intercept;
07
 
08
namespace AOPEventDrivenServices.Spring
09
{
10
    /// <summary>
11
    /// ServiceHandler is an AOP method interceptor advice responsible
12
    /// for processing all service requests. The 
13
    /// JobServicesPostProcessor should be active to create the Job
14
    /// proxy object supporting AOP. This handler
15
    /// hijacks the call to the Execute method and provides support
16
    /// for all events.
17
    /// OnStart - services are processed before the Execute method
18
    /// is invoked
19
    /// OnSuccess - services are processed after the Execute method
20
    /// is invoked and when it was successful
21
    /// OnError - services are processed after the Execute method
22
    /// is invoked and when an exception was encountered
23
    /// OnComplete - services are processed after the Execute method is
24
    /// invoked and when the OnError or OnSuccess services are processed
25
    /// NOTE: services that throw an exception will force the process to
26
    /// skip all subsequent OnStart and OnSuccess services
27
    /// NOTE: OnStart services that throw an exception will also cause
28
    /// the Execute method not to be invoked, so the job will not be launched
29
    /// </summary>
30
    public class ServiceHandler : IMethodInterceptor
31
    {
32
        private IList<IService> colOnStart = new List<IService>();
33
        private IList<IService> colOnComplete = new List<IService>();
34
        private IList<IService> colOnSuccess = new List<IService>();
35
        private IList<IService> colOnError = new List<IService>();
36
 
37
        /// <summary>
38
        /// MethodInterceptor method for hijacking and processing all services
39
        /// </summary>
40
        /// <param name="invocation"></param>
41
        /// <returns></returns>
42
        public object Invoke(IMethodInvocation invocation)
43
        {
44
            //check object to make sure it is of the IJob type - 
45
            //this validation is also part of the JobServicePostProcessor
46
            if (invocation.TargetType.GetInterface("IJob") != null)
47
            {
48
                Console.WriteLine("ServiceHandler invoke");
49
                IJob job = (IJob)invocation.Target;
50
                if (job.Services != null)
51
                {
52
                    PopulateEventCollections(job.Services);
53
                    try
54
                    {
55
                        //OnStart event
56
                        Console.WriteLine("ServiceHandler OnStart event");
57
                        SendAll(colOnStart);
58
 
59
                        //proceed with invoking the Execute method
60
                        invocation.Proceed();
61
 
62
                        //OnSuccess event
63
                        Console.WriteLine("ServiceHandler OnSuccess event");
64
                        SendAll(colOnSuccess);
65
                    }
66
                    catch
67
                    {
68
                        //OnError event
69
                        Console.WriteLine("ServiceHandler OnError event");
70
                        SendAll(colOnError);
71
                        throw;
72
                    }
73
                    finally
74
                    {
75
                        //OnComplete event
76
                        Console.WriteLine("ServiceHandler OnComplete event");
77
                        SendAll(colOnComplete);
78
                    }
79
                    return null;
80
                }
81
            }
82
            //no services requested or does not qualify
83
            return invocation.Proceed();
84
        }
85
...
86
    }
87
}
88

A simple console client will create the Job object and call the Execute method. The client is unaware the call is hijacked and the appropriate services are invoked. We included console messages, so you can follow the workflow.

01
using System;
02
using System.Collections.Generic;
03
using System.Text;
04
using Spring.Context;
05
using Spring.Context.Support;
06
using AOPEventDrivenServices.Logic;
07
 
08
namespace AOPEventDrivenServices.UI
09
{
10
    class AOPEventDrivenServicesClient
11
    {
12
        static void Main(string[] args)
13
        {
14
            try
15
            {
16
                Console.WriteLine("*** Start Client ***");
17
                //Load DI context from app.config
18
                Console.WriteLine("Client starts IoC container");
19
                IApplicationContext context = ContextRegistry.GetContext();
20
                //Get object from DI container
21
                Console.WriteLine("Client request object from IoC container");
22
                IJob job = (IJob)context.GetObject("TestJob");
23
                Console.WriteLine("Client job.Execute() call");
24
                job.Execute();
25
                Console.WriteLine("*** End Client ***");
26
            }
27
            catch (Exception e)
28
            {
29
                Console.WriteLine("Client Error: " + e.Message); 
30
            }
31
        }
32
    }
33
}
34

AOPEventDrivenServiceClient Output

AOPEventDrivenServiceClient Output

You can download the source code and explore the project. This implementation will allow you to add a variety of services and associate them with one or more Job implementations. The services can also be invoked at any of the defined events with no additional coding. The Spring IoC container and dependency injection are the heavy lifters for this feature.

This will allow you to add new features without changing the implementation and separate services that should not be tightly coupled with a Job. For example, if you had multiple jobs that copied files to/from a file server then you can build a common service to handle this process. You can add this service to one or more Job implementations. We discussed a few popular implementations for AOP, which can be built using this event-driven service implementation. This includes database transactions, caching and logging services. In my recent project, we built common services to email, FTP, manage database transactions, log entries, run SQL scripts and many other domain specific processes.

I hope you find this simple AOP implementation useful and can apply the event-driven service framework to your project.

Source Code

Part V: Brick Design – How to design and build better software for tomorrow?

This series is an adaption of my “Getting Started with Dependency Injection using Spring.NET” presentation, which was a session available at the Orlando .NET Code Camp 2012. Since I received invaluable participation and feedback from the attendees, this post will include expanded material and code examples.

In Part IV, we refactored our prototype design and introduced the Stick Contact Manager. We applied several design patterns, which established a solid foundation for well-designed software. The design patterns and principles include Dependency Injection Principle (DIP), Separation of Concerns (SoC), Dependency Injection (DI) and Single Responsibility Principle (SRP). The Stick Contact Manager scored high marks during our evaluation, although we discovered one frustrating side-effect of the design – the client still must have knowledge of how to create the dependencies. We successfully eliminated the dependency creation throughout the majority of the objects, but we are forced to write significant code to create the dependencies.

The solution is centralizing the object creation or instantiation, so the client can delegate the task and knowledge of all the dependencies to another entity. This can also promote reusability, so the clients or consumers can also delegate this task to the same entity. This would eliminate the need for redundant client code required to create the dependencies.

So…let’s revisit the Stick Contact Manager client, which is responsible for creating the ContactsService object. Since we applied the Dependency Injection pattern, the dependencies are injected via the constructor.

01
using System;
02
using System.Text;
03
using System.Configuration;
04
using ContactManager.Common.Model;
05
using ContactManager.Common.Services;
06
using ContactManager.Common.Repository;
07
namespace StickContactManager
08
{
09
    class ContactManager
10
    {
11
        static void Main(string[] args)
12
        {
13
            try
14
            {
15
                Console.WriteLine("*** StickContactManager ***");
16
                Contact contact = new Contact("John Doe",
17
                                              "jdoe@somewhere.com",
18
                                              "407123456");
19
 
20
                //get common properties from app.config
21
                string smtpHost = ConfigurationManager.AppSettings["Smtp.Host"];
22
                string emailSender = ConfigurationManager.AppSettings["Email.Sender"];
23
 
24
                //initialize dependencies
25
                IContactRepository contactRepository = new ContactRepository();
26
                INotificationService notificationService;
27
 
28
                //notificationService = new EmailService(smtpHost,
29
                //                                       emailSender,
30
                //                                       "Hello!",
31
                //                                       "Hello Email Contact!");
32
                notificationService = new SmsService("Hello Text Contact!");
33
 
34
                //inject dependencies via constructor
35
                ContactsService service = new ContactsService(contactRepository,
36
                                                              notificationService);
37
                service.AddContact(contact);
38
            }
39
            catch (Exception e)
40
            {
41
                Console.WriteLine(e.Message);
42
            }
43
        }
44
    }
45
}
46

As discussed previously, the above Stick Contact Manager client still must have knowledge of how to create the dependencies. As an alternative, we will introduce Inversion of Control (IoC) for our centralized object creation solution. An IoC container will handle the object creation, so now we can remove the majority of the code and just request the object. In general, IoC inverts the relationship by the client delegating control to the container.

The following are a few popular IoC frameworks, which are listed below.

  • Castle Windsor
  • Ninject
  • Spring.NET
  • StructureMap
  • Microsoft Unity

This is really a pick your flavor, so I would recommend researching and prototyping. In general, they all provide a core framework for a container.

I have participated in several projects that utilized Dependency Injection with IoC, so I have some experience with many of the popular frameworks. Since I prefer the benefits of a module based enterprise application framework, I selected Spring.NET for the job. I will present future blogs on the many features of the Spring.NET and provide some great tricks for building enterprise applications using this powerful framework.

So…let’s refactor the client for the Brick Contact Manager to call the Spring.NET IoC container and request the ContactsService object (download source code). The container will instantiate the object and injects all dependencies, so the object is ready to use without additional initialization.

01
using System;
02
using System.Text;
03
using ContactManager.Common.Model;
04
using ContactManager.Common.Services;
05
using Spring.Context;
06
using Spring.Context.Support;
07
namespace BrickContactManager.UI
08
{
09
    class ContactManager
10
    {
11
        static void Main(string[] args)
12
        {
13
            try
14
            {
15
                Console.WriteLine("*** BrickContactManager ***");
16
                Contact contact = new Contact("John Doe",
17
                                  "jdoe@somewhere.com",
18
                                  "407123456");
19
 
20
                //set reference to IoC container
21
                IApplicationContext context = ContextRegistry.GetContext();
22
                //request object from container - client is no longer aware of
23
                //how to construct the object and its' dependencies!!!
24
                ContactsService service = (ContactsService)context.GetObject("ContactsService");
25
 
26
                service.AddContact(contact);
27
            }
28
            catch (Exception e)
29
            {
30
                Console.WriteLine(e.Message);
31
            }
32
        }
33
    }
34
}
35

It was just a few lines of code for the client and we significantly reduced the clutter regarding the instantiation of the objects, which has been delegated to the Spring.NET IoC container. How does the container know how to create the objects? Well, we will need to provide the instructions to the container. We will select the xml metadata configuration approach, so the object definition is contained in an external xml file. I prefer this approach, so we do not encumber the objects with code. This would tightly couple our objects to a particular implementation, where Spring.NET does not require the objects to contain any specific code (i.e. our ContactManager library will require no changes). So…let’s look at the object definitions in the xml metadata files.

01
<?xml version="1.0" encoding="utf-8" ?>
02
<objects xmlns="http://www.springframework.net">
03
 
04
  <!-- constructor-based injection -->
05
  <object id="EmailNotificationService"
06
          type="ContactManager.Common.Services.EmailService,
07
                ContactManager.Common">
08
    <constructor-arg name="smtpHost"  value="${Smtp.Host}" />
09
    <constructor-arg name="sender"    value="${Email.Sender}" />
10
    <constructor-arg name="subject"   value="Hello!" />
11
    <constructor-arg name="message"   value="Hello Email Contact!" />
12
  </object>
13
 
14
 
15
  <object id="SmsNotificationService"
16
          type="ContactManager.Common.Services.SmsService,
17
                ContactManager.Common">
18
    <constructor-arg index="0" value="Hello Text Contact!" />
19
  </object>
20
 
21
</objects>

01
<?xml version="1.0" encoding="utf-8" ?>
02
<objects xmlns="http://www.springframework.net">
03
 
04
  <object id="ContactsService"
05
          type="ContactManager.Common.Services.ContactsService,
06
                ContactManager.Common">
07
    <!-- inline object -->
08
    <constructor-arg name="contactRepository">
09
      <object type="ContactManager.Common.Repository.ContactRepository,
10
                    ContactManager.Common" />
11
    </constructor-arg>
12
    <constructor-arg name="notificationService"
13
                     ref="SmsNotificationService" />
14
  </object>
15
 
16
</objects>

The element object contains the instructions to instantiate and initialize an object. The id or name attribute is the  reference name, which is the value that is passed when requesting an object from the IoC container. The type attribute is the namespace qualified class name and fully qualified assembly name. The primary object child elements include the constructor-arg and property, which provide constructor and setter based dependency injection techniques. There are several options to the constructor element, which include name and index attributes. The container will attempt to match the signature of the appropriate constructor, so providing the correct name or index of the parameters is important. The value attribute is the static value, expression or placeholder. In the example above, the ${Smtp.Host} value is replaced with the configuration appsettings value. The ref attribute identifies the name or id of another object element, so the value is set to this object.

Our Brick Contact Manager xml metadata example is only scratching the surface of the options available for defining your objects. The following is a quick reference of the object configuration and the popular elements and attributes supporting instantiation, inheritance, lifecycle, scope and the injection types (setter and property).

Spring.NET Configuration Metadata

Spring.NET Configuration Metadata

The choice of the dependency injection type (i.e. constructor, setter or method) is sometimes a personal choice. I prefer defining a constructor for mandatory dependencies and setter properties for optional items. If you have too many mandatory items then your constructors become difficult to manage and configure using IoC.

We will also add some configuration to our app.config file. The spring context section identifies the location of the external configuration metadata, so the container can load the object definitions and be ready to return the correct object to the client.

Spring.NET Application Configuration

Spring.NET Application Configuration

 

The application configuration file (app.config) is the best location to initialize your IoC container. The spring section group defines the context handler, which is the service that reads and processed the xml metadata for the container. The DefaultSectionHandler is optional, which allows you to include xml metadata within the application configuration file. The spring section group appears in the application configuration file, which contains the context element. If you included the DefaultSectionHandler then you will also have the objects element within the spring section. The context element contains the child elements defining one or more resource elements with the uri attribute identifying the location of the xml metadata. The uri can be an external file, assembly or config element. We have the VariablePlaceholderConfigurer object defined, which defines the variable sources for the placeholders. There are several out-of-the-box variable sources including .NET configuration files, environment variables, command-line arguments, registry and connecton string configuration sections. The final object example is the MailBase, which is an abstract object used for setting properties for all mail concrete objects using the parent attribute. See the xml metadata configuration information for the example.

Unfortunately, Spring.NET provides a robust set of IoC features that are too many to present in this post. You can download the Brick Contact Manager and review the code. You will find making changes is simple. You can also download the Spring.NET Visual Studio add-in, so changing the xml metadata is even easier with intellisense.

In future posts, I will introduce additional Spring.NET features including Spring.Web, Spring.MVC, Aspect Oriented Programming (AOP) and extending the framework. You can visit http://www.springframework.net/ to learn more or download the latest open source version. Spring is sustained by SpringSource, which is a division of VMWare.

The IoC alternatives including Service Locator and Factory Design patterns also provide solutions for centralizing the object creation. The Factory Design hides the complexities with a factory object that contains static methods returning the instantiated object. The Service Locator is also a central repository, which the client passes a key to retrieve an object. These options also work well with the core Dependency Injection Principle (DIP), Separation of Concern (SoC), Dependency Injection (DI) and Single Responsibility Principle (SRP). I always recommend exploring your options and pick the approach that works best for your project.

In summary, the Brick Contact Manager complements the Stick Contact Manager and solves the object creation responsibility problem. I have found that following the software design principles we have defined in Part II and implementing the patterns in Part IV are a good defense against premature end-of-life scenario for your applications. The addition of an IoC container will further promote the software design principles and add significant value to your product. You will find maintenance and change requests will be less painful.

I hope you found this series helpful and will be able to follow the concepts presented with your next project. I still receive messages from colleagues of previous projects as they continue to promote the design patterns and practices discussed in this series. If you are currently working a project then take a few minutes and perform an evaluation using our software design principles. Would your project benefit from the design patterns and concepts?

Source Code

Previous: Part IV: Stick Contact Manager

Java Version: Part VI: Brick Contact Manager

Go to Top