Sunday, November 7, 2010

My simple embedded Db4o repositories.

So, switching gears....
I hate plumbing code. I hate writing code to translate something from what it really is to something it really isn't. To be fair a "MartialArtist" isn't some c# object. But still, it's a closer representation than some row in a database table isn't it?
I've been using Db4o for quite a while, and I'm a big fan. While NHibernate totally rocks (really, LOVE it), there is something totally liberating about NOT worrying about making everything virtual just so your ORM can proxy it, or thinking about how Nhibernate is gunna handle persistence. I

The simplicity of Db4o awesome. Instead of going into how it works and all that jazz, I'm gunna show you how I use it. Since this post is a continuation of my DDD / CQRS series, I'm not gunna explain why I use the repository pattern. I'll just show you my 2 interfaces for them.
   1:  using System;
   2:   
   3:  namespace myDojo.Infrastructure
   4:  {
   5:      public interface IAggrigateRootRepository<T> where T : IObjectWithIdentity
   6:      {
   7:          void Store(T entity);
   8:          T GetById(Guid id);
   9:      }
  10:  }



   1:  using System;
   2:  using System.Collections.Generic;
   3:   
   4:  namespace myDojo.Infrastructure
   5:  {
   6:      public interface IReadModelRepository<T> where T : IObjectWithIdentity,new()
   7:      {
   8:          void Store(T entity);
   9:          IEnumerable<T> Get(Predicate<T> query);
  10:          T GetSingle(Predicate<T> query);
  11:          T GetById(Guid id);
  12:          IEnumerable<T> GetAll();
  13:      }
  14:  }
 
IObjectWithIdentity is just a interface (and I made an abstract for it) that encapsulates an object that has a unique identity - an actual entity (duh). Here it is (and the abstract class for it).
 

   1:  using System;
   2:   
   3:  namespace myDojo.Infrastructure
   4:  {
   5:      public interface IObjectWithIdentity
   6:      {
   7:          Guid Id { get; set; }
   8:          int Version { get; set; }
   9:      }
  10:   
  11:      public abstract class ObjectWithIdentity : IObjectWithIdentity
  12:      {
  13:          protected ObjectWithIdentity()
  14:          {
  15:              Id = ANew.Comb();
  16:          }
  17:   
  18:          protected ObjectWithIdentity(Guid id)
  19:          {
  20:              Id = id;
  21:          }
  22:          public virtual Guid Id { get; set; }
  23:          public virtual int Version { get; set; }
  24:          public override bool Equals(object obj)
  25:          {
  26:              if (ReferenceEquals(null, obj)) return false;
  27:              if (ReferenceEquals(this, obj)) return true;
  28:              if (!(obj is ObjectWithIdentity)) return false;
  29:              return Equals((ObjectWithIdentity) obj);
  30:          }
  31:   
  32:          public bool Equals(ObjectWithIdentity other)
  33:          {
  34:              if (ReferenceEquals(null, other)) return false;
  35:              if (ReferenceEquals(this, other)) return true;
  36:              return other.Id.Equals(Id);
  37:          }
  38:   
  39:          public override int GetHashCode()
  40:          {
  41:              return Id.GetHashCode();
  42:          }
  43:      }
  44:  }
 
Why the Version? Fair question -- it's there cause I use NHibernate a lot :), old habits die hard?
 
So what really happened was that I just wrote two classes that implemented these interfaces, then noticed the abstract concept of using Db4o and pulled an abstract out of that, but I suck at git logs still, so I'm not gunna find that code, I'll just show you my abstract class now.
 

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using Db4objects.Db4o;
   5:   
   6:  namespace myDojo.Infrastructure.Db4o
   7:  {
   8:      public abstract class Db4oRepo<T>  where T:IObjectWithIdentity
   9:      {
  10:          protected IObjectContainer Db { get; private set; }
  11:   
  12:          public Db4oRepo(IObjectContainer db)
  13:          {
  14:              Db = db;
  15:          }
  16:          public virtual void Store(T entity)
  17:          {
  18:              entity.Version++;
  19:              Db.Store(entity);
  20:          }
  21:          public virtual T GetById(Guid id)
  22:          {
  23:              return Db.Query<T>(t => t.Id == id).First();
  24:          }
  25:          protected virtual IEnumerable<T> Get(Predicate<T> query)
  26:          {
  27:              return Db.Query(query);
  28:          }
  29:          
  30:      }
  31:  }
 

Seriously,  could it be much simpler? IObjectContainer (I hate that name, I think IOC's have a trademark on the word "Container" darnit!) is basically a ISession (if you're an Nhibernate person) or DbContext if you're an idiot (I mean Ado.Net user).

Let's look at the 2 classes that implement my 2 repo interfaces.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using Db4objects.Db4o;
   5:   
   6:  using myDojo.Infrastructure;
   7:  using myDojo.Infrastructure.Db4o;
   8:   
   9:  namespace MyDojo.Query.Infrastructure
  10:  {
  11:      public class Db4oReadModelRepository<T> :Db4oRepo<T>, IReadModelRepository<T> where T : IObjectWithIdentity,new()
  12:      {
  13:          public Db4oReadModelRepository(IObjectContainer db):base(db){}
  14:          public IEnumerable<T> Get(Predicate<T> query)
  15:          {
  16:              return base.Get(query);
  17:          }
  18:   
  19:          public T GetSingle(Predicate<T> query)
  20:          {
  21:              return Db.Query(query).FirstOrDefault();
  22:          }
  23:         
  24:          public IEnumerable<T> GetAll()
  25:          {
  26:              return Db.Query<T>();
  27:          }
  28:      }
  29:  }

And

   1:  using Db4objects.Db4o;
   2:   
   3:  namespace myDojo.Infrastructure.Db4o
   4:  {
   5:   
   6:      public class Db4OAggrigateRootRepository<T> : Db4oRepo<T>, IAggrigateRootRepository<T> where T : ObjectWithIdentity
   7:      {
   8:         public Db4OAggrigateRootRepository(IObjectContainer db):base(db){}
   9:      }
  10:  }

Couldn't be much simpler could it?

Let's go look at some integration tests really quick.

   1:  using System.IO;
   2:  using Db4objects.Db4o;
   3:  using Db4objects.Db4o.Internal;
   4:  using myDojo.Domain.UnitTests;
   5:  using myDojo.Domain.Users;
   6:  using myDojo.Infrastructure;
   7:  using myDojo.Infrastructure.Db4o;
   8:  using NUnit.Framework;
   9:   
  10:  namespace myDojo.Domain.IntegrationTests
  11:  {
  12:      [TestFixture]
  13:      public class test_the_repo
  14:      {
  15:          [Test]
  16:          public void store_and_retrieve_a_martialartist()
  17:          {
  18:              var ma = new MartialArtist(null);
  19:              _repo.Store(ma);
  20:              _dbContainer.Close();
  21:              var newContainer = Db4oFactory.OpenFile(dbFile);
  22:              var repo2 = new Db4OAggrigateRootRepository<MartialArtist>(newContainer);
  23:              var retrieved = repo2.GetById(ma.Id);
  24:              retrieved.ShouldEqual(ma);
  25:              newContainer.Close();
  26:          }
  27:          private IObjectContainer _dbContainer;
  28:          private Db4OAggrigateRootRepository<MartialArtist> _repo;
  29:          private const string dbFile = "tests.db";
  30:          [SetUp]
  31:          public void setup()
  32:          {
  33:              _dbContainer = Db4oFactory.OpenFile(dbFile);
  34:              _repo = new Db4OAggrigateRootRepository<MartialArtist>(_dbContainer);
  35:          }
  36:          [TearDown]
  37:          public void teardown()
  38:          {
  39:              _dbContainer.Close();
  40:             File.Delete(dbFile);
  41:          }
  42:      }
  43:  }

I showed you this first because it's easier to see the code than explain it. Db4o gives you a Db4oFactory object that lets you do a lot of stuff to create IObjectContainers. My repositories don't care what kind of IObjectContainer it is, just that it is one.  This test just creates a new db (line 33). If the file isn't there, it just creates it. No mapping, nothing, just store your stuff. How awesome is that?

