Requirements of the Persistence Infrastructure


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:

  • It's not possible to use readonly for fields.

    The most intention revealing construction if you need a read-only property in a class is to use the readonly keyword, but then NHibernate can't set the field by reflection. Therefore, it has to be converted to private field + a public get property.

  • All classes must have default constructors.

    So even if you don't need one in your Domain Model, you have to have a constructor without any parameters. The constructor can be internal and even private, but you still need to add it.

  • You should avoid strongly typed collections.

    I often don't see that as a very bad thing anyway. I mean, I like to use IList, for example, instead of a CustomerList class. Generics, of course, is also very good.

    (Unfortunately, at the time of writing, generics isn't well supported by NHibernate, so you have to go through some jumps here and there.)

  • You shouldn't set Identity Fields.

    Normally, you would give Identity Fields default values, such as Guid.NewGuid(), but that's not a good idea if it's possible to avoid it when you use NHibernate because NHibernate uses the Identity Field values to determine if it should be an UPDATE or INSERT. If you provide a default value (unless you set it to unsaved-value, of course), NHibernate will always expect UPDATE if you don't explicitly provide guidance. (You can also use the version tag for signaling INSERT/UPDATE.)

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.

Table 9-1. Summary of the Semantics for the Life Cycle of the Domain Model Instances

Operation

 

Result Regarding Transient/Persistent

Call to new

Transient

Repository.Add(instance) or persistentInstance.Add(instance)

Persistent in Domain Model

x.PersistAll()

Persistent in Database

Repository.Get()

Persistent in Domain Model (and Database)

Repository.Delete(instance)

Transient (and instance will get deleted from database at x.PersistAll)


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:

Table 9-2. Summary of the Semantics for the Life Cycle of the Domain Model Instances, with NHibernate

Operation

NHibernate

 

Result Regarding Transient/Persistent

Call to new

Call to new

Transient

Repository. Add(instance) or persistentInstance. Add(instance)

ISession.Save(), Update(), SaveOrUpdate(), or associate with persistent instance

Persistent in Domain Model

x.PersistAll()

ISession.Flush()

Persistent in Database

Repository.Get()

ISession.Load(), Get() or List()

Persistent in Domain Model (and Database)

 

ISession.Evict(), Clear() or Close()

Transient (but not deleted); not in control of the Identity Map/Unit of Work

Repository.Delete(instance)

ISession.Delete()

Transient (and instance will get deleted from database at x.PersistAll)


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.

Note

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.

Note

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!




Applying Domain-Driven Design and Patterns(c) With Examples in C# and  .NET
Applying Domain-Driven Design and Patterns: With Examples in C# and .NET
ISBN: 0321268202
EAN: 2147483647
Year: 2006
Pages: 179
Authors: Jimmy Nilsson

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net