w00t : Building a new app from the ground up : IOC, database and other stuff; a real framework
This is one of the articles in our series; the parts are:
- Methodology, technology & tools used
- Setting up the environment
- First specs and getting started
- IOC, database and other stuff; a real framework
Happy reading !
Finally a framework !
When I went to bed last night, I was thinking about a few things, so I said to myself : "Ok, I'll quickly implement them". But, as one can expect, a few minutes became a few hours, so in the end I did not get much sleep. I decided to finish it this morning, so the now the main stuff of the framework is finsished, and I'll write a little post about this.
Back to the drawing board
After taking a closer look at simplerepository to know the domain model conventions I noticed that the support for relationships was a mess (i.e. you have to join data yourself using Id's etc, so I decided to go back to the one thing I know : fluent nhibernate.
In the mean time my specs were constantly evolving… It lead me to do a complete refactoring of my code; you can see the current state of the spec in the end :
Delete subsonic and get FluentNhibernate, Nhibernate.Linq and Castle.Windsor
1. In the project folder, delete the folder "SubSonic-3.0"
2. Start a git bash in the project folder, and type :
git clone git://github.com/jagregory/fluent-nhibernate.git
3. look for fluentnhbernate.csproj and build it (make sure the configuration mode is set to release)
4. look for machine.Specifications.sln and build it
5. Change the batch file tools\copydependenciesandtools.cmd to the following :
@Echo off
echo.
echo Copying all tools and dependencies
echo.
copy ..\machine.Specifications\Build\Release\*.dll
copy ..\machine.Specifications\Build\Release\*.exe
copy ..\machine.Specifications\Build\Release\*.dll ..\lib\*.*
copy ..\fluent-nhibernate\src\FluentNHibernate\bin\Release\*.dll ..\lib\*.*
pause
6. execute tools\copydependenciesandtools.cmd
7. execute tools\cleanup.Cmd
8. Download Castle.Windsor for IOC and put the dll’s in the lib folder
9. Download NHibernate.Linq and put the dll’s in the lib folder
Setup your business logic project
11. Creat a new class lib project and name it “xxx.Core”
12. Add references to the following libs in both your specs project and your Core project :
..\lib\Castle.Core
..\lib\Castle.MicroKernel
..\lib\Castle.Windsor
..\lib\FluentNHibernate
..\lib\NHibernate
..\lib\NHibernate.ByteCode.Castle
..\lib\NHibernate.Linq
..\lib\System.Data.SqlLite
..\lib\System.Data.SqlLite.Linq
Add the core project as a ref to your specs project .
13. Create your model classes according to the fluentnhbibernate conventions. An example :
// models\_ModelBase.cs
//
// the abstract base class for the domain objects
//
namespace Be.HorecaTouch.Core.Models
{
public abstract class ModelBase
{
public virtual int Id {get;set;}
public virtual string Description { get; set; }
}
}
// models\Product.cs
//
// a product
//
namespace Be.HorecaTouch.Core.Models
{
public class Product : ModelBase
{
public virtual int Number { get; set; }
public virtual ProductGroup Group { get; set; }
public virtual bool Available { get; set; }
public virtual bool PrintOutToReceipt { get; set; }
public virtual bool PrintOutToKitchen { get; set; }
public virtual string CourseName { get; set; }
public virtual bool NonProfit { get; set; }
public virtual ICollection<Lookup> Lookups { get; set; }
public virtual int SellingPrice1 { get; set; }
public virtual int SellingPrice2 { get; set; }
public virtual int SellingPrice3 { get; set; }
public virtual int SellingPrice4 { get; set; }
public virtual int SellingPrice5 { get; set; }
}
}"font-size: 10pt; font-family: 'Courier New'">
14. After this I created the interfaces in the core project :
// Interfaces \ IIOC.cs
namespace Be.HorecaTouch.Core.Services.Interfaces
{
public interface IIOC
{
T Resolve<T>();
T Resolve<T>(string name);
}
}
// interfaces\IRepository.cs
namespace Be.HorecaTouch.Core.Services.Interfaces
{
public interface IRepository<T> where T:Models.ModelBase
{
T GetById(int id);
IQueryable<T> Find { get; }
void SaveOrUpdate(T instance);
void Delete(T instance);
}
}
// interfaces\IUnitOfWork.cs
namespace Be.HorecaTouch.Core.Services.Interfaces
{
public interface IUnitOfWork : IDisposable
{
void Commit();
Dictionary<string, object> SessionVars { get; }
}
}
// interfaces\IUnitOfWorkFactory.cs
namespace Be.HorecaTouch.Core.Services.Interfaces
{
public interface IUnitOfWorkFactory
{
IUnitOfWork CurrentUnitOfWork {get;}
}
}
// interfaces\IUnitOfWorkFactory.cs
namespace Be.HorecaTouch.Core.Services.Interfaces
{
public interface IXmlAdapter
{
void Import(string xml);
}
}"font-size: 10pt; font-family: 'Courier New'">
15. Implement concrete classes that implement the interfaces
//implementations\database.cs
//
// Automaticly creates an sqllite db based on the classes in the Models namespace.
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate;
using FluentNHibernate;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Automapping;
using Be.HorecaTouch.Core.Models;
using Be.HorecaTouch.Core.Services.Interfaces;
using NHibernate.Tool.hbm2ddl;
namespace Be.HorecaTouch.Core.Services.Implementations
{
public abstract class Database : IUnitOfWorkFactory
{
protected ISessionFactory SessionFactory;
protected static ISession LocalSession;
protected Database(IPersistenceConfigurer pcfg)
{
var cfg = Fluently.Configure()
.Database(pcfg)
.Mappings(m => m.AutoMappings.Add(
AutoMap.AssemblyOf<Product>()
.IgnoreBase<ModelBase>()
.Where(am => am.Namespace == typeof(Product).Namespace)))
.BuildConfiguration();
SessionFactory = cfg.BuildSessionFactory();
LocalSession = SessionFactory.OpenSession();
new SchemaExport(cfg).Execute(false, true, false, LocalSession.Connection, null);
}
#region IUnitOfWorkFactory Members
public virtual IUnitOfWork CurrentUnitOfWork
{
get {
LocalSession = null;
return new UnitOfWork(()=>SessionFactory.OpenSession(),true,false );
}
}
#endregion
}
public class SqlLiteInMemoryDatabase : Database
{
public SqlLiteInMemoryDatabase() : base(SQLiteConfiguration.Standard.InMemory())
{
}
public override IUnitOfWork CurrentUnitOfWork
{
get
{
return new UnitOfWork(() => LocalSession, false, true);
}
}
}
public class SqlLiteDatabase : Database
{
public SqlLiteDatabase(string filename) : base(SQLiteConfiguration.Standard.UsingFile(filename))
{
}
public override IUnitOfWork CurrentUnitOfWork
{
get
{
return new UnitOfWork(() => SessionFactory.OpenSession(), false, false);
}
}
}
}
// implementations\IOC.cs
//
// use the default castle windsor IOC container
//
using Be.HorecaTouch.Core.Services.Interfaces;
namespace Be.HorecaTouch.Core.Services.Implementations
{
public class IOC : Castle.Windsor.WindsorContainer, IIOC {}
}
// implementations\Repository
//
// a generic repository for nhibernate
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Be.HorecaTouch.Core.Services.Interfaces;
using FluentNHibernate;
using NHibernate.Linq;
namespace Be.HorecaTouch.Core.Services.Implementations
{
public class Repository<T> : IRepository<T> where T:Models.ModelBase
{
NHibernate.ISession session;
public Repository(Database db)
{
session = ((UnitOfWork)db.CurrentUnitOfWork).Session.Instance ;
}
#region IRepository<T> Members
public T GetById(int id)
{
return session.Get<T>(id);
}
public IQueryable<T> Find
{
get { return session.Linq<T>(); }
}
public void SaveOrUpdate(T instance)
{
session.SaveOrUpdate(instance);
}
public void Delete(T instance)
{
session.Delete(instance);
}
#endregion
}
}
So what is the Result until now ?
Now you have a fully setup environment with an (inmemory/file) database for your domainmodel, acessible with a generic iRepository. All your service interfaces are available through an IOC, and your data can be easily persisted…
Your development can now also be driven by specs
Adjust the spec file to your likings;this is currently mine :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Machine.Specifications;
using Be.HorecaTouch.Core.Models;
using Be.HorecaTouch.Core.Services.Interfaces;
using Be.HorecaTouch.Core.Services.Implementations;
namespace Specs
{
public abstract class with_empty_database
{
protected static IIOC ioc;
Establish context = () =>
{
var cont = new IOC();
cont.AddComponent<Database, SqlLiteInMemoryDatabase>();
cont.AddComponent<IRepository<Product>,Repository<Product>>();
cont.AddComponent<IRepository<Lookup>, Repository<Lookup>>();
cont.AddComponent<IXmlAdapter, XmlAdapter>();
ioc = cont;
};
}
public class when_products_are_imported_from_external_xmlfile : with_empty_database
{
Because of = () => ioc.Resolve<IXmlAdapter>().Import(Properties.Resources.ExternalProductsXml);
It should_contain_multiple_products = () =>
ioc.Resolve<IRepository<Product>>().Find.Count().ShouldBeGreaterThan(0);
It should_have_a_product_called_steak_with_cooking_and_sauce_lookups = () =>
ioc.Resolve<IRepository<Product>>().Find
.Where(a => a.Description == "Steak").Count().ShouldBeGreaterThan(0);
It should_contain_multiple_lookups = () =>
ioc.Resolve<IRepository<Lookup>>().Find.Count().ShouldBeGreaterThan(0);
It should_contain_multiple_cookings;
It should_contain_multiple_courses;
}
public abstract class with_filled_database : with_empty_database
{
Establish context = () =>
{
ioc.Resolve<IXmlAdapter>().Import(Properties.Resources.ExternalProductsXml);
};
}
"font-size: 10pt; font-family: Arial">}
Finally, this is what my current Spec build html looks like :
Specs
1 concern, 1 context, 5 specifications, 2 not implemented specs
specifications
1 context, 5 specifications, 2 not implemented specs
when products are imported from external xmlfile
5 specifications, 2 not implemented specs
- should contain multiple products
- should have a product called steak with cooking and sauce lookups
- should contain multiple lookups
- should contain multiple cookings< NOT IMPLEMENTED
- should contain multiple courses< NOT IMPLEMENTED
Conclusion
Et voila, in a few easy steps we have created a framework which offers quite some advantages, and should be easy to extend... and is BDD (hype hype hype !!) I do not offer the holy grail however, so always make sure you adapt the code to your wishes.
Since blogging about this does take quite some time, and the development now becomes really specific, I think this will probably be my last post about this new project, but he, never say never !!!
Maybe if I have some issues regarding MVC testing I might blog about it, but I can not guarantee anything... Happy coding !!<-->