A Default DDD Architecture with Auto-mapping Domain Entities

Download source:
up-to-date src at github

Introduction

The idea of this article is to develop a small tiny bare minimum framework in ASP.NET for any start-up project who wishes to embrace the domain driven design. Also you can easily plug this as part of your large framework. It basically tries to remove those tasks of a DDD project when used with an ORM as NHibernate which most developers find as tedious:
  • Writing and/or generating hbm mapping files for each domain entity
  • Generate corresponding ddl queries and run against the database
  • Write repositories for CRUD functionality on entities
A similar feature rich product is LightSpeed which has its own ORM. However, we will be using the most comprehensive ORM NHibernate and we can also easily use complex hbm mapping for legacy databases with our framework.
With the framework in place, one has to write only the domain model and can run CRUD operations on those using repositories provided.
The framework will provide these by automatic mapping of domain entities using Fluent NHibernate with an extendable predefined set of conventions. We will be able to easily switch between database(i.e compact to sql server) by merely changing NHibernate connection settings as the ORM will do the translation part for us. We will build a demo MVC 3 application on it demonstrating how we can start developing application by writing only the domain model, controllers and views and override of default mapping of a model. We will implement a database schema change management flow as the project evolves and the model changes.

Background

Seems like everybody is interested in developing the domain driven way. In the .Net community the power tools are the ASP.NET MVC 3 for presentation layer framework, NHibernate for ORM, Castle or spring for IOC, log4net for logging and so on. I assume reader is at least familiar with not all of these frameworks but at least ASP.NET MVC and the buzzword Domain Driven Design.
Although we can easily start a MVC 3 project from Visual Studio 2010 and write the models, and test CRUD functionality using mock frameworks like rhino mocks, but for a prototype built with all the actual persistence mechanism in place from the start would make us feel better, I believe.
And before start coding let me just give you a picture of the dependencies of our to be  developed project:
New Picture
Fig: generated by VS 2010 from Architecture->Generate Dependency Graph->by assembly from the solution
Note, MyFramework is a stand alone project.

Using the code

Environment Requirements: VS 2010 SP1, SQL COMPACT EDITION 4.0
First let me show the solution structure:
clip_image001[9]
MyFramework - class library project which we are going to write. Stands alone, has no dependency on other projects.
NHAutoMvcDemo - demo MVC 3 application built to demonstrate using the framework.
MyTest - is a MS test project used here to generate and update/recreate the database schema



 







MyFramework – In VS 2010 We start with a class library project. As we will be using the automapping feature from fluent NHibernate we need to add reference to it through nuget. I used the Tools->Library Package Manger->Package Manager Console to install using command: PM> Install-Package FluentNHibernate
because the manage nuget user interface does not show the latest version 1.3 which supports NHibernate 3.2. From GUI interface it shows the version 1.2 which supports only upto NHibernate 3.1

In order to identify our entities (models which will be persisted) we define an abstract base class DomainEntiy.
   1: namespace MyFrameWork.Domain
   2: {
   3:     public abstract class DomainEntity
   4:     {        
   5:         public virtual long Id { get; set; }
   6:  
   7:         public virtual string CreatedBy { get; set; }
   8:         public virtual DateTime CreatedAt { get; set; }
   9:         public virtual  string LastUpdatedBy { get; set; }
  10:         public virtual DateTime LastUpdatedAt { get; set; }
  11:  
  12:         protected DomainEntity()
  13:         {
  14:             this.CreatedAt = DateTime.Now;
  15:             this.LastUpdatedAt = DateTime.Now;
  16:         }
  17:     }
  18: }

A class needs to derive from it in order to identify itself as a domain entity thus mapped to database table. This is the clue we will use (typeof(DomainEntity)) to identify entities and generate auto-mapping based on our conventions. This is also a good practice as we know our domain model will be Plain Old CLR Object (POCO), hence need not derive from any other special class. Also this class hosts some audit information which we desire to persist on every modification of entities.

We are not using the ClassMap<T> nor the hbmMapping xml here. Please note, NHibernate 3.2 already provides a similar classmap feature without Fluent NHibernate but I believe not the Fluent automapping. So, We will use fluent NHibernate’s defaultautomappingconfiguration as:


   1: using System;
   2: using FluentNHibernate;
   3: using FluentNHibernate.Automapping;
   4: using MyFrameWork.Domain;
   5:  
   6: namespace MyFrameWork.NHib
   7: {
   8:     public class FrameworkMappingConfiguration : DefaultAutomappingConfiguration
   9:     {
  10:         public override bool AbstractClassIsLayerSupertype(Type type)
  11:         {
  12:             return type == typeof(DomainEntity);
  13:         }
  14:  
  15:         public override bool IsId(Member member)
  16:         {
  17:             return member.Name == "Id";
  18:         }
  19:  
  20:         public override bool ShouldMap(Type type)
  21:         {
  22:             return
  23:                 type.BaseType == typeof(DomainEntity);
  24:         }
  25:  
  26:         public override bool ShouldMap(Member member)
  27:         {
  28:             return base.ShouldMap(member) && member.CanWrite;
  29:         } 
  30:     }
  31: }

This class defines which class type we should map, that is our entity which should derive from MyFramework.Domain.DomainEntity so we check type.BaseType == typeof(DomainEntity). It also defines how to recognize the ID field of entities.  Note we will not use this implementation to configure fluent map. Rather we will create a persistent model generator. The purpose of the model generator would be to setup some conventions

Model generator – In First step we create an interface IAutoPersistenceModelGenerator and provide a default implementation. This interface also works as the plugging point for the consumer of the framework to provide its own implementation.

We write the conventions which resides in Conventions folder.

Finally our implementation of the model generator which sets up the conventions along with our Mapping configuration implementation.


   1: using System.Collections.Generic;
   2: using System.Reflection;
   3: using System;
   4: using FluentNHibernate.Automapping;
   5: using FluentNHibernate.Conventions;
   6: using MyFrameWork.Domain;
   7: using MyFrameWork.NHib.Conventions;
   8:  
   9: namespace MyFrameWork.NHib
  10: {
  11:     public class AutoPersistenceModelGenerator : IAutoPersistenceModelGenerator
  12:     {
  13:         public List<Assembly> AutoMappingAssemblies { get; set; }
  14:         public Assembly OverrideAssembly { get; set; }
  15:  
  16:         public AutoPersistenceModelGenerator()
  17:         {
  18:             AutoMappingAssemblies = new List<Assembly>();
  19:             OverrideAssembly = Assembly.GetAssembly(typeof (DomainEntity));
  20:         }        
  21:  
  22:         public AutoPersistenceModel Generate()
  23:         {
  24:             return
  25:                 AutoMap.Assemblies(new FrameworkMappingConfiguration(), this.AutoMappingAssemblies)
  26:                     .Conventions.Setup(this.GetConventions())
  27:                     .IgnoreBase<DomainEntity>()
  28:                     .UseOverridesFromAssembly(this.OverrideAssembly);
  29:         }
  30:  
  31:         private Action<IConventionFinder> GetConventions()
  32:         {
  33:             return c =>
  34:                 {
  35:                     c.Add<PrimaryKeyConvention>();
  36:                     c.Add<HasManyConvention>();
  37:                     c.Add<TableNameConvention>();                    
  38:                 };
  39:         }
  40:     }
  41: }

Now we are all set with the mapping configuration and conventions for automapping. So we implement the static NHibernateSession object to get the ISession.

Configuration

For brevity our config settings are taken from AppSettings like this:


   1: <appSettings>
   2:         <add key="MappingAssemblies" value="NHAutoMvcDemo;MyFrameWork" />        
   3:         <add key="OverrideAssembly" value="NHAutoMvcDemo" />
   4:         <add key="NHibConfigFile" value="nHibernate.config" />
   5:         <add key="HbmExportPath" value="C:\" />
   6:     </appSettings>

Here MappingAssemblies are assemblies which should be automapped and OverrideAssembly is the assembly from which we will use any default automap override for any number of entities. We see an example later about this in the demo app.

And as mentioned as NHibConfigFile, we will use NHibernate connection settings from nHibernate.config like:


   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
   3:     <session-factory>
   4:         <property name="connection.connection_string">Data Source=C:\Users\latifur.rahman\Documents\Visual Studio 2010\Projects\DefaultDDDArch\NHAutoMvcDemo\App_Data\Demo_DB.sdf;</property>
   5:         <property name="dialect">NHibernate.Dialect.MsSqlCeDialect</property>
   6:         <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
   7:         <property name="connection.driver_class">NHibernate.Driver.SqlServerCeDriver</property>
   8:         <property name="show_sql">true</property>
   9:         <property name="connection.release_mode">on_close</property>        
  10:  
  11:         <!-- hbm2ddl tool property should NOT be used in production and is here to get you 
  12:         going with the Cookbook! -->
  13:         <!--<property name="hbm2ddl.auto">update</property>-->
  14:         <!-- Mapping assemblies -->
  15:         <!-- Can't map it for Fluent NHibernate here; instead, load the mapping assembly in Global.asax.cs.
  16:             If you're still using HBMs, you can use the mapping here or pass the assembly via Global.asax.cs
  17:             as well, just like you can do with the Fluent NHibernate assembly(s). -->
  18:     </session-factory>
  19: </hibernate-configuration>

The <property name="connection.release_mode">on_close</property> is for a hibernate bug when the database is sql compact.

Generic Repository
Finally we implement a minimal generic repository for CRUD functionality of entities.

