Ayende @ Rahien

Refunds available at head office

Hibernating Rhinos #9 - Application Architecture

image

It has been a while since I last published a screen cast, but here is a new one.

This one is in a slightly different style. I decided to follow Rob Conery's method of using a lot of prepared code instead of my usual ad hoc programming.

Please let me know what you think about the different style.

This is a far more condensed episode, lasting just under half an hour, and it is focus primarily on the internal architecture of a real world application.

I tried to go over a lot of the concepts that seems to trip people up when they come to define the structure of the application.

 

The technical details:

  • ~30 minutes
  • 28.4 MB

You can download this from this address: http://ayende.com/hibernating-rhinos.aspx

Comments

Matthieu
09/27/2008 03:27 PM by
Matthieu

Well done ! It's always interesting to have a clear explanation of software architecture !

A remark about the "Futures" : if I understand correctly : repositories can send back "delayed" collections. The actual SQL queries are not executed until the collections are used.

In the case of NHibernate the Session should be opened when the collections are used so it's like a OpenSessionInView pattern but more generic ? It's considered to be a bad idea : if your entity in your collection has a lazy load collection itself you can hit the N+1 problem ?

Igor Tamaschuk
09/27/2008 03:33 PM by
Igor Tamaschuk

I could say that 1 more role of repositores in the application is to allow access to Aggregate Entities only... Or to 'define' which entitie are the 'root' ones. You could have OrderRepository in your system but couldn't have OrderItemRepository... This kind of thing

Ayende Rahien
09/27/2008 03:51 PM by
Ayende Rahien

Matthieu,

re: futures,

I am not following your association from futures to lazy loaded collections.

The session is opened for the entire request, including the view.

Ayende Rahien
09/27/2008 03:52 PM by
Ayende Rahien

Igor,

Which is why I explicitly stated that this is not DDD

Matthieu
09/27/2008 04:30 PM by
Matthieu

"The session is opened for the entire request, including the view" <= ok

In your sample if your repository returns a collection of Employee (Future) and the entity Employee has a collection mapped as lazy, for example Adresses

if you do :

foreach(Employee emp in myFutureEmployeeCollection)

{

 foreach(var address in emp.Adresses)

  {

     someWork(address.Name) // <= N+1 : one select by adresses collection for each employee

  }

}

I'm not saying it's always bad, but if the lazyloading of collection/entities is not carefully explained to developpers you can have trouble here. I prefer to have methods in repository that explicitly stated the level of "loading"

FindEmployee //lazy loading

FindEmployeeWithAdress // not lazy laoding

And so the session is never open in the view. If you get the lazy loaded entities, a lazy exception will be throwed (for the developper I mean, not the user :)

I'm not saying that Futures is evil, just that it requires that the Session is open during the whole request, it's not always desirable ?

Ayende Rahien
09/27/2008 04:37 PM by
Ayende Rahien

Matthieu,

Discussing of lazy loading is orthogonal to the discussion of futures

Ayende Rahien
09/27/2008 04:37 PM by
Ayende Rahien

I'm not saying that Futures is evil, just that it requires that the Session is open during the whole request, it's not always desirable ?

Why not? This is my usual MO

Matthieu
09/27/2008 04:47 PM by
Matthieu

ok I brought the subject because I saw the code I wonder how the Futures can delayed the SQL. It's delayed because the Session is open and so on.

Why not? <= for the reasons I explained. Your session is always open, so if you have a complex graph of objects and it's lazy-loaded (and it's often the case with Nhibernate, lazy is set to true by default) you can have a lot of SQL queries executed without be aware of it.

Matthieu
09/27/2008 04:56 PM by
Matthieu

I shoud have said that it's not for all type of applications but only for ones which use a model with a lot of entities with a lot of associations.

Jan Van Ryswyck
09/28/2008 02:13 PM by
Jan Van Ryswyck

In this webcast, you mentioned that calling session.Flush after a session.Save is a bad thing. What if I'm using TransactionScope instead of NHibernate transactions?

Thx for your efforts for putting out this webcast!

Ayende Rahien
09/28/2008 03:22 PM by
Ayende Rahien

The problem is not the actual calling of Flush or Commit, the problem with that scenario is that you are doing this type of work in too lower layer.

Jan Van Ryswyck
09/28/2008 04:36 PM by
Jan Van Ryswyck

Oh, I see. Doing this in - e.g. WCF-session-per-request - an operation context is fine, just not in the repository itself?

Ayende Rahien
09/28/2008 04:41 PM by
Ayende Rahien

