Simple NHibernate Example, Part 2…, the “Issue” of Identity
Before going into repositories, it is important to review the idea of identifying entities in the domain. In Domain Driven Design, by definition, an Entity is a domain object that is defined primarily by its identity.
Identity will play an important role in interacting with repositories; looking up, saving, and tracking entites.
Consider identity from a purely technical point of view:
- At the CLR level, identity is provided by the object reference. A domain object is considered the same as another, if they both point to the same object reference. Given two objects and in a single NHibernate Session, then you can test to see if the two objects are the same with code such as:
leadBuyer1 == leadBuyer2
- At the database level, there are many ways to define keys to uniquely define a row. But for our example, we’ll use a simple identity column and so we end up with two objects equal to each other when: <pre style="margin: 0px">leadBuyer1.Id.Equals(leadBuyer2.Id)</pre>
The first example is object identity, but the second example is more relevant to us since this is persistent (or database) identity.
From a Domain Driven Design perspective, we have similar identity concepts to those in a database. From the attributess of an entity, we can identify natural candidate keys (or identifiers). These are combinations of attributes on the entity that provide the entity a unique identity within the domain. Whether or not the candidates are useful depends on many things such as the general usefulness of the candidates in the context of the domain, the stability of the underlying attributes (they cannot be null, change over time, and must guarantee uniqueness), and simply whether or not the entity has any candidate keys.
In the case where no candidates can be found, a surrogate key is often used on the entity. This identifier has no direct relationship with the attributes of the domain, but is guaranteed to identify the entity as unique.
However, the use of surrogate keys in instances where an entity DOES have candidate keys is fairly common practice. This makes sense because it is often difficult to guarantee that the attributes of the domain will never change. By default, domain objects are often purposefully “marred” with these surrogate keys – identifiers with no domain specific meaning, except to guarantee uniqueness of the entity.
Assume that the domain entity has the following specifications:
using Foo.Domain;
using NUnit.Framework;
namespace Tests.Foo.Domain
{
[TestFixture]
public class WhenSettingUpANewBuyer
{
private Buyer buyer;
[SetUp]
public void SetUpContext()
{
buyer = new Buyer();
}
[Test]
public void ShouldAllowFirstNameToBeCaptured()
{
buyer.Name = "MyName";
Assert.AreEqual("MyName", buyer.Name, "Buyer Name was not captured.");
}
[Test]
public void ShouldAllowEmailToBeCaptured()
{
buyer.Email = "[email protected]";
Assert.AreEqual("[email protected]", buyer.Email, "Buyer Email was not captured.");
}
}
}
Simple enough, but how do we want to guarantee that the entity is uniquely identified in the domain?
The user could look up the entity by Name or Email. In fact, Email for a user is most likely unique and tends to be a unique identifier for a users in web-based systems. However, think about the uniqueness of these attributes.
What if a buyer is really a corporate entity and the email address is associated with a single employee’s email. What happens then if he or she leaves the company? A system user could change the email address in that case. The email attribute is not necessarily guaranteed to be unique or to remain the same over time (unless we provide that specification, of course).
A surrogate system key is going to be the answer. In fact, to keep this potentially long-winded discussion short, a surrogate system key will be associated with ANY entity defined in the system. The system identifier will guarantee uniqueness and NHibernate will now be able to manage the domain entity.
Going back to the specification, the ID will simply be set to 0 (uninitialized) on a new buyer. Also, no specification will be made (can be made?) on setting the ID.
[Test]
public void ShouldNotHaveSystemIdSet()
{
Assert.AreEqual(0, buyer.Id, "Buyer ID should not be set until saved to the repository.");
}
The specifications result in the following domain entity:
namespace Foo.Domain
{
public class Buyer
{
private string name;
private string email;
private int id;
public virtual string Name
{
get { return name; }
set { name = value; }
}
public virtual string Email
{
get { return email; }
set { email = value; }
}
public virtual int Id
{
get { return id; }
}
}
}
Note the virtual modifiers on the properties. Marking the properties with virtual is recommended when using NHibernate or the Rhino Mocks mocking framework. Similar to the creating the surrogate key, this is a compromise for utilizing the tools – creating proxies from our original classes is now possible. 🙂
No specification has been made that would require the system to directly set the Id. NHibernate will set the Id property using reflection, so we do not need to worry about a setter.