Ok, so, let's go look at my actual application and how it injects an IObjectContainer into repositories.

   1:  using System.Web;
   2:  using System.Web.Mvc;
   3:  using System.Web.Routing;
   4:  using Db4objects.Db4o;
   5:  using myDojo.Web.Init;
   6:  using StructureMap;
   7:   
   8:  namespace myDojo.Web
   9:  {
  10:      // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
  11:      // visit http://go.microsoft.com/?LinkId=9394801
  12:   
  13:      public class MvcApplication : HttpApplication
  14:      {
  15:         
  16:          public override void Dispose()
  17:          {
  18:              base.Dispose();
  19:              if(Db!=null)
  20:                  Db.Dispose();
  21:              
  22:          }
  23:          public static IEmbeddedObjectContainer Db { get; private set; }
  24:          private IEmbeddedObjectContainer OpenDatabase()
  25:          {
  26:              string relativePath = DbFileName;
  27:              string filePath = HttpContext.Current.Server.MapPath(relativePath);
  28:              return Db4oEmbedded.OpenFile(filePath);
  29:          }
  30:   
  31:          public const string DbFileName = "myDojo.db4o";
  32:          
  33:          protected void Application_Start()
  34:          {
  35:              
  36:              AreaRegistration.RegisterAllAreas();
  37:   
  38:              RegisterGlobalFilters(GlobalFilters.Filters);
  39:              RegisterRoutes(RouteTable.Routes);
  40:              Db = OpenDatabase();
  41:   
  42:              ObjectFactory.Configure(c =>
  43:                                          {
  44:                                              c.Scan(s =>
  45:                                                         {
  46:                                                             s.AssembliesFromApplicationBaseDirectory();
  47:                                                             s.LookForRegistries();
  48:                                                         });
  49:                                              
  50:                                          });
  51:              ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory(ObjectFactory.Container));
  52:              
  53:          }
  54:  /*lots of other stuff
  55:  */
  72:  }

Ok, so you see I'm actually creating an IEmbeddedObjectContainer, but not where I'm using it. This happens with some IOC nested container and ControllerFactory magic.

Let's go look at my StructureMapControllerFactory.

   1:          protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
   2:          {
   3:              if (controllerType == null) return null;
   4:              var nestedContainer = _container.GetNestedContainer();
   5:              nestedContainer.Configure(c => c.AddRegistry(new PerRequestRegistry(requestContext)));
   6:              requestContext.HttpContext.Items[ContainerKey] = nestedContainer;
   7:              ServiceLocation.CurrentContainer = nestedContainer;
   8:              return (IController) nestedContainer.GetInstance(controllerType);
   9:          }
  10:   
  11:          public override void ReleaseController(IController controller)
  12:          {
  13:              var baseController = controller as Controller;
  14:              if (baseController != null)
  15:              {
  16:                  var nestedContainer = (IContainer) baseController.HttpContext.Items[ContainerKey];
  17:                  var db = nestedContainer.GetInstance<IObjectContainer>();
  18:                  db.Close();
  19:                  nestedContainer.Dispose();
  20:              }
  21:              base.ReleaseController(controller);
  22:          }
  23:      }
  24:   
  25:      public class PerRequestRegistry : Registry
  26:      {
  27:          public PerRequestRegistry(RequestContext requestContext)
  28:          {
  29:              ForSingletonOf<IObjectContainer>().Use(c => MvcApplication.Db.Ext().OpenSession());
  30:              For<RequestContext>().Use(requestContext);
  31:              For<HttpContextBase>().Use(requestContext.HttpContext);
  32:          }
  33:      }
  34:  }
 
So what's going on here? When the MVC stack creates an instance of the requested controller, we create a new nested container (line 4) and add a PerRequestRegistry to it. If we look at line 29, you see I register IObjectContainer as a Singleton, and tell StructureMap to call Ext().OpenSession() on the Db we created in Application_start to construct it. 
When the Stack releases the controller, we want to actually commit our changes (ISession.Flush()). To do that, I go get the nested container from HttpContext.Items (where I stored it on GetControllerInstance), fetch the ObjectContainer from there, and call Close(). Close automatically does a flush. So I'm good. I then Dispose the container, which will dispose of any IDisposable in my container.
 
It's really that simple. This just works. You've already seen code that uses these repo's so not sure what else to show ya here. I strongly recommend you check out Db4o, it makes life nice.

3 comments:

  1. Did you know that db4o can store the version number for you? It has a revision number in the object metadata (used by replication).

    ReplyDelete
  2. And take a look at ObjectDinner project on google code for a great example of using db4o with MVC.

    ReplyDelete