Composite Applications with NHibernate

On June 21, 2009, in Uncategorized, by derekgreer

Composite application architecture is an approach to software development which seeks to produce applications which can be constructed from pre-built components, thus allowing a single platform to be more easily customized for end users.

The pre-built components, or modules, comprising a composite application often share data access concerns such as the need to access a central database. The use of object-relational mapping frameworks are an increasingly common strategy for facilitating data access needs. This is due to their ability to minimize much of the repetitive, low-level data access coding often required by more traditional approaches, and their ability to abstract much of the idiosyncrasies of specific database vendors, providing greater potential for portability between vendors.

NHibernate, arguably the leading object-relational mapping framework for the .Net platform, facilitates object-relational mapping through a centrally configured component called the SessionFactory. A SessionFactory is configured with the connection parameters for a specific database along with the mapping meta-data required for associating objects to the relational data within the database.

It is generally advised that a single SessionFactory be created per database. This is both due to the overhead required in its creation, and in order to facilitate various caching strategies such as the caching of generated SQL statements, and of entities across sessions (referred to as the second-level cache).

For composite applications, configuration of the SessionFactory can pose a bit of an obstacle given that any centralization of the mapping configuration would tend to couple otherwise independent and optional modules together. Additionally, a centralized configuration of the mappings for each of the modules precludes encapsulation of the configuration needs within each module, and can introduce maintenance complexity for modules developed by separate development teams or organizations. On the other hand, allowing modules to maintain their own SessionFactory for a shared database instance introduces startup overhead and precludes taking advantage of the caching features of the framework.

As demonstrated in the remainder of this article, one approach to solving these challenges is to create a staged application bootstrapping process which both facilitates decentralized mapping registration and centralized SessionFactory initialization.

While potentially applicable in other usage scenarios, the following approach is based upon the use of Fluent NHibernate, the Prism composite application library, and the staged bootstrapping strategy presented within the article: Enhancing the Prism Module Initialization Lifecycle.

Our first step in demonstrating this strategy will be to create a module named DataModule which will serve as an infrastructure-only Prism module for configuring NHibernate. Using the ModuleBase implementation of IModule presented in the aforementioned article, the OnPreInitialization() and OnPostInitialization() methods will be overridden.

namespace CompositeSpike.Data.Module
{
    public class DataModule : ModuleBase
    {
        readonly IUnityContainer _container;

        public DataModule(IUnityContainer container)
        {
            _container = container;
        }

        protected override void OnPreInitialization()
        {
        }

        protected override void OnPostInitialization()
        {
        }
    }
}

The OnPreInitialization() method will be used to register services to be consumed by other modules while the OnPostInitialization phase will be used to perform the final database configuration.

Next, let’s create a CustomerModule which represents a module using NHibernate for its data access needs. This module need only override the OnInitialization() method to gain access to services registered during the Pre-Initialization bootstrapping phase.

namespace CompositeSpike.Data.Module
{
    public class CustomerModule : ModuleBase
    {
        readonly IUnityContainer _container;

        public DataModule(IUnityContainer container)
        {
            _container = container;
        }

        protected override void OnInitialization()
        {
        }
    }
}

Next, we’ll define a mapping service to be used by our CustomerModule for registering which map types and/or assemblies are to be included during the configuration of the SessionFactory.

namespace CompositeSpike.Data.Services
{
    public interface IMappingRegistryService
    {
        void AddMap() where T : IClassMap;
        void AddMap(Type classMap);
        void AddMapsFromAssemblyOf();
        void AddMapsFromAssembly(Assembly assembly);
    }
}

Next, we’ll provide an internal implementation of the mapping service to be contained within t
he DataModule. Two additional properties, Assemblies and Types, are provided to enable the DataModule to access any mapping types and assemblies registered by modules during the Initialization phase.

namespace CompositeSpike.Data.Module.Services
{
    class MappingRegistryService : IMappingRegistryService
    {
        readonly IList _assemblies;
        readonly List _types;

        public MappingRegistryService()
        {
            _assemblies = new List();
            _types = new List();
        }

        public IEnumerable Assemblies
        {
            get { return _assemblies; }
        }

        public IEnumerable Types
        {
            get { return _types; }
        }

        public void AddMap() where T : IClassMap
        {
            AddMap(typeof (T));
        }

        public void AddMap(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }

            if (!_types.Contains(type))
            {
                _types.Add(type);
            }
        }

        public void AddMapsFromAssemblyOf()
        {
            AddMapsFromAssembly(typeof (T).Assembly);
        }

        public void AddMapsFromAssembly(Assembly assembly)
        {
            if (!_assemblies.Contains(assembly))
            {
                _assemblies.Add(assembly);
            }
        }
    }
}

With the service defined, let’s return to the DataModule and complete the body of the initialization methods.

To ensure the mapping service is available during the initialization phase of the other modules within the application, the IMappingRegistryService and its internal implementation need to be registered as a singleton with the Unity container during the PreInitialization bootstrapping phase:

        protected override void OnPreInitialization()
        {
            _container.RegisterType(new ContainerControlledLifetimeManager());
        }

After modules have been given an opportunity to register mappings with our service during the Initialization phase, the service can be retrieved and used to supply the class map types and/or assemblies required for the Fluent NHibernate configuration:

        protected override void OnPostInitialization()
        {
            var service = _container.Resolve() as MappingRegistryService;

            ISessionFactory sessionFactory = Fluently.Configure()
                .Database(OracleConfiguration.Oracle9
                              .ConnectionString(
                              c => c.Is(ConfigurationManager.ConnectionStrings["example"].ConnectionString))
                              .ShowSql())
                .Mappings(m =>
                    {
                        service.Types.ForEach(t => m.FluentMappings.Add(t));

                        service.Assemblies.ForEach(a => m.FluentMappings.AddFromAssembly(a));
                    })
                .BuildSessionFactory();

            _container
                .RegisterInstance(new NHibernateDatabaseContext(sessionFactory),
                                  new ContainerControlledLifetimeManager());
        }

Our final step is to complete the initialization method within our CustomerModule to register any desired mappings to be used by the DataModule:

        protected override void OnInitialization()
        {
            var dataMappingService = _container.Resolve();
            if (dataMappingService != null) dataMappingService.AddMapsFromAssemblyOf();
        }

That concludes our example.

By utilizing a mapping service in conjunction with a multi-phased initialization strategy, a proper level of separation of concerns can be maintained for the modules within a composite application while at the same time utilizing NHibernate’s SessionFactory in the prescribed manner.

Tagged with:  

The Composite Application Library, better known as Prism, is a framework for developing composite-style applications for WPF and Silverlight. Prism facilitates simple module initialization upon application startup, but this is not always adequate for every application. This article discusses an enhanced approach for module initialization within Prism-based applications.

Within Prism, modules are identified by registering types which implement the IModule interface. This interface defines a single Initialize() method which provides an opportunity to perform any module-level configuration needed by the application. This might include such things as configuring a dependency injection container, registering views with the Region Manager, initializing a module-level Application Controller, and other module-level initialization needs.

The IModule interface is defined as follows:

    public interface IModule
    {
        void Initialize();
    }

While the specifics of module initialization can vary depending upon which dependency injection container (if any) is chosen, the initialization process followed by the UnityBoostrapper provided with Prism iterates over an enumeration of ModuleInfo instances derived from an instance of IModuleCatalog and delegates initialization to an instance of IModuleInitializer for each module. Within the default implementation of IModuleInitializer, the type derived from IModule is retrieved from the ModuleInfo instance, resolved through the Common Service Locator, and the IModule.Initialize() method is invoked on the resolved instance.

The following sequence diagram depicts a simplified view of this process:

Prism Initialization Sequence

While this process enables basic module initialization, it does not facilitate the ability to initialize modules which may have interdependencies with other modules. One example requiring a multi-phased initialization strategy would be the use of components which initialize application-scoped objects based upon information registered by other modules within the application. For instance, consider an infrastructure module whose purpose is to initialize a database context based upon object-relational mapping information specific to each module. To achieve this within a single-phased initialization strategy, the object-relational mapping information would need to be centralized in one location. This could negatively impact the overall decoupling provided by the Prism framework, and would violate the Open/Closed principle for registering information for newly created modules. With a multi-phased initialization strategy, such a component could register a service used as the object-relational mapping registry, and on a later phase retrieve any registered mapping information to initialize a single database context to be used later by all modules.

One approach to achieving a multi-phased initialization strategy would be to register custom implementations of the Prism types involved in the module initialization process. While this strategy is possible, there are a few disadvantages to this approach.

While many types used within the Prism library enable alternate implementations, the responsibility of initializing modules is not encapsulated by a single type, and the types which encapsulate the module initialization responsibility are not limited to this responsibility alone. To be fair, this statement is only partially true, and only realized upon attempting to replace the assumed single-phased strategy with a multi-phased strategy. The responsibility of initializing each module is assigned to an IModuleInitializer type, but unfortunately alternate implementations are limited to alternate single-phase strategies. This is because the responsibility of iterating over the module metadata resides within the IModuleManager type. Additionally, the methods responsible for iterating over the modules cannot be overridden within a derived type. Providing a new initialization strategy would thus require custom types to be implemented for both interfaces, or for the existing codebase to be modified.

It would be ideal if an alternate initialization strategy could be provided without modifying or rewriting existing framework code, but in lieu this, the existing single-phased strategy can still be leveraged to enable a separate initialization strategy.

While initially considering use of the EventAggregator to raise specific events representing each initialization phase, I chose to encapsulate the phase sequencing responsibilities within an IStagedSequenceService to arrive at a more cohesive, intention revealing, and reusable approach.

The IStagedSequenceService is declared as follows:

    /// <summary>
    /// Defines a service for providing a staged sequence of events.
    /// </summary>
    public interface IStagedSequenceService
    {
        void RegisterForStage(Action action, TStageEnum stage);
    }

The purpose of this interface is to allow components to register an Action delegate to be associated with a supplied generic enumeration stage.

The implementation of the IStagedSequenceService is declared as follows:

    /// <summary>
    /// Provides the default implementation for <see cref="IStagedSequenceService"/>.
    /// </summary>
    public class StagedSequenceService : IStagedSequenceService
    {
        readonly List[] stages;

        public StagedSequenceService()
        {
            stages = new List[NumberOfEnumValues()];

            for (int i = 0; i < stages.Length; ++i)
            {
                stages[i] = new List();
            }
        }

        public virtual void ProcessSequence()
        {
            foreach (var stage in stages)
            {
                foreach (var action in stage)
                {
                    action();
                }
            }
        }

        public virtual void RegisterForStage(Action action, TStageEnum stage)
        {
            stages[Convert.ToInt32(stage)].Add(action);
        }

        int NumberOfEnumValues()
        {
            return typeof (TStageEnum).GetFields(BindingFlags.PublicBindingFlags.Static).Length;
        }
    }

This implementation initializes a two-dimensional array based upon the number of stages in a sequence (i.e. the number of enumeration values representing the stages). The implementation of the RegisterForStage() method is then able to store each registered Action delegate in the appropriate collection for each stage. A ProcessSequence() method is supplied with a simple two-level loop which executes the registered delegates for each stage.

Next, an enumeration type is required to define the desired stages for the application:

    /// <summary>
    /// Defines stages used by modules requiring staged initialization.
    /// </summary>
    public enum ModuleInitializationStage
    {
        /// <summary>
        /// The preinitialization stage is used to perform initialization
        /// required before external module initialization.
        /// </summary>
        PreInitialization,

        /// <summary>
        /// The initialization stage is used to perform
        /// initialization required by the current module.
        /// </summary>
        Initialization,

        /// <summary>
        /// The post-initialization stage is used to perform
        /// initialization required after external module initialization.
        /// </summary>
        PostInitialization,

        /// <summary>
        /// The startup stage is used to perform perform initial
        /// tasks after all initialization is complete.
        /// </summary>
        StartUp
    }

Next, an IModuleInitializationService and implementation is defined specifying the ModuleInitializationStage enumeration:

    public interface IModuleInitializationService : IStagedSequenceService
    {
    }

    public class ModuleInitializationService : StagedSequenceService, IModuleLifecycleService
    {
        public virtual void Initialize()
        {
            base.ProcessSequence();
        }
    }

An Initialize() method is provided to wrap the ProcessSequence call. This is done to provide a more meaningful API to the specific context for this manifestation of the IStagedSequenceService.

To provide the base implementation required to take advantage of the new staged-initialization capabilities, a ModuleBase abstract class is defined which registers a protected virtual method corresponding to each of the defined stages:

    public abstract class ModuleBase : IModule
    {
        public void Initialize()
        {
            var moduleLifecycleService = ServiceLocator.Current.GetInstance();

            moduleLifecycleService.RegisterForStage(OnPreInitialization, ModuleInitializationStage.PreInitialization);

            moduleLifecycleService.RegisterForStage(OnInitialization, ModuleInitializationStage.Initialization);

            moduleLifecycleService.RegisterForStage(OnPostInitialization, ModuleInitializationStage.PostInitialization);

            moduleLifecycleService.RegisterForStage(OnStartUp, ModuleInitializationStage.StartUp);
        }

        protected virtual void OnPreInitialization()
        {
        }

        protected virtual void OnInitialization()
        {
        }

        protected virtual void OnPostInitialization()
        {
        }

        protected virtual void OnStartUp()
        {
        }
    }

To drive the new initialization strategy, the new ModuleInitializationSerivce is registered with the container and the UnityBootstrapper.InitializeModules() method is overridden in the main bootstrapper class to invoke the service’s Initialize() method:

    public class DesktopBootstrapper : UnityBootstrapper
    {
        // other methods

        protected override void ConfigureContainer()
        {
            _moduleInitializationService = new ModuleInitializationService();

            Container.RegisterInstance(_moduleInitializationService, new ContainerControlledLifetimeManager());

            // other configuration
        }

        protected override void InitializeModules()
        {
            base.InitializeModules();
            _moduleInitializationService.Initialize();
        }
    }

From here, modules requiring participation within the staged-initialized strategy need only implement the abstract ModuleBase class and override the appropriate method based upon the specific scenario required:

    public class ExampleModule : ModuleBase
    {
        protected override void OnPreInitialization()
        {
            // TODO Register services required by other modules.
        }

        protected override void OnInitialization()
        {
            // TODO perform initialization requiring services from other modules.
        }
    }

In conclusion, while the Composite Application Library does not provide a multi-phased initialization strategy, this can still be achieved by leveraging the existing behavior to facilitate a new strategy.

Tagged with: