Showing posts with label DDD. Show all posts
Showing posts with label DDD. Show all posts

Repository implementation

Along with NHibernate mappings, Peniko.DataLayer project contains Repository implementations. Repository interfaces are in Peniko.Domain project, as mentioned before.

All communication with NHibernate is done through ISession instance. Session is basically a Unit of Work for NHibernate. We have a standard NHibernateHelper static class for encapsulating ISessionFactory and for creating / opening the session:

public static class NHibernateHelper

{

    private static ISessionFactory _sessionFactory;

 

    private static ISessionFactory SessionFactory

    {

        get

        {

            if (_sessionFactory == null)

            {

                var configuration = new Configuration();

                configuration.Configure();

                configuration.AddAssembly(typeof(Product).Assembly);

                _sessionFactory = configuration.BuildSessionFactory();

            }

            return _sessionFactory;

        }

    }

 

    public static ISession OpenSession()

    {

        return SessionFactory.OpenSession();

    }

}

SessionFactory is created from the NHibernate configuration the first time its accessed. Configuration comes from hibernate.cfg.xml file defined in UI and test projects:

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">

  <session-factory>

    <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>

    <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>

    <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>

    <property name="connection.connection_string">...</property>

    <property name="show_sql">true</property>

    <mapping assembly="Peniko.DataLayer" />

  </session-factory>

</hibernate-configuration>

In Peniko.DataLayer, every Repository method uses its own session, so we can't use NHibernate's lazy loading feature. Better option would be session-per-conversation pattern mentioned here, but we'll leave it as is for now.

The most important class in Repository implementation is Repository<T> implementing our IRepository<T> interface:

public interface IRepository<T> where T : Entity<T>

{

    void Save(T entity);

    void Delete(T entity);

    T GetById(Guid id);

    IList<T> GetAll();

}

 

public abstract class Repository<T> : IRepository<T> where T : Entity<T>

{

    private static readonly ILog _log = LogManager.GetLogger(typeof (Repository<T>));

    protected static ILog Log { get { return _log; } }

 

    public void Save(T entity) { ... }

    public void Delete(T entity) { ... }

    public T GetById(Guid id) { ... }

    public IList<T> GetAll() { ...  }

}

It's defined as abstract class and provides inherited classes with basic strongly typed methods for saving, deleting and retrieving all entities or a single entity by id and also an log4net's ILog instance . Inherited class is defined like this:

public class ProductRepository : Repository<Product>, IProductRepository { ... }

This is a Save method from Repository<T>:

public void Save(T entity)

{

    if (!entity.IsValid) throw new ValidationException("Entity is invalid");

 

    using (var session = NHibernateHelper.OpenSession())

    using (var transaction = session.BeginTransaction())

    {

        Log.DebugFormat("Saving entity Id: {0}", entity.Id);

 

        try

        {

            session.SaveOrUpdate(entity);

            AfterSaveEntity(session, entity);

            transaction.Commit();

        }

        catch (StaleObjectStateException ex)

        {

            transaction.Rollback();

            Log.Error("Error saving entity", ex);

            throw new ConcurrencyException("The data has been modified by another user", ex);

        }

        catch (HibernateException ex)

        {

            transaction.Rollback();

            Log.Error("Error saving entity", ex);

            throw;

        }

    }

}

First thing it does is to check if entity is valid. Then it opens a session and begins a new transaction on it. As of NHibernate 2.0, all Saves, Updates and Deletes must be encapsulated in transaction. Save method calls session.SaveOrUpdate() which does both insert and update operations based on the value of entity.Id. In our case, if Id is Guid.Empty, it will do Insert, otherwise Update.

AfterSaveEntity call allows inherited class to do some action after the entity is saved successfully. I.e. to save some other, unrelated entity.

Note that we catch StaleObjectStateException. That exception is thrown when there is a concurrency problem. We encapsulate it in our ConcurrencyException and throw that instead. That way it's easy to catch it in UI code without dependency on NHibernate.

Delete method is more or less the same. The only difference is that it calls session.Delete() instead of session.SaveOrUpdate().

GetById looks like this:

public T GetById(Guid id)

{

    using (var session = NHibernateHelper.OpenSession())

    {

        Log.DebugFormat("Getting entities with Id: {0}", id);

 

        try

        {

            var entity = session.Get<T>(id);

            AfterGetEntity(session, entity);

            return entity;

        }

        catch (HibernateException ex)

        {

            Log.Error("Error getting entities by id", ex);

            throw;

        }

    }

}

This method is very simple. session.Get<T>(id) returns a single entity with the given id. GetAll method is similar, but it uses NHibernate Criteria API and returns session.CreateCriteria(typeof (T)).List<T>() as a result.

In another project I'm currently involved, Repository also has FindAll and FindOne methods that accept DetachedCriteria as a parameter. The idea came from 12. episode of Summer of NHibernate.

Other Repository classes inherited from Repository<T> have its own methods for retrieving entities. These methods are mostly using Criteria API. More on Criteria API and HQL in NHibernate learning material.

The core of all applications

Today, I'll talk a little about the core of our application. There are many terms to describe it: business layer/model, entity layer/model, domain layer/model, middle layer... Whatever you want to call it, it still represents the problem you are trying to solve.

Being highly influenced by some articles we read, we mostly use Domain-Driven Design terms. So, our core project is called Peniko.Domain (Peniko is the name of my friends' company). This project started as a collection of POCO classes without much behavior, just enough to accommodate NHibernate's needs, but as we moving toward the higher layers of the application, it's getting more "intelligent". Below is the current class diagram.

Peniko.Domain class diagram

All class names are in English, except for PdvRate class. It should be VatRate (PDV = VAT), but we prefer our local acronym. You'll notice that all classes inherit from Entity<T> and below is how this class looked when it's created. The class is copied from here and renamed to Entity.

public abstract class Entity<T> where T : Entity<T>

{

    private int? _oldHashCode;

 

    /// <summary>

    /// Gets or sets the object Id.

    /// </summary>

    public virtual Guid Id { get; protected set; }

 

    /// <summary>

    /// ...

    /// </summary>

    public override bool Equals(object obj)

    {

        var other = obj as T;

        if (other == null) return false;

 

        // Handle the case of comparing two NEW objects

        var otherIsTransient = Equals(other.Id, Guid.Empty);

        var thisIsTransient = Equals(Id, Guid.Empty);

        if (otherIsTransient && thisIsTransient)

            return ReferenceEquals(other, this);

 

        return other.Id.Equals(Id);

    }

 

    /// <summary>

    /// ...

    /// </summary>

    public override int GetHashCode()

    {

        // Once we have a hash code we'll never change it

        if (_oldHashCode.HasValue) return _oldHashCode.Value;

 

        var thisIsTransient = Equals(Id, Guid.Empty);

 

        // When this instance is transient, we use the base GetHashCode()

        // and remember it, so an instance can NEVER change its hash code.

        if (thisIsTransient)

        {

            _oldHashCode = base.GetHashCode();

            return _oldHashCode.Value;

        }

 

        return Id.GetHashCode();

    }

 

    public static bool operator ==(Entity<T> x, Entity<T> y)

    {

        return Equals(x, y);

    }

 

    public static bool operator !=(Entity<T> x, Entity<T> y)

    {

        return !(x == y);

    }

}

 

There is one more important concept added to Domain project. Repository pattern interfaces. We are reevaluating the decision to have the repository interfaces in domain layer, but for now they are there. The purpose of repositories is to communicate with the mapping layer (load entities, save and delete them). Because CRUD operations are common for all our repositories, we created a base IRepository<T> interface. It looks like this:

public interface IRepository<T> where T : Entity<T>

{

    void Add(T entity);

    void Update(T entity);

    void Delete(T entity);

    T GetById(Guid id);

    IList<T> GetAll();

}

Other interfaces inherit IRepository<T>, and add more methods if necessary. I.e. IProductRepository looks like this:

public interface IProductRepository : IRepository<Product>

{

    Product GetProductByCode(string code);

    Product GetProductByBarCode(string barCode);

}

The classes implementing Repository interfaces are located in Peniko.DataAccess project. I'll talk more about them more in one of the following posts.

NHibernate learning material

We started off by learning NHibernate. Up until then I didn't use any OR/M (I don't consider LINQ 2 SQL to be a full OR/M). Most of the time, the code I wrote used ADO.NET DataReaders, alone or inside CSLA.NET DataPortal_xxx methods.

Since there is a lot of buzz lately about OR/Ms, Entity Framework is about to be released, it already got a vote of no confidence from ALT.NET group, etc, the good learning material isn't so hard to find. When I'm learning something new, I'm trying to find and use best good practices, established by the people who have been used that technology for a longer time. The following posts/articles/screencasts really helped me and my team to get on the right track.

The NHibernate FAQ by Gabriel Schenker is a great starting point:

Steve Bohlen's excellent screencast series Summer of NHibernate.

Ben Scheirman's series of articles about NHibernate and Domain Driven Design:

Davy Brion's NHibernate posts.

THE article NHibernate Best Practices with ASP.NET by Billy McCafferty. I'm looking forward for a chance to try his S#arp Architecture on a real project.

David Veeneman's NHibernate Made Simple article.

All of the above links will not only teach you how to use NHibernate, but also give you a start with Domain Driven Design, Unit Testing, Integration Testing, logging etc. Enjoy!