As I said, before we go into the details, I think it's fitting to discuss the overview requirements that were defined in Chapter 8. I'm thinking about Persistent Ignorant (PI) level, supported life cycle, and database treatment.
High Level of Persistent Ignorant
The programming model of NHibernate isn't far from being runtime-PI, especially regarding the Domain Model itself. But that also comes with a price. For example, NHibernate doesn't help you at all with the inverse property management where you want to have bidirectional relationships. Instead, you are totally on your own.
Another example of such a price is that NHibernate won't help by telling you whether or not your instances are dirty by using an IsDirty property or something similar. (NHibernate will decide which instances should be persisted at persist time, but that's not what I'm referring to here.)
These are just two examples of the high level of PI coming at a price. Another such cost is performance, which might become problematic because of the high PI.
There are also some things that you have to do with your Domain Model classes in order to make it possible for them to become persisted by NHibernate at all. Here are some typical examples:
This isn't too bad, but there are still some things that lower the PI level. To be honest, I don't know how to avoid the problem when using the readonly statement, so I guess it won't be any better in other solutions unless they manipulate your code.
Certain Desirable Features for the Life Cycle of the Persistent Entities
In Chapter 5, "Moving Further with Domain-Driven Design," (and it's repeated in Chapter 8) I talked about the simple life cycle I needed to support for my Domain Model instances. The summary is repeated here again in Table 9-1.
This is easily mapped to NHibernate and is therefore easily supported. Let's add one more column for mapping Table 9-1 to NHibernate. Then it looks as in Table 9-2:
As you saw in Table 9-2, I added one more row about how to free the Identity Map/Unit of Work of an instance (or all instances). That wasn't something I envisioned before that I would need, but in reality I run into situations when this is needed, such as when "moving" instances between sessions.
Let's conclude by stating that there's not much more to say about this right now.
There are loads of details, of course, but we are staying at a bird's eye view here.
I know, you think I was thinking too much about NHibernate when I wrote that section in Chapter 5, don't you? That may be so, but you will find that most other O/R Mappers support this as well.
Deal Carefully with the Relational Database
Finally, I want the O/R Mapper to deal carefully with the Relational database. That is, I want it to treat the database in the same way or at least similar to how we would when we are programming it manually and being extra careful.
If the previous requirement (the life cycle) was "easy," this one is much harder and will vary quite a lot between different O/R Mappers.
It's easy to be critical about how NHibernate deals with the database in certain situations and think that you could do better manually. I have said over and over again that I think it makes sense not to optimize every call to the database because it most often doesn't matter much anyway. Instead, you can always go to the code and optimize the places where it really is needed. Still, the requirement is set because I don't want to optimize at all more than is necessary, and when I optimize I don't want to have to go to ordinary SQL more than is absolutely necessary.
I hope this didn't sound like "avoid SQL at any price" or "use only O/R Mappers that are extremely function-rich."
SQL is great, and I like using it when that's the best solution.
So what are the problem areas to look out for regarding NHibernate? An obvious one I think is that when we move processing out from the database, for example, we avoid having logic in stored procedures (and actually avoid stored procedures altogether). This is intentional because we want to achieve a better maintenance story by having all the logic in the Domain Model and its nearby surroundings. (And again, when we really have to, we can optimize the rare places with stored procedures.)
Another typical example of a problem area is when we do processing that is normally set-based, such as increasing a column value of all instances of a certain class (for example, setting the Price of all products to Price = Price * 1.1). Doing that in SQL is perfectly normal and recommended. With NHibernate, the sets will be single row sets regarding how it's processed against the database. This can also be a good thing, because the validation rules are most often instance-based, and we want them to be processed for large updates, too. However, the cost might be too high.
A third problem is that the consumer of the Domain Model probably doesn't care about the database at all. He or she might create a huge load on the back-end without realizing it or actually be almost incapable of realizing it. That's one drawback to the nice isolation and abstraction the Domain Model provides.
A fourth problem is that of selective updates if a certain criterion is met, such as
UPDATE x SET y = 42 WHERE y >= 1
That has to be dealt with by a pre-read instead.
A fifth is the "reading way too much" problem when too many complex types are read even though just a tiny amount of information is really needed. This is especially common if you haven't used a somewhat careful Aggregate design and haven't used Lazy Load.
Yet another classic problem is that of n+1 selects. Assume you fetch all Orders in the database and that OrderLines are Lazy Loaded. Then you touch each Order and its OrderLines. Then there will be a new SELECT issued for each Order's OrderLines. If you don't use Lazy Load, the problem will be smaller, but then you run the risk of getting into the problem just mentioned of fetching too much in some scenarios.
Neither of these examples is specific to NHibernate, but they are pretty normal for O/R Mappers. An example of a problem that I think is more related to NHibernate is that of IDENTITY or sequences. Using such an Identity Field leads to earlier INSERTs to the database than what you probably expect.
All in all, it's my opinion that NHibernate does a good job of fulfilling those overview requirements, especially if you are happy with the nature of how O/R Mappers typically work and the tradeoffs that are involved!