Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DDD: Primary keys (Ids) and ORMs (for example, NHibernate)

Why is it considered OK to have an Id field in the domain entities? I have seen several solutions that provide base class with Id and Id-based GetHashCode/Equals.

My understanding of domain model is that it should contain only things related to the domain. While in rare cases (trackable orders) Ids are meaningful, most of the time they do not provide anything except a simple way to reference objects in DB/on UI.

I do not see a Equals/GetHashCode benefits either, since the Identity Map implementation should guarantee that reference equality is the id equality anyway.

Strangely, I can not easily find what other people think on this subject, so I am asking it here. What is the general opinion on using non-domain related Ids in the domain entities? And are there any problems with NHibernate if I do not add Ids to my domain entities?

UPDATE:

Thanks for the answers.

Several of them suggest that having Id is the only way for the ORM to do a DB update. I do not think this is the case. ORM already keeps track of all entities loaded from the DB, so it should be easily able to get an Id internally when it needs one.

UPDATE 2:

Answer to Justice and similar points: What if we have a web application and need a way to reference the entity between sessions? Like edit/resource/id?

Well, I see this as a specific need of the constrained UI/environment, not a domain model need. Having an application service or repository with GetIdentitity method (consistent with Load(identity) method) seems to be enough for this scenario.

like image 967
Andrey Shchekin Avatar asked May 13 '09 20:05

Andrey Shchekin


3 Answers

I just can talk about NHibernate. There you need a field for the primary key, it's up to you if you take business data (not recommended) or a surrogate key with no business meaning.

Typical scenarios are:

  • auto-incrementing value generated by the database
  • guid generated by NHibernate
  • guid generated by the business logic

There is a big advantage to have a unique id. When you pass your object around, for instance to the client and back to the server, you can't rely on memory identity. You could even have several instances in memory of the same business instance. The business instance is identified by the id.

Id-based comparison is a matter of taste. Personally I like simple and reliable code, and id-based comparison is simple and reliable, while deep-comparison is always a bit risky, error-prone, unmaintainable. So you end up with operator == comparing the memory identity and Equals comparing the business (and database) identity.

NHibernate is the less intrusive way to map a class model to a relational database I know. In our large project we have primary keys and version properties (they are used for optimistic locking). You could argue that this is intrusive, because it is not used for the business logic.

NH doesn't require to have these properties. (however it needs one of the properties as primary key.) But consider:

  • It just works better, eg. pessimistic locking is only possible with a appropriate property,
  • faster: int id's perform better on many databases then other data types
  • and easier: taking properties with a meaning to the business are discouraged for several reasons.

So why making your life harder than necessary?


Edit: Just read the documentation and found that NHibernate does not need an id property in the persistent class! If you don't have an indentifier, you can't use some features. It is recommended to have it, it just makes your life easier.

like image 65
Stefan Steinegger Avatar answered Nov 16 '22 02:11

Stefan Steinegger


Typically, you will not have the one true domain model. You will have innumerable concurrent and sequential sessions, which must be coordinated, and each of which contains within it its own miniature, isolated, transactional domain model. You will not have the same object instances between two concurrent or between two sequential sessions. But you will need some way to ensure that you can move information from one session to another.

An example, of course, is a /resource/list and /resource/show/id pair of URL's (corresponding to an appropriate pair of controller actions in asp.net mvc). Moving from one to the other, there are two completely independent sessions, but they need to communicate (through HTTP and the browser) with each other.

Even without using a database, your domain model objects will still need some form of identity because you will still have numerous miniature, isolated domain models, not the one true domain model; and you will have all these domain models in independent sessions both at the same time and one after another.

Object sameness does not suffice, because objects are not the same between sessions even though the underlying conceptual entities which they represent are the same entity.

like image 41
yfeldblum Avatar answered Nov 16 '22 01:11

yfeldblum


In Nh, you can't use reference equality for detached instances. This stuffs you for using NH in clients that would like to use this behaviour (in my opinion this is the correct behaviour, NH doesn't depend on magic!) .

In web apps or session based usages where you can go to the db in the scope of one ISession, I think I am correct in saying you can rely on reference equality. But in a smart client scenario, or more specifically, any scenario in which two ISessions are sharing an instance (one loaded from or inserted into the db, then passed to the second), you need the db id field(s) stored along with the instance.

the second session below would no idea that the instance that it is being passed is has already been inserted and requires an update, and even if you changed it to an update, I assume I am right in thinking it is not going to know the DB ID of the class.

class SomeEntityWithNoId
{
public  blah etc {}
}

class dao
{

void DateMethod()
{
var s1 = sessionFactory.OpenSession();
var instance = new SomeEntityWithNoId();
instance.Blah = "First time, you need to insert";
s1.Save(s1); //now instance is persisted, has a DB ID such as identity or Hilo or whatever
s1.close();
var s2 = sessionFactory.OpenSession();
s2.Blah = "Now update, going to find that pretty hard as the 2nd session won't know what ID to use for the UPDATE statement";

s2.Update(instance); //results in boom! exception, the second session has no idea *how* to update this instance.




}
}

If you are concerned about the ID field(s), which you are correct in thinking is largely useless for anything other than persistence concerns, you can make them private, so at least this behaviour and semantics are encapsulated. You might find that this approach puts some friction into TDD.

I think you are correct about the ID maps with regards to NH at least. If the instance is transient when it is first attached to the session, your type does not need to store its db ID in a field. I think the session will know how to manage it's relationship with the db. I'm sure you can bust out a test to prove this behaviour ;p

like image 21
Noel Kennedy Avatar answered Nov 16 '22 01:11

Noel Kennedy