Wednesday, August 27, 2008

Domain object mapping

I have been using Nhibernate now for almost 2 years and to be honest for most of the time really missed the point of using an ORM. I think alot of this stems for the M$ influence mindset of building from the DB up. This is not necessarily a bad thing and to be honest is often a necessity if you are using an existing DB or working disparate to the DBA or owner. However as i explore the DDD world i see the benefit of build from the domain out, especially in complex domains that are not just crud operations.
Most of my time using an ORM i have just been using it as a glorified typed data set. The object would contain data in the form of properties that mapped to the structure of the table it is mapped to. This really is not using the full strength of an ORM, and to be honest if this is all you are doing then a code gen option like Net Tiers is probably a much better path (faster, less stress easier).
However now I feel the ORM really is a way of abstracting your domain from your persistence mechanism. Unfortunately it took an nasty database to show me the light.
I dont mind admitting my mistakes, usually its in a less public forum, however these are mistake I see frequently that i dont know if the authors are aware they are making.
So here's some guidelines that i am now trying to follow, take them for what they are.

Properties != Columns
Column and property names do not need to be the same. I have often seen the suffix "Flag" for Boolean columns; however this is less IMO readable than the “Is” prefix in managed code. do not feel the need to map names directly. use the most suitable domain representation

Incorrect Mapping of Types
I have seen properties that are of type string because the underlying table has a char(1) for Y/N flags. there is then a property that interrogate the string flag property and return a bool depending on the result... yuck. Why not use the mapping correctly
<property name="IsActive" type="YesNo" column="ACTIVE_FLAG" not-null="false" />
this also applys to numeric types and strings. There are more numeric types than int, so use the most appropriate one. if a string has a max limit on the DB of 30, enforce that in your map and in your class.

Inappropriate use of the public keyword
The public key word for some reason seems to be a default accessor. it should only be used when you w this aspect of your code.
Classes
If you have a class that is in way relevant to the outer world, do not mark it as public. Many mapping classes fall in to this category. I dont want to see Foo.FooBar[0].Bar... this show me implementation details that i dont care about and just add noise. If you need the join class but it serves no public functionality encapsulate it by providing a Bar collection property that transverses the join classes to give Foo.Bar[0]. You may want to consider if your loading strategy for this relationship is appropriate (ie not lazy) if you are using this out of session.
Bottom line is: ORMs are there to provide DB/Persistence ignorance not highlight the DB structure.
Properties
Properties that are mapped to columns may not always be publicly accessible. There may be time when if you update property A then B must be considered too. Dont make these setters public, or you give the impression that changing them freely is OK. Use a method to populate the properties so you can also encapsulate business logic associated to these.
Methods
This point follows on from the preceding point. You domain objects do not have to be buckets designed to carry data. They can and should have behaviour associated to them. Not doing so leaks your logic out of the domain and results in duplicated logic and hard-to-maintain uber services. Dont let this happen.

Fear of re factoring
It is often difficult to change a database especially in post live situations. this does not mean your domain has to reflect old/incorrect data structures. Feel free to remove mapped columns form your classes and mapping files if they no longer make sense in the domain. just because one area of development is wrong it does not mean it has to leak out to the other areas.

Light Handed Mappings
That title doesn't really makes sense, what i really mean is be heavy handed with your mappings, classes and tables. Assert your assumptions. If a column can not be null, assert that in the DB, maps and in the managed code. If you know the class a will always need reference to its collection of class B then set lazy loading to false. Ignorance is a common excuse, but its one i am trying to minimise

Redundant Repositories
A repository is generally only required for an Aggregate Root. Define your boundaries and remove redundant Repositories. Do you really need an ICustomerAddressRepository? get rid of it!

Confusing the purpose of a Repository
This may be more than a ORM issue but a design issue. A Repository is a store for entities. It does not do reporting. It does not do complex SQL searches that return weird and wonderful datasets. I "like" Greg Youngs notion* (which i initially thought was wildly over the top, but goes to prove a point) of just saving the id of aggregate root and then serialise the object graph in to the other column of the table. This database is not for reporting, its for serving the domain.
Although your approach man be a little less drastic, this highlights a point. If you cant think of your repository as an in memory store then you are not using like a repository. In fact my current thought is your should be easily be able to inject an in memory store that uses the I[Foo]Repository interface and work exactly as intend , albeit a smaller set of data (eg for testing). I may be going a bit OTT here and would like feedback.

*i hope i am not miss-quoting Greg

Hopefully that has clarified some of the errors i have done/seen repeatedly in the past. i would like to here feedback. have i yet again missed a point? am i being over the top?
lay it on :)

No comments: