Hibernating Rhinos #9 - Application Architecture
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
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 ?
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
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.
Igor,
Which is why I explicitly stated that this is not DDD
"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)
{
}
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 ?
Matthieu,
Discussing of lazy loading is orthogonal to the discussion of futures
Why not? This is my usual MO
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.
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.
Please read this one:
ayende.com/.../Shocking-Rob.aspx
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!
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.
Oh, I see. Doing this in - e.g. WCF-session-per-request - an operation context is fine, just not in the repository itself?
Yes, that is the idea. In the repository is it to low level, at the entire operation, it works much better.
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
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.
Ayende, do you plan to add Future <t() method to IRepository <t of Rhino.Commons?
It is there, take a look at FutureQueryOf and FutureValue.
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?
Stephen,
Yes, that is basically how they work
^ 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?
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
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!
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).
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.
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.
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%.
Thanks for the nice overview.
Any reason why you put the IRepository in the persistance assembly and not in the domain assembly?
Tolomaüs.
@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"?
...status....to Tobin Harris...
googling for "automatic dirty checking" or "transactional write-behind"..700 results...working....
@liviu,
There is no such thing as ISet <t in the framework (which I consider a shame).
As for the rest, feel free not to use NHibernate, but read this first:
ayende.com/.../...urOwnObjectRelationalMapper.aspx>
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.
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.
Comment preview