Long time since I've posted anything but I came across an interesting problem the other day whilst I was working - 'How can I audit changes to specific entities when using nHibernate?'
There are several implementation out there already (see Oren's & others posts) but of ones I've seen they are to low level for my liking - they push the auditing of changes into the NH infrastructure away from the Service implementing the business behaviour. I want my service to control and define what is audited I don't want everything audited in the same manner. Auditing for me is the pushing out of events that have affected entities persisted in an OLTP database to OLAP database ideally in an asynchronous manner via some queuing mechanism (MSMQ).
So how do you get events from NH?
Now if you want to observe changes to entities in NH you have to register you're interest by passing an implementation of an IXXXListener interface to the NH configuration when create the SessionFactory, this is the classic implementation of the observer pattern and I guess it's like this because this is a straight port from java - it doesn't follow the .Net approach of EventHandlers & Events, I'm liking this approach as it gives exposure to a different implementation.
What did interest me about this implementation was that as user of the NH Session I can't register with the Session my interest in observing changes to the entities. I have to do this with the SessionFactory and therefore I'm getting notified of ALL events to ALL entities whilst this SessionFactory is alive.
So you can register your interest by implementing a class and passing this to the NH configuration - I've tried to keep it simple by register for events that tell me when CRUD operations have been completed on entities, this is done by implementing the following NH interfaces:
I've left out all the XXXEventArgs classes from the post for brevity - I hope you can figure out the structure!
This SessionFactory is backed by an interface, that defines 2 methods - one for access to the Session & the other for accessing an implementation of SessionObserver<T> exposed as an IObserveSession<T> interface, where the generic T represents the entity type I'm interested in observing.
So the interface looks like:
SessionFactory interface:
Now I've not tested this fully and I'm not sure of the viablity of this option or whether I like the end usage - I suppose I could wrap the NH Session behind a custom Session class that exposes the events directly. But it's a start at thinking how I'm going to achieve the auditing required.
Wow that was longer thanexpected ;)
Anyway till next time...
Awkward
There are several implementation out there already (see Oren's & others posts) but of ones I've seen they are to low level for my liking - they push the auditing of changes into the NH infrastructure away from the Service implementing the business behaviour. I want my service to control and define what is audited I don't want everything audited in the same manner. Auditing for me is the pushing out of events that have affected entities persisted in an OLTP database to OLAP database ideally in an asynchronous manner via some queuing mechanism (MSMQ).
So how do you get events from NH?
Now if you want to observe changes to entities in NH you have to register you're interest by passing an implementation of an IXXXListener interface to the NH configuration when create the SessionFactory, this is the classic implementation of the observer pattern and I guess it's like this because this is a straight port from java - it doesn't follow the .Net approach of EventHandlers & Events, I'm liking this approach as it gives exposure to a different implementation.
What did interest me about this implementation was that as user of the NH Session I can't register with the Session my interest in observing changes to the entities. I have to do this with the SessionFactory and therefore I'm getting notified of ALL events to ALL entities whilst this SessionFactory is alive.
So you can register your interest by implementing a class and passing this to the NH configuration - I've tried to keep it simple by register for events that tell me when CRUD operations have been completed on entities, this is done by implementing the following NH interfaces:
IPostInsertEventListener,
IPostLoadEventListener,
IPostUpdateEventListener,
IPostDeleteEventListener.
I also created an interface called IObserveNHibernate that fires traditional .Net style events when an NH event has been observed.I've left out all the XXXEventArgs classes from the post for brevity - I hope you can figure out the structure!
public interface IObserveSession<T> : IDisposable where T : IEntity
{
event EventHandler<SessionEventArgs<T>> EntityLoaded;
event EventHandler<SessionEventArgs<T>> EntityCreated;
event EventHandler<SessionEventArgs<T>> EntityUpdated;
event EventHandler<SessionEventArgs<T>> EntityDeleted;
}
So the class looks like:public sealed class NHibernateObserver : IObserveNHibernate,
IPostInsertEventListener,
IPostLoadEventListener,
IPostUpdateEventListener,
IPostDeleteEventListener
{
public event EventHandler<NHibernateEventArgs> EntityLoaded = delegate { };
public event EventHandler<NHibernateEventArgs> EntityCreated = delegate { };
public event EventHandler<NHibernateEventArgs> EntityUpdated = delegate { };
public event EventHandler<NHibernateEventArgs> EntityDeleted = delegate { };
public void OnPostDelete(PostDeleteEvent @event)
{
EntityDeleted(this, new NHibernateEventArgs(@event.Entity));
}
public void OnPostInsert(PostInsertEvent @event)
{
EntityCreated(this, new NHibernateEventArgs(@event.Entity));
}
public void OnPostLoad(PostLoadEvent @event)
{
EntityLoaded(this, new NHibernateEventArgs(@event.Entity));
}
public void OnPostUpdate(PostUpdateEvent @event)
{
EntityUpdated(this, new NHibernateEventArgs(@event.Entity));
}
}
And this class is then used when creating the SessionFactory for NH. Now currently I wrap the creation of the NH SessionFactory into a custom SessionFactory - this class usually exists as a singleton in the host process and lifetime is managed by the IoC container (it's marked as a singleton).This SessionFactory is backed by an interface, that defines 2 methods - one for access to the Session & the other for accessing an implementation of SessionObserver<T> exposed as an IObserveSession<T> interface, where the generic T represents the entity type I'm interested in observing.
So the interface looks like:
public interface IObserveSession<T> : IDisposable where T : IEntity
{
event EventHandler<SessionEventArgs<T>> EntityLoaded;
event EventHandler<SessionEventArgs<T>> EntityCreated;
event EventHandler<SessionEventArgs<T>> EntityUpdated;
event EventHandler<SessionEventArgs<T>> EntityDeleted;
}
The interface implements the Dispose pattern so the un-hooking of events automatically happens when you've finished observing NH events. The implementation of the interface is show below:public sealed class SessionObserver<T> : IObserveSession<T> where T : IEntity
{
public event EventHandler<SessionEventArgs<T>> EntityLoaded = delegate {};
public event EventHandler<SessionEventArgs<T>> EntityCreated = delegate {};
public event EventHandler<SessionEventArgs<T>> EntityUpdated = delegate {};
public event EventHandler<SessionEventArgs<T>> EntityDeleted = delegate {};
private IObserveNHibernate _observer;
public SessionObserver(IObserveNHibernate observer)
{
_observer = observer;
_observer.EntityLoaded += (HandleEntityLoaded);
_observer.EntityCreated += (HandleEntityCreated);
_observer.EntityUpdated += (HandleEntityUpdated);
_observer.EntityDeleted += (HandleEntityDeleted);
}
public void Dispose()
{
_observer.EntityLoaded -= HandleEntityLoaded;
_observer.EntityCreated -= HandleEntityCreated;
_observer.EntityUpdated -= HandleEntityUpdated;
_observer.EntityDeleted -= HandleEntityDeleted;
_observer = null;
EntityCreated = null;
EntityUpdated = null;
EntityDeleted = null;
}
private void HandleEntityDeleted(object sender, NHibernateEventArgs args)
{
if (args.Entity is T)
EntityDeleted(this, new SessionEventArgs<T>((T)args.Entity));
}
private void HandleEntityUpdated(object sender, NHibernateEventArgs args)
{
if (args.Entity is T)
EntityUpdated(this, new SessionEventArgs<T>((T)args.Entity));
}
private void HandleEntityCreated(object sender, NHibernateEventArgs args)
{
if (args.Entity is T)
EntityCreated(this, new SessionEventArgs<T>((T)args.Entity));
}
private void HandleEntityLoaded(object sender, NHibernateEventArgs args)
{
if (args.Entity is T)
EntityLoaded(this, new SessionEventArgs<T>((T)args.Entity));
}
}
So I have a custom SessionFactory that looks something like the following - pretty standard NH Session semantics and the new method SessionObserverT - this creates an instance of the SessionObserver<T> per request.SessionFactory interface:
public interface IProvideSessions
{
ISession GetSession();
IObserveSession<T> SessionObserver<T>() where T : IEntity;
}
SessionFactory class:public sealed class SessionFactory : IProvideSessions
{
private readonly ISessionFactory _sessionFactory;
private readonly NHibernateObserver _nHibernateObserver;
public SessionFactory(string connectionString)
{
_nHibernateObserver = new NHibernateObserver();
var cfg = new Configuration();
cfg.EventListeners.PostLoadEventListeners = new IPostLoadEventListener[] { _nHibernateObserver };
cfg.EventListeners.PostInsertEventListeners = new IPostInsertEventListener[] { _nHibernateObserver };
cfg.EventListeners.PostUpdateEventListeners = new IPostUpdateEventListener[] { _nHibernateObserver };
cfg.EventListeners.PostDeleteEventListeners = new IPostDeleteEventListener[] { _nHibernateObserver };
_sessionFactory = Fluently.Configure().ExposeConfiguration(c => cfg.Configure())
.Database(MsSqlConfiguration.MsSql2005.ConnectionString(connectionString).ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<IEntity>())
.BuildSessionFactory();
}
public ISession GetSession()
{
return _sessionFactory.GetCurrentSession() ?? _sessionFactory.OpenSession();
}
public IObserveSession<T> SessionObserver<T>() where T : IEntity
{
return new SessionObserver<T>(_nHibernateObserver);
}
}
So an example of how I would use this is show below, it's a little contrived and not tested but I hope you get the idea:public class CashService
{
private readonly SessionFactory _sessionFactory = new SessionFactory("SOME CONNECTION STRING");
public void Debit(string accountNumber, decimal amount)
{
using(var session = _sessionFactory.GetSession())
using(var trans = session.BeginTransaction())
using(var sessionObserver = _sessionFactory.SessionObserver<Transaction>())
{
sessionObserver.EntityCreated += ((sender, args) => AuditTransaction(args));
var account = session.Get<Account>(accountNumber);
session.Save(new Transaction(account, amount));
trans.Commit();
}
}
private void AuditTransaction(SessionEventArgs<Transaction> args)
{
// Write transaction event to audit queue...
// if fails the throw exception...
}
}
Now I've not tested this fully and I'm not sure of the viablity of this option or whether I like the end usage - I suppose I could wrap the NH Session behind a custom Session class that exposes the events directly. But it's a start at thinking how I'm going to achieve the auditing required.
Wow that was longer thanexpected ;)
Anyway till next time...
Awkward
That's interesting.
ReplyDeleteI've looked at the implementations that use a separate database call to handle database access from inside an event. It breaks the model a bit, and if (n)Hibernate rolls back, does your audit event also roll back? Not sure if it would be in the same transaction.
It would be nice if you could access the session from inside an interceptor or event handler.
I have a case where I have to flush out an object that is loaded from an XML file, and then merged into the session.
I don't store the primary identifier (identity) in the XML, as the object needs to be portable.
So I have to do a look-up first to see if I can find the object in the database (there is an alternate key), and set the identifier in the object if found.
Events and interceptors are a great place to do that, but nHibernate does not seem to want to co-operate.