Yes, that is the idea. In the repository is it to low level, at the entire operation, it works much better.

Manuel Martone
09/28/2008 11:29 PM by
Manuel Martone

I really like this web cast, but some little things leave me in a sort of "middle state" not realing very well what just I can imagine:

I think I understood the concept behind futures "delayed" collections, I imagine it acts as a sort of "lazy" object...and this I like, but the last thing I cannot understand at all is, when you say that the session stuff (i.e. Flush, Commit) should be done ath higher level than repository, what if I have to deal with some atomic operations? This should be accomplished however by higher code?

Thanks for your work, and maybe I don't understood at all all your english in the webcast, so be patient

Ciao

Ayende Rahien
09/29/2008 05:24 AM by
Ayende Rahien

Manuel,

The problem is that managing the session at the repository level is removing all the advantages that NHibernate bring to the table. Change tracking, automatic persistence, batching, etc.

What you want is to have the session for the entire lifespan of the current request.

Abrakadabr
09/29/2008 11:45 AM by
Abrakadabr

Ayende, do you plan to add Future <t() method to IRepository <t of Rhino.Commons?

Ayende Rahien
09/29/2008 11:47 AM by
Ayende Rahien

It is there, take a look at FutureQueryOf and FutureValue.

Stephen
09/29/2008 11:49 AM by
Stephen

Very cool webcast, its probably really obvious to some people how to seperate layers of an architecture cleanly like this, I'm not a large application dev at all but I've always really worried about how generic you can treat a persistence layer given that you need to give it enough context about what you are trying to do.. talking to it inside a unit of work which is generic enough of a construct that it doesn't damage your business logics (in terms of succinctness and not having to write something thats specific to the persistence type), is a perfect way to give it plenty of context..

The futures thing is very neat, I take it these work inside a transaction so the first future iterated would trigger all deferred actions inside that transaction?

Ayende Rahien
09/29/2008 11:51 AM by
Ayende Rahien

Stephen,

Yes, that is basically how they work

Stephen
09/29/2008 11:52 AM by
Stephen

^ trigger is the wrong word, btw is the future thing abstract from the persistence layer? ie - does it just aggregate the 'true calls' and send them to the persistence layer at once when one is first needed - or is it up to the persistence layer to manage how futures are optimized inside a tx?

Ayende Rahien
09/29/2008 11:54 AM by
Ayende Rahien

Stephen,

This is implemented using facilities that the persistence layer gives us. I am not sure what you mean with regards to abstract from it

Stephen
09/29/2008 12:06 PM by
Stephen

Sorry I just meant is the future coming from the persistence layer, or is it something that sits on top of the persistence layer.

But I've just rewatched the part about futures and I see that a future isn't something the persistence layer has to understand, the future will purely just manage deferring and then aggregating the queries inside a transaction..

ps: I'm just being nosey and I could be wrong in my assumptions!

alberto
09/29/2008 06:15 PM by
alberto

Thanks, the automatic persistance part was very clarifying. (I had to watch a couple of times to understand what you meant, though, I might be a little slower than usual today).

liviu
09/30/2008 03:15 PM by
liviu

Hi Ayende,

it's cool to have a presentation. But, i see that you advocating decoupling, have still added a reference to Iesi.Collections.dll to the model project. That's a bit of cheating. You KNOW you will use NHIBERNATE. The same with all virtual properties in the model, they need to be there in order to have NHibernate instrument the lazy loading...

Hmm, if it quacks like a NHibernate it must be NHibernate :--).

What is good design in writing hbml by hand to persist a model?

I add a property in the model, oops, i have to change my hbml that sits in another project.

I would have say it is a good design if: the model would have been taged with some generic persistence attributes that do not depend on any ORM.

Based on those attributes the persistence cooking would have been handled by a repository wrapper.

I would have been impressed if the primary key would be hidden from the model. If i may choose to implement GUIDs as primary keys, do i have to change the model? or just the persistence model?

I would have been marvled if the persistence layer would be generic, without any assumption about the model persisted.

Yet, that is not the case.

Ayende Rahien
09/30/2008 04:22 PM by
Ayende Rahien

liviu,

Iesi.Collection has no association to NHibernate.

.Net doesn't have an ISet/Set implemention, so we need to go to Iesi for that.

If you consider that and virtual properties a violation of POCO, you may do so. I find this appropriate and acceptable

As for the mapping, there are many strategies to deal with that, ranging from plugins to R# and VS to NHibernate Conventions.

For the PK, you can hide that if you want. I haven't found a good reason to do so in any of my projects.

liviu
09/30/2008 04:55 PM by
liviu

Ayende,

Ok, but why not ISet <t and HashSet <t?

I work for a company that develops enterprise software in the spagetti code way, antipatterns championship way.

Many times i encounter situations when some kind of changes, optimizations are to be done in the whole application.

Because things were not abstracted enough, it is always a pain for the developers and QA to correct old mistakes and guarantee a deliverable version...

For example: the type of PKs, some numeric usertypes precision and size, how incremental fetch is implemented, how certain roundings happen for financial calculations, etc

With Nhibernate i have a problem: why do i have to pass an xml?

Why it is not better to say: Hibernate map this TYPE t and do not try to find it by a string later...nhibernate proxies my objects, takes bad dicisions in my opinion for object tracking, but yet, has no decent documentation how to override that...

I wrote a simple ORM mapper in 2 weeks. I prefer to write my 10% solution instead using undocumented Open source code for that exact 10%.

Toloma&#252;s
09/30/2008 07:05 PM by
Tolomaüs

Thanks for the nice overview.

Any reason why you put the IRepository in the persistance assembly and not in the domain assembly?

Tolomaüs.

Tobin Harris
09/30/2008 08:02 PM by
Tobin Harris

@liviu

NHIbernate came out of Beta in 2005, there's good documentation, tons of forum posts, sites, blog posts and books (if you include Hibernate books). Plus you can browse the source to see what's going on :)

But, I agree it's still not perfect, but we're getting there :)

I'd be surprised if there's no documentation or blog posts about object tracking - have you searched for "automatic dirty checking" or "transactional write-behind"?

liviu
09/30/2008 08:27 PM by
liviu

...status....to Tobin Harris...

googling for "automatic dirty checking" or "transactional write-behind"..700 results...working....

liviu
10/01/2008 07:37 AM by
liviu

Hi Ayende,

I agree, the 25 points list not to write your own ORM are valid. But, is it not more frustrating using another framework ( i am not "blaming" Hibernate) that does not perform things as you expect (need)? Trying to tweak something bigger than you written by others that is in continous change...? Nhibernate touches all 25 points you mentioned, but doesn't handle them perfectly.

Achieving ORM independence is impossible. I was cheating a little when i show myself "mad" that you seem to favor NHibernate in the design.

Because all the 25 points are implemented more or less in different ORMs, and some things are really different, and you just cannot escape that without writing a big wrapper for every persistence method.

No ORM is perfect, but this is just because ORMs are not trying to solve the HARD problems, but just the many EASY ONES.

Persistence is very tightly related to logic of the model. This may seem strange but i give you an example:

a) Lazy loading. I have entity A with a collection of B. Imagine that for each A i have ~ 5000 B. I am in UI, I load A but i wanted to optimize that so I don't load B collection yet. The B collection loads lazy when it is accessed, but let say it is loaded 5 seconds after I load A. But what if another user updated B collection? It is a problem if I have an aggregate in A, A.SumB = SUM(B), because i will load a different collection and the sum will be wrong. The solution is that i have to recalculate the SUM on the client side each time I load B collection.

If i want to cheat and have the sum calculated in the database with a trigger, I have still to detect that A has changed after i lazy load B collection and refrech A.SumB.

What is here tricky is that i cannot cheat and optimize my aggregate calculation only when B items are added or deleted. I have to explicitily calculate the FULL sum on LOAD, after lazy LOAD.

It is a waste to calculate the sum again if i don;t lazy load B collection, so I am bound when doing model logic from persistence and retrieval strategies.

The scenario is more funny if i do some sort of paging on the lazy loaded collection.

Another thing are rules. Imagine i have some business rule on A that depends on what is inside my B collection. If i want to check it on client side, so that the user does not do 10 operations, saves and get an error, that is not enough. I have a snapshot of data. So i have to check the rule also when saving data, in transaction, because B can change....so I depend upon persistence ....

I personally am working just on a thing like that. An ORM that is aware of the model fields and relations but also ON LOGIC. Because if i don't want my model to know about persistence, than a persistence layer must know about some LOGIC rules in the model.

Ayende Rahien
10/01/2008 08:08 AM by
Ayende Rahien

does not perform things as you expect

NHibernate's behavior is well documented and has the extension points to change if you need it.

Use collection filters for doing that work.

You need to understand that in multi threaded world, there is no such thing as the correct answer, only correct at a given time.

That is why NHibernate support the notion of optimistic and pessimistic concurrency.

Comments have been closed on this topic.