NHibernate - Opinionated Style
NHibernate supports several programming paradigms that allows you to write what I consider good code, some of the ideas expressed in this post are opinionated and the merit of such should be reviewed against your own coding preferences and applicability to your project.
Creation
NHibernate requires each mapped type to have a parameterless constructor - However, this parameterless constructor does not need to pollute your domain model as it can be hidden from external code by marking it with protected or protected internal whilst your parameterized constructors can be public , meaning you can still enforce integrity at the constructor level, thus never allowing for creation of invalid entities. If for some reason you never want to create a Dog without a date of birth you could enforce this whilst still using NHibernate by doing something as simple as this:
public class Dog { protected internal Dog() { } public Dog(DateTime dateOfBirth) { this.dateOfBirth = dateOfBirth; } private DateTime dateOfBirth; public DateTime DateOfBirth { get { return dateOfBirth; } protected set { dateOfBirth = value; } } }
(You could optionally use an automatic property if using C# 3.0)
Where you don’t require a separate factory I’ve personally taken to creation static methods to give some more semantic meaning to the creation. Here’s a trivial example, but this style really comes into its own when you have multiple constructor parameter sets that have the same signature, not only do the static method names gives meaning, but you can’t vary a constructor unless you vary its parameter set too.
public static Dog CreateFromEstimatedAge(int estimatedAge) { var dog = new Dog {isEstimated = false, dateOfBirth = DateTime.Now.AddYears(-estimatedAge)}; return dog; }
2) Encapsulation
Classes that contain nothing but public virtual type name { get; set; } are potentially a big code smell. To preserve encapsulation one needs to carefully consider the visibility of each member and any constraints that should be imposed on the setters. For example, if you had a database key that was a simple auto-increment identity that required no visibility then you have no good reason to define a public setter for this property. On brownfield projects you should also consider if you need to map everything that’s in the database, if you’ve already gone past that point, use either the inbuilt tools or Resharper to find usage of properties (or rather where no usage exists).
2b) Don’t expose IList
These are big smells as far as I’m considered, when you do something like person.Children.Add(new Child("Fabio")) you’re violating the "tell, don’t ask principle", you should be delegating the work of adding a child to the person, however subtly in this case you’re asking for the collection of children and doing the work yourself. You should expose methods "AddChild()" - which throws it own meaningful exception when constraints are violated, and return an IEnumerable of Children via the .Children property (getter only). I disagree with throwing a not implemented via your own collection or not supported via a read only list since where a class implements a particular interface it should be substitutable without worry, I shouldn’t have to know that underneath it is a read only collection or a list stole of its owed behaviour.
Most consumers of an IList I’ve seen in projects use only the iterator and the Count property - which is available via Linq extensions method of Count() - perhaps not as efficient but I’m willing to make this tiny sacrifice. Linq extensions method also provide numerous other operations over IEnumerables including sorting, selection, projection etc.. Expose the lowest common interface of IEnumerable and you’ll be able to change the underlying concrete implementation as you please. Avoid creating your own collection type to handle constraints as this adds extra work and in my experience quite often the constraints that govern the collection are owned and unique to the particular type of parent.
4) Validation
You should be throwing a suitable exception if immediately possible when a call to a setter is attempting to put the object into an invalid state. So person.Age = -5 should throw ArgumentException. If a string property can not be null or empty, throw an ArgumentNullException when null and an ArgumentException when empty, follow the BCL conventions. Where more complex validation is carried out and it is the responsibility of the object to perform this validation then move away from properties, where properties run for more than a few lines or make calls to foreign objects (exception of lazy loading) then its time to consider moving them to methods.
I’ve seen a couple of people object to immediate validation, preferring to allow consumers to push the object into a known invalid state and then call IsValid or Validate or something similar (and even some delegating this to Interceptors etc..), but you can still achieve summary/delayed validation whilst not weakening your domain integrity which I’ll demonstrate in another blog post in a few days.
5) Repository
There’s been a lot of talk on generic repositories recently, I’m definitely a fan of hiding the generic repository in an inner repository, exposing find all by criteria style methods on the domain repository gives away too much of its own responsibility as well probably leaking some ORM specific dependencies out of your repository (think DetachedCriteria), and even NHibernate.ObjectNotFoundException etc.. This style of repository I think works quite well:
using System; namespace Cruisers.Ecommerce.Domain.Repository { public interface ICruiseRepository { IEnumerable<ICruise> GetCruisesOnDate(DateTime date); IEnumerable<ICruise> GetCruisesThatAreDiscounted(); ICruise GetCruiseById(int id); ICruise GetCruiseByBrokerCodeName(string codeName); } } namespace Cruises.Ecommerce.DataAccess { public class NHCruiseRepository : ICruiseRepository { private readonly IDaoRepository<Cruise> daoRepository; public NHCruiseRepository(IDaoRepository<Cruise> daoRepository) { this.daoRepository = daoRepository; } public IEnumerable<ICruise> GetCruisesOnDate(DateTime date) { throw new System.NotImplementedException(); } public IEnumerable<ICruise> GetCruisesThatAreDiscounted() { throw new System.NotImplementedException(); } public ICruise GetCruiseById(int id) { try { return daoRepository.Get(id); } catch(NHibernate.ObjectNotFoundException nhException) { // throw new ArgumentOutOfRangeException("id","cruise not found",nhException); throw new CruiseNotFoundException("cruise with id " + id + " not found.",nhException); } } public ICruise GetCruiseByCodeName(string codeName) { throw new System.NotImplementedException(); } } } namespace Cruisers.Common { public interface IDaoRepository<T, K> { T Get(K id); IList<T> FindAll(DetachedCritiera critiera); } }
But eventually you may reach a point when you have to consider sorting, paging, etc.. in these cases one can create a Query object to nicely encapsulate sorting and paging options and provide methods to accept such e.g.
IEnumerable<ICruise> GetCruisesOnDate(OnDateQuery onDateQuery)
As a further improvement in 2.1 we’ll be able to elegantly use the specification pattern (you can today but it is some work) and have the NH repository convert the predicates to NH queries, I intend to spike an example from the trunk soon. NH 2.1 will give us the ability to use Linq natively, but I recommend you avoid exposing this as you’ll have query logic (admittedly NH clean) thrown throughout your application, instead we harness the forthcoming Linq power to use clean specifications instead.
NHibernate - Opinionated Style | Matt Freeman’s Coding Blog…
Thank you for submitting this cool story - Trackback from DotNetShoutout…
DotNetShoutout
2 Feb 09 at 12:42 pm
[...] NHibernate - Opinionated Style - Matt Freeman looks at some of the enforce style that NHibernate brings to your code, and talks about how you can make these conventions nicer. [...]
Reflective Perspective - Chris Alcock » The Morning Brew #278
3 Feb 09 at 8:16 am
“…available via Linq extensions method of Count() - perhaps not as efficient but I’m willing to make this tiny sacrifice…”
Enumerable.Count extension method actually has a shortcut for ICollection interface - it just returns Count property without iterating the collection.
q
3 Feb 09 at 2:07 pm
I am one of the people that full disagrees with #4. Validation should be regulated into it’s own services, combining it with your domain objects make your code hard to test, spreads out the validation logic, makes complex validation nearly impossible and just leads to poor and unmaintainable business code.
Business code should not be inside the domain layer!
Now I do have some wiggle room on that and if you have simple validations such as Age must be > 0, or regular expression validators that make sure this string field for zip code really is a zip code, I can accept that in a domain layer.
I still think it’s bad practice to throw exceptions from properties in any situation though because then every single operation you ever do with your object you MUST surround it with try/catch, which isn’t really acceptable in my opinion which is why it makes sense to not combine domain objects + validation. But if you must atleast take advantage of enterprise library’s validation classes and do screening on Validation.Validate(object) instead of throwing random exceptions from setters.
Chris Marisic
3 Feb 09 at 5:08 pm
@q: Thanks for sharing. It’s handy to know that the Linq extension for Count will shortcut where it can, nice!
@Chris: Where different uses of the domain model require a variation in rules or where the rules are not stable then I somewhat agree that embebbing the rules may make make such undesireable. However for rules that are always applicable then these should exists in the domain model, your domain model should rich rather than weak. You could have both embedded and flexible rules, how they are injected is outside scope of this topic.
With regards to immediate validation, I disagree that you must surround every operation with exception catching, I’ll illustrate the technique and more importantly the concepts in a blog post forthcoming if you can hand on a few days.
mattcodes
10 Feb 09 at 8:56 pm