Its pretty simple like :


   1: using System.Collections.Generic;
   2: using MyFrameWork.Domain;
   3:  
   4: namespace MyFrameWork.Repository.Contract
   5: {
   6:     public interface IRepository<T> where T : DomainEntity
   7:     {        
   8:         IList<T> GetAll();
   9:  
  10:         T Get(long id);
  11:         
  12:         T SaveOrUpdate(T entity);
  13:         void Delete(T entity);
  14:  
  15:         void Delete(long id);
  16:     }
  17: }

And the implementation:


   1: #region REFERENCES
   2: using System.Collections.Generic;
   3: using MyFrameWork.Domain;
   4: using MyFrameWork.NHib;
   5: using MyFrameWork.Repository.Contract;
   6: using NHibernate;
   7:  
   8: #endregion REFERENCES
   9:  
  10: namespace MyFrameWork.Repository
  11: {
  12:     public class NhRepository<T> : IRepository<T> where T:DomainEntity
  13:     {
  14:         ISession Session { 
  15:             get {
  16:                 return NHibernateSession.Initialize(null);
  17:             } 
  18:         }
  19:  
  20:         public virtual IList<T> GetAll()
  21:         {
  22:             ICriteria criteria = this.Session.CreateCriteria(typeof(T));
  23:             return criteria.List<T>();
  24:         }
  25:  
  26:         public T Get(long id)
  27:         {
  28:             return this.Session.Get<T>(id);
  29:         }        
  30:  
  31:         public T SaveOrUpdate(T entity)
  32:         {
  33:             ISession session = this.Session;
  34:             session.SaveOrUpdate(entity);
  35:             session.Flush();
  36:  
  37:             return entity;
  38:         }
  39:  
  40:         public void Delete(T entity)
  41:         {
  42:             this.Session.Delete(entity);
  43:         }
  44:  
  45:         public void Delete(long id)
  46:         {
  47:             ISession session = this.Session;
  48:             
  49:             session.Delete(session.Get<T>(id));
  50:             session.Flush();
  51:         } 
  52:     }
  53: }

Note, we only provided the SaveOrUpdate, GetAll and Get. Also the consumer can override this default implementation to add search query features.

Also for every crud operations, we are configuring, initializing and building the sessionAnnoyed
In ideal case the session would be initialized once in app_start and previously opened session will be reused. But I saved that for another postWinking smile

Demo MVC Application

Now we create an MVC 3 app with VS 2010 project template. The structure would be like this:

clip_image001
 

Entities:


Simple Bookstore Domain with with three entities: Author, Book, Publisher.
An author has written multiple books, A book can have multiple authors and a book must have a Publisher. A Publisher publishes multiple books.

Controllers:


Just added PublisherController to crud Publishers. And StoreController to add books with publisher relation here. Note, we haven’t deal with Author for brevity.

 


 


 


 


 


 


 


 


Mapping Override:

We demonstrate the override by making the Book.BookDescription property length as 2000 instead of default mapping generated length as nvarchar(255).

This is done at BookMappingOverride.cs


   1: namespace NHAutoMvcDemo.DomainModels.MapOverrides
   2: {
   3:     public class BookMappingOverride : IAutoMappingOverride<Book>
   4:     {
   5:         public void Override(AutoMapping<Book> mapping)
   6:         {
   7:             mapping.Map(m => m.BookDescription).Length(2000);
   8:         }
   9:     }
  10: }

Rest of the MVC like views controller implementation I believe can be rest assured as understandable.

Generate Schema and Execute DDL: Database change management

I have made a MS Test project MyTests

Added two test methods, one for recreate the whole database schema and other for try update the schema through alter table so that previous data remains untouched. Running tests outputs the script, so we can choose to run or not the ddl through SchemaExport or SchemaUpdate export argument as true/false by watching the scripts generated.

Here are the tests:


   1: [TestMethod]
   2:        public void DropAndRecreateSchema()
   3:        {
   4:            //
   5:            // TODO: Add test logic here
   6:            //
   7:            MyFrameWork.NHib.NHibernateSession.Initialize(null);
   8:  
   9:            var config = NHibernateSession.Configuration;
  10:  
  11:            var schemaExport = new SchemaExport(config); 
  12:            schemaExport.Execute(true, false, false);
  13:        }
  14:  
  15:        [TestMethod]
  16:        public void TryAlterDbSchema()
  17:        {
  18:            //
  19:            // TODO: Add test logic here
  20:            //
  21:            MyFrameWork.NHib.NHibernateSession.Initialize(null);            
  22:  
  23:            var config = NHibernateSession.Configuration;
  24:  
  25:            var schemaUpdate = new SchemaUpdate(config);
  26:            schemaUpdate.Execute(true, true);
  27:        }

Now that we have executed the ddl, we can check the database from server explorer:
clip_image001[7]
As the database schema is there, we can run the MVC application and play with the crud functionalities implemented merely by writing a single repository call.

We have achieved our goal! We can reuse the MyFramework.dll onto any other project with entities written deriving from DomainEntity and retrieve/save those entities through the generic Nhrepository.

 


Points of interest




History

Comments

Popular Posts