Introduction
At last, I've found some time to sit down and begin writing what should be the last part of this 'Domain Driven Design Quickstart' series of articles. In this final article, I will implement the persistence of the domain model that I’ve created in the previous parts of this series. (part I, part II and part III).
To implement the persistence functionality of the domain model, I will use an O/R mapper. I’ve opted to use NHibernate as O/R mapper for this project.
I will not make an in-depth coverage of NHibernate in this article, since this is a bit beyond the scope of this article. Besides, there are various excellent resources on (N)Hibernate available. The book Hibernate in Action, for instance, is an excellent resource to learn about (N)Hibernate, and it offers also some more insights in the O/R principle.
The Database Schema
To get the information in a database, we need a database first (clever :) ). So, I’ve created this ERD:
I’m not going to elaborate on this database-schema since I presume that you have enough knowledge of databases / database-modelling, etc… So lets hop to the next phase.
NHibernate configuration
NHibernate is an O/R mapper which lets you persist your data to a number of supported databases. To be able to use it however, it needs to be setup properly; in .NET this can be easily done in the configuration file of your project.
For this project, I’ll create a WinForms application, and I’ll make some changes to the app.config file to configure NHibernate. This is done like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="nhibernate"
type="System.Configuration.NameValueSectionHandler,
System, Version=1.0.5000.0,Culture=neutral,
PublicKeyToken=b77a5c561934e089"/>
</configSections>
<nhibernate>
<add key="hibernate.connection.provider"
value="NHibernate.Connection.DriverConnectionProvider"/>
<add key="hibernate.dialect"
value="NHibernate.Dialect.MsSql2000Dialect"/>
<add key="hibernate.connection.connection_string"
value="Data Source=localhost;
Initial Catalog=SomeShop;
Integrated Security=SSPI"/>
</nhibernate>
</configuration>
As you can see, I need to define the nhibernate configuration section first. Once this is done, I specify to which type of database NHibernate will have to talk to (SQL Server 2000), and I also specify the connection string that must be used.
If you use Microsoft Visual Studio.NET 2005, you might come across some unwanted error/information messages concerning the nhibernate config section. I’ve written another post about this problem, and how to solve it a while ago. You can read it here.
Now we’ve configured our project to use NHibernate, but we also need to tell NHibernate how our classes should be mapped to our database-schema. This can be done by creating NHibernate mapping files which describe how a class should be represented in the database. Those mapping files are nothing more then XML files. As I’ve said earlier, this is not an NHibernate tutorial, but merely an article on how NHibernate can be incorporated in a Domain Driven Design project. Therefore, I will not post all the mapping files here or explain them in detail. I will post 2 mapping files here however, and explain them a bit.
NHibernate mapping files
For each class in my domain model, I create an NHibernate mapping file in my Visual Studio.NET project which contains my business classes.
In this specific case, I have a Class Library project which contains the ‘Customer’, ‘Order’, ‘OrderLine’, etc… business classes, so it is in this class library that I also create the NHibernate mapping files.
The mapping file for the Order class, for instance, looks like this:
<xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="SomeShopDomain.Order, SomeShopDomain"
table="[Order]" lazy="false">
<id name="Id" column="OrderId" type="Int32"
unsaved-value="-1"
access="field.camelcase-underscore">
<generator class="identity" />
</id>
<property name="OrderDate" column="OrderDate"
access="field.camelcase-underscore" />
<property name="Status" column="OrderStatus"
access="field.camelcase-underscore" />
<property name="Discount" column="Discount"
access="field.camelcase-underscore" />
<many-to-one name="OwningCustomer" column="CustomerId"
class="SomeShopDomain.Customer, SomeShopDomain"
not-null="true"
access="field.camelcase-underscore"
/>
<bag name="OrderLines" cascade="save-update"
inverse="true"
access="field.camelcase-underscore">
<key column="OrderId" />
<one-to-many class="SomeShopDomain.OrderLine, SomeShopDomain"/>
</bag>
</class>
</hibernate-mapping>
This is not the most simple mapping-file in the project, since the Order
class contains a collection of OrderLines
, so this relationship is reflected in the mapping file.
But, it is maybe better if I just explain the complete mapping file a bit .
As you can see at the beginning of the file, this file describes how the Order
class should be mapped to the Order table in the database.
The 'id' element tells NHibernate that the Id property of the class should be used to identify each Order. This element also defines that the Id
property of the class maps to the OrderId column in the Order table. The data-type is also defined in there, and it is stated that instances of the Order
class that have a value of -1 for the Id property, are New/transcient (unsaved).
Now, NHibernate will use the Id
property of the Order
class to get or set the value of the Id.
This however, has some disadvantages in my opinion: what if you have some specific logic in the setter-part of the property, some validation-logic for instance ? When NHibernate loads an object out of the database, should this logic be executed ? In my humble opinion not. When an object is loaded from a persistent state, the values of the object must be set, without performing any extra logic.
Another problem pops up when the property is read-only. In this case, Id
is an 'identification', and the programmer that uses the Order
class, shouldn't be able to change this property. Therefore, this property has only a getter in the Order
class, and no setter. NHibernate will have a problem with that: it will throw an exception saying that it cannot set the value of the Id property since it has no setter.
That's why I use the access
attribute. With this attribute, I tell NHibernate that it should use the field instead of the property, and that it can find this field by taking the property-name that I've defined in the mapping file, prefix it with an underscore, and make the first character lowercase.
So, in this case, instead of setting the Id property, NHibernate will look for a field in the Order
class which is called _id
and give this field the value of the OrderId column. By using the field instead of the property, I'm always sure that no extra logic will be executed, and I do not have to bother with getter-only properties.
You can read more about this here and here Particulary in the comments of these articles.
The following three properties are quite straightforward. These elements just say to which column each property maps, and I've also defined that NHibernate should use the field instead of the property to get / set these properties.
The next two elements are bit more interesting though.
The many-to-one
element defines how the relationship between Order
and Customer
must be saved. As you might remember, the Order
class holds a reference to the Customer
to which the order belongs. To be able to populate the OwningCustomer
property which contains this reference, and to be able to save the link with the Customer
when the Order
is saved, the many-to-one
element is used. A Customer
can have many Orders
, and an Order
always belongs to one Customer
. The many-to-one
element just says that the OwningCustomer
property maps to the CustomerId
column in the Order table, and that the OwningCustomer
is of the type SomeShopDomain.Customer
. By defining this type, NHibernate knows that it should look for a mapping file which should be called SomeShopDomain.Customer.hbm.xml
in where the mapping of the Customer
type is defined.
Mapping files should be named after the class for which they contain the mapping, and should have the extension hbm.xml. Next to that, you also have to specify 'embedded resource' as build action for the mapping files.
The <bag>
element defines that the Order
contains a collection of OrderLines
, and that these objects must be of type SomeShopDomain.OrderLine
. NHibernate will use the mapping-file of the OrderLine
class to determine in which table those OrderLines
must be saved.
The <key>
element states that the Id
of the current Order
must be saved in the OrderId column of the table where the OrderLines
will be saved.
This concludes the quick tour of the NHibernate mapping file. For more information about the possible elements, I'll refer to the Hibernate in Action book, or to the Hibernate documentation.
The mapping file of the OrderLine
class, looks like this:
<?xml version="1.0" encoding="utf-8" ?>I'm not going to elaborate on this file, so lets get to the next part. :)
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="BlogShopDomain.OrderLine, BlogShopDomain"
table="OrderLine" lazy="false">
<id name="Id" type="Int32"
column="OrderLineId"
unsaved-value="-1"
access="field.camelcase-underscore">
<generator class="identity" />
</id>
<property name="ArticleName" column="ArticleName"
access="field.camelcase-underscore" />
<property name="ArticlePrice" column="ArticlePrice"
access="field.camelcase-underscore" />
<property name="ArticleId" column="ArticleId"
access="field.camelcase-underscore" />
<property name="NumberOfItems" column="NumberOfItems"
access="field.camelcase-underscore" />
<many-to-one name="OwningOrder" column="OrderId"
class="BlogShopDomain.Order, BlogShopDomain"
access="field.camelcase-underscore"
not-null="true" />
</class>
</hibernate-mapping>
Integrating NHibernate in the project
At this point, I've done some necessary configurations, but some coding needs to be done as well.
NHibernate uses the concept of Sessions as an 'interface' between your objects and the database. A session can be considered as a unit of work. This is a bit a simplistic definition, but you can find a better one in the Hibernate documentation or in Hibernate in Action.
To be able to create Sessions, you'll need a SessionFactory
. Unlike Hibernate Sessions, a SessionFactory is an expensive object to instantiate, so it's best practice to create this object only once.
Therefore, I like to create a Singleton class which holds amongst some other stuff, the NHibernate ISessionFactory
:
public class ApplicationSettings
{
private static ApplicationSettings _instance;
private ApplicationSettings() {}
// The only access point to the ApplicationSettings instance.
public static ApplicationSettings Instance
{
get
{
if( _instance == null )
{
_instance = new ApplicationSettings();
}
return _instance;
}
}
private ISessionFactory _sessionFactory = null;
public ISessionFactory SessionFactoryObj
{
if( _sessionFactory == null )
{
Configuration cfg = new Configuration();
cfg.AddAssembly (typeof(SomeShopDomain.Customer).Assembly);
_sessionFactory = cfg.BuildSessionFactory();
}
return _sessionFactory;
}
}
This structure guarantees that I'll have only one ISessionFactory in my project, and that this SessionFactory is created only once.
The code to create the ISessionFactory
is quite simple; first of all a Configuration
object is instantiated, and the assembly which contains the business classes is added to the Configuration
. As you can see, I use reflection to find that assembly, since I don't want to be bothered with assembly-names and locations. After that, the BuildSessionFactory
method is called which creates the ISessionFactory.
This ApplicationSettings
class is not part of my class library which contains the domain, but belongs to the project that contains my client application (in this case, a WinForms application). More on that later.
At this time, we can create ISessions
.
ISession s = ApplicationSettings.Instance.
SessionFactoryObj.OpenSession();
Creating Repositories for our Domain classes
In Domain Driven Design, getting objects from a persistence store, and getting objects into a persistence store, is being abstracted by the use of repositories.
As you might have seen in the previous articles of this serie, I've already created 'dummy' repositories for the unit-tests.
Now, it is time to implement the 'real' repositories, which will actually do the work of loading / storing objects to our database.
In the 2nd part of this serie, I've already mentionned the existence of the DomainSettings
class and the RepositoryFactory
interface.
At that point, I've only implemented the 'mock-repositories', but now, I want to implement repositories which use NHibernate to do some real work. This means that I'll have to create an NHibernateRepositoryFactory
class. I choose to put this class, along with the NHibernate-repositories in the same project / assembly where my business classes reside.
The NHibernateRepositoryFactory
class must implement the IRepositoryFactory
interface, which looks like this:
public interface IRepositoryFactory
{
ICustomerRepository CreateCustomerRepository();
IOrderRespository CreateOrderRepository();
...
}
There's one caveat here however... When using databases you'll be faced with 'Transactions'. A repository should not be the initiator nor the 'committer' of a transaction.
Why? The repository has no knowledge of the 'context' of the situation. The repository does not know when it should initiate and commit or rollback a unit-of-work.
As I've told earlier in this article, the NHibernate Session object is the NHibernate object which 'controls' a unit-of-work. Therefore, I'd like to extent the IRepositoryFactory
interface with methods which allow me to create a specific repository with an object which is responsible for transaction-handling injected into it:
public interface IRepositoryFactoryThis allows me to create repositories which can take part in a specific transaction/unit-of-work, and it also allows me to create repositories without having to take care about transactions. (This is also necessary; as you can see in the code in the
{
ICustomerRepository CreateCustomerRepository();
IOrderRespository CreateOrderRepository();
...
ICustomerRepository CreateCustomerRepository( object transHandler );
IOrderRepository CreateOrderRepository( object transHandler );
}
Customer
class in part 2 of this serie. There I need to retrieve a value, which is given to me by a repository. In this case, I do not really need a transaction context).I've chosen to define the transHandler
in my CreateXXX
methods in the IRepositoryFactory
as an object, since I do not want to couple the IRepositoryFactory
interface with NHibernate.
At this time, I can already start writing an implementation of a NHibernateRepositoryFactory
:
public class NHibernateRepositoryFactory :The
IRepositoryFactory
{
public IOrderRepository CreateOrderRepository()
{
ISession s = DomainSettings.Instance.SessionFactoryObj.OpenSession();
return new NHibernateOrderRepository(s);
}
public IOrderRepository CreateOrderRepository( object transHandler )
{
// convert the transHandler to an NHibernat ISession object.
ISession s = transHandler as ISession;
if( s == null )
{
throw new ArgumentException ("object of type NHibernate.ISession expected.",
"transHandler");
}
return new NHibernateOrderRepository (s);
}
}
NHibernateOrderRepository
can look like this:public class NHibernateOrderRepository :I will not elaborate here on the HQL and the specific
IOrderRepository
{
private ISession _context;
public NHibernateOrderRepository( ISession s )
{
_context = s;
}
public Order GetOrder( int id )
{
return _context.Get (typeof(Order), id) as Order;
}
public decimal GetOrderTotalForCustomerSinceDate( Customer c, DateTime date )
{
string hql = "select sum (ol.NumberOfItems * ol.ArticlePrice) " +
"from Order o, " +
"OrderLine ol, Customer c " +
"where ol.OwningOrder = o and o.OwningCustomer = c " +
" and c.Id = :custId and o.OrderDate >= :orderDate";
IQuery q = _context.CreateQuery (hql);
q.SetInt32 ("custId", c.Id);
q.SetDateTime ("orderDate", date);
object o = q.UniqueResult ();
if( o == null || o == DBNull.Value )
{
return 0;
}
else
{
return Convert.ToDecimal (o);
}
}
}
ISession
methods which I've used in the code above, since this would be truly outside the scope of this article. For more information, I'd refer -again- to the Hibernate documentation or the Hibernate in Action book.I do like to mention though that the bug which I've mentionned here is fixed in NHibernate 1.2.0 CR1. :)
As I'm typing this, I now also see that it would be better to let the repositories implement the IDisposable
interface. In the Dispose
method. In the Dispose method of the NHibernateRepositories, I can then check if my _context
is still open, and close it if it is.
Then, I should also change the code in the Status
property of the Customer
class, so that it looks like this:
public CustomerStatus StatusIf I don't do this, the ISession which is used by the
{
get
{
CustomerStatus result = CustomerStatus.Normal;
using( IOrderRepository or =
DomainSettings.Instance.
RepositoryFactoryObj.
CreateOrderRepository () )
{
decimal orderTotal = or.GetOrderTotalForCustomerSinceDate(this,
DateTime.Now.AddMonths (-3));
if( orderTotal > DomainSettings.GoldAmountTreshold )
{
result = CustomerStatus.Gold;
}
}
...
return result;
}
OrderRepository
would be kept open.Using NHibernate in the client application
Now that we've seen (albeit rather quickly) how we can use NHibernate, it is time to integrate it in the client application.
Because of the way NHibernate works when it has to save changed objects back to the DB, I'd like to use long living sessions.
When you retrieve an object from the database, NHibernate will keep this object in the Session. When you save the object back to the database, NHibernate will try to find the object in the ISession
instance which is used to perform the save. If the instance is found, NHibernate will update the corresponding record. If the instance is not found, NHibernate will create a new record.
If you use a separate ISession
to retrieve an object from the DB, and use another ISession
to persist this object back to the DB, NHibernate will asume that it has to insert a new record.
Therefore, it is -imho- best to use the same ISession
instance for retrieving and saving the same business object instance.
When you're using NHibernate in Windows applications, you can do this by using the 'long conversation' model.
In short, this means that you can keep your session open as long as the user is busy with a 'unit-of-work', but you can disconnect your session from the database when no database-access is needed for a while.
Although this article doesn't provide really in-depth information, I'd like to finish it here. This is yet another article which is way to long (maybe I should stop writing such long posts), but if you've gotten to here, please feel free to post any comments, critics, remarks, ...
2 opmerkingen:
Hi, I want to ask a question about service locator.
When you use "DomainSettings.Repository..." object in your domain, doesn't it cause a cyclic reference, or am i missing sth?
I guess your architecture is like this, domain assembly which contains repository interfaces, repository assembly which contains reference to domain. Then in which assembly service locator resides?
Hi,
It does not really cause a cyclic reference since I'm using reflection to load the assembly and create an instance of the class.
My project doesn't have a 'hard' reference to the actual assembly which contains the 'concrete' repository.
Een reactie posten