Sunday, 21 April 2013

Creating your Domain Model with TinyNH, a Simple NHibernate Infrastructure

TinyNH is a demo project and series of articles designed to help you get your project up and running with a simple solid NHibernate persistence infrastructure. See contents for a full list of articles.

Introduction

The raison-d'ĂȘtre of our infrastructure is to load and save instances of our objects from and to the database, so let’s start with our domain model.

Background

If you are using an ORM, then you are using a domain model architecture. Your domain model consists of classes that capture the data and behaviour of "things" in your business domain.

The "things" that you model within a domain are commonly described as “entities” and we'll be using this term throughout these articles. To be more precise, the term "entity" is being used here to describe "things" in our business domain that that have some concept of identity and ongoing life-cycle.

Our Model

TinyNH uses a simplified e-commerce catalogue model, containing Product, Category and Supplier entity classes.


We've chosen to locate our entity classes within the Domain namespace of a TinyNh.DemoStore.Core C# class library project. This project contains core functionality that is shared by 2 applications in our solution: the catalogue admin web application and a product importer console application. For a simpler solution, there’s no reason why your solution couldn't consist of, say, a single ASP.Net web application containing controllers, views etc, along with your domain model.

Here's the Product class:

Look at Product.cs on github if you can't see the code in-line above.

I said it was simple... TinyNH’s focus is on “where to put things” when setting up NHibernate for your model, not on the design of your entities or the specifics of your NHibernate mappings. Our entities don't have much in the way of behaviour either. For more about developing behavioural domain models, Eric Evans' Domain Driven Design book and the related discussion group are excellent resources.

Entity Base Class

Domain model entities usually have some shared characteristics that can be extracted to a common base class. TinyNH includes the Entity abstract base class that is inherited by all entity classes in our domain model. Here's the code in full:

Look at Entity.cs on github if you can't see the code in-line above.

This class provides a common Id property and an Equals / GetHashCode implementation.

Id Property

Our Product, Category and Suppliers are root entities - they exist independently, have their own identity and can be queried directly from the database. As such, they need an identity field. On a new project, where you have full control over the database schema, it is a common practice to use surrogate key as an identifier, an an arbitrary “meaningless” value generated by your application / database, rather than a real-life property from the application domain.

We have chosen the type System.Guid for our Id property. We will configure NHibernate to generate our Id values using the Guid.Comb strategy. Guid.Comb is a "special kind of GUID", that performs well when used as a primary key in a relational database (see Jimmy Nilsson's article for background).

If you really don’t like GUIDs (do users of your application get cross when you ask them to read out GUID values over the phone?), you can use ints for your Ids instead and get either NHibernate or the database to supply the values as objects are saved.

Equals Implementation

The default implementation of Equals for reference types is based on reference equality. 2 different Product instances with the same Id value would not be equal if compared using object.Equals method. Within the scope of your application, you would probably consider these two entity object instances to be logically equal, i.e., they are 2 different objects materialised from the same database record and represent the same entity in your business domain.

Our entity base class overrides the Equals method to compares objects based on their identity. If two objects are of the same type and have the same Id value, then they are considered equal. There are a few issues to consider around NHibernate-generated proxies and unsaved objects - see the comments in the Equals override for full details. Overrides of the == and != operators have not been implemented, it is not recommended for mutable reference types.

Note that if you have 2 unsaved objects (with no Id value to act as an identifier), there is no attempt to determine whether they might still be logically equal. If you can think of scenarios where this might introduce a problem, then you need to read up on a more involved approach to equals and gethashcode implementation. See Sharp Architecture’s entity base class for another example, then read this detailed discussion.

But before you do, here’s a test: Can you suggest a scenario within your application where you need business key equality to support identification of 2 different unsaved objects that represent the same logical entity? Don’t introduce this requirement into your project if you don’t need it.

GetHashCode Implementation

Because all objects that are equal should have the same hash code, our entity’s hash code is generated using a combination of the entity's type and value of its Id property.

It is important to be aware that the hash code returned by Entity.GetHashCode will change when an entity is saved and given an Id value (from this point onwards, the hash code will be stable however, so there's light at the end of the tunnel during these trying times...). Therefore, we should bear in mind the following rule from Eric Lippert's Guidelines and rules for GetHashCode: "the integer returned by GetHashCode must never change while the object is contained in a data structure that depends on the hash code remaining stable".

This means that unsaved entities should probably not be used as a key in a hashtable if they are going to be saved at some point while the hashtable is in use. In practice.

Note that the complications around entity equality and hash codes are not specific to NHibernate. A correct implementation is important for domain model entities regardless of how they are saved to a data store. You'll get very funny results when using LINQ operators like Distinct if you don't implement it.

How to do it

It's time to add some code to your project.

1. Choose a suitable place in your solution for your domain model entities.

2. Copy and paste the Entity.cs class from TinyNH into your project and modify the namespace accordingly.

3. Start adding your own entities. Note that you’ll definitely feel more “in control” if you first get your NHibernate infrastructure running end-to-end with the simplest possible scenario to start with, so consider starting off with a single entity to start with.


No comments:

Post a